Ruby -> Crystal: n.to_s[0].to_i

In Ruby:

123.to_s[0].to_i  # => 1
 87.to_s[0].to_i  # => 8
  0.to_s[0].to_i  # => 0

What’s the Crystal equivalent code?

123.to_s[0…0].to_i

Thanks @bcardiff.

Correction: to_s[0..0] instead of to_s[0...0] (2 dots, not 3).

I searched all over the docs for to_s but couldn’t find this example.
Where is this shown/explained?

Let me say that the net result of the expressions is the same in Crystal.

String#[0] returns the first character as a Char, and if the char is a digit in base 10 Char#to_i returns it as an integer, which is guaranteed to be the case because we start from non-negative integers:

% crystal eval '[123, 87, 0].each {|n| p n.to_s[0].to_i}'
1
8
0

The inner workings are different because String#[0] returns a string in Ruby (nowadays), and because String#to_i does not raise as Char#to_i could do with an arbitrary receiver.

So, the given examples work the same in Crystal, but whether that really matters depends on the real usage of that idiom.

1 Like

I would reach for something like

struct Int
  def first_digit
    n = self
    while n >= 10
      n //= 10
    end
    n
  end
end
 
pp 123.first_digit
pp  87.first_digit
pp   0.first_digit

Since version 2.4 Ruby has Integer#digits, so idiomatic Ruby would be 123.digits.first.
Maybe Crystal could have such method too.

Note however 123.digits return [3,2,1], not [1,2,3]. So it should be 123.digits.last to get 1 as in the original example.

Good catch, thanks!

For the record: The Ruby code has exactly the same results in Crystal. You don’t need range literals to specify the first character, a simple integer index works.

1 Like

I’ve actually given coding exercises to candidates where finding arbitrary digits is a useful path to solving it. This was the most elegant solution one candidate submitted (translated to Crystal):

struct Int
  def digit(place, base = 10)
    digit_count = Math.log(self, base).floor.to_i + 1
    
    if place > digit_count
      raise "Can't find digit #{place} of #{self}"
    end
  
    self // (base ** place) % base
  end
end

123.digit(2)

Also it looks like the Crystal parser for syntax highlighting doesn’t distinguish the // integer-division operator from an empty regex. :joy:

Related: https://github.com/crystal-lang/crystal/issues/8554.

If I remember correctly the Ruby discussion boiled down to this.

Since a decimal integer 123456789 can be decomposed to its base 10 exponents: 9*10^0 + 8*10^1 +..+ 1*10^8, the exponents correspond to the array index, so you can do n.digits[d] to easily get the digit you want. The array is also easier to generate by just slicing off the LSD and shifting, as the algorithms show.

Thus, Ruby went with 123.digits => [3, 2, 1]
as 43210.digits[0] => 0 makes sense, as there’s always an LSD as n[0] * 10^0.

Also in Ruby digits doesn’t work for negative integers (which it shouldn’t conceptionally, because the negative sign is not a digit), so to get the digits of the number you just do n.abs.digits, and go forward from there.