Ruby Gotcha of the Day: String Ranges

Posted by kev Wed, 20 Jun 2007 19:05:00 GMT

Spot the pattern?

('1'..'10').to_a
# => ["1", "2", "3", "4", ..... "10"]

('2'..'10').to_a
# => []

('2'..'20').to_a
# => ["2", "3", "4", "5", ..... "20"]

('3'..'20').to_a
# => []

('3'..'30').to_a
# => ["3", "4", "5", "6", ..... "30"]

('4'..'30').to_a
# => []

('4'..'40').to_a
# => ["4", "5", "6", "7", ..... "40"]

(2..10).to_a
# => [2, 3, 4, 5, 6, 7, 8, 9, 10]

('2'.to_i .. '10'.to_i).to_a
# => [2, 3, 4, 5, 6, 7, 8, 9, 10]

Posted in ,  | 5 comments

Comments

  1. Avatar Matthew Conway said about 1 hour later:

    Interesting – took me a while to figure it out :)

    • spoiler *

    I wouldn’t call it a bug though – ‘2’ is > ‘10’ i.e. strings don’t sort like numbers

  2. Avatar Dr J said about 2 hours later:

    Very good find…

  3. Avatar Kevin Clark said about 7 hours later:

    Ok, whether this is a bug or not is certainly up for interpretation, but it’s an interesting gotcha either way.

  4. Avatar Jean-Philippe Bougie said about 9 hours later:

    String#succ, which is used by the the range is quite a strange beast, especially since its rules do not conform well to String#<=>. succ has special treatment of carry with characters and numbers, and doesn’t follow the normal ascii set. For example, ‘9’.succ is ‘10’, ‘z’.succ is ‘aa’, but ’ ?’.succ is ’@’. On the other hand, <=> goes character by character. So yes, it is definitely a gotcha, but stranger things do happen with ranges :

    (‘a ’..’b’).include? ‘a ’ => true (‘a ’..’b’).include? ‘b’ => true (‘a ’..’b’).to_a => [‘a ‘] # where is ‘b’?

  5. Avatar Jean-Philippe Bougie said about 9 hours later:

    oops, those small examples got mangled

    (‘a ’..’b’).include? ‘a ’ => true

    (‘a ’..’b’).include? ‘b’ => true

    (‘a ’..’b’).to_a => [‘a ‘] # where is ‘b’?

    In the end, one has to be careful using string ranges with Range#to_a, as it can give strange results even with alphabetic characters only :

    (‘A’..’b’).include? ‘a’ => true

    (‘A’..’b’).to_a.include? ‘a’ => false

    (‘A’..’b’).to_a => [‘A’, ‘B’, ‘C’, ... ‘Z’]

    ‘Z’.succ gives ‘AA’, which is greater than ‘a’, thus #to_a stops at that point

Comments are disabled