简体   繁体   中英

Why is the difference between the Range objects 'A'..'AB' and 'B'..'AB' in Ruby?

Can someone please explain me why

('A'..'AB').to_a
#=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB"]

but

('B'..'AB').to_a
#=> []

?

string1..string2 will list "all" strings between the two values in "dictionary order" .

More precisely, it will repeatedly call String#succ on string1 , until it reaches string2 .

"A".succ == "B"
"B".succ == "C"
# ...
"Z".succ == "AA"
"AA".succ == "AB"

On the other hand, if string2 < string1 (ie " string2 comes before string1 in a dictionary"), then you get an empty range. There is a slight discrepancy here between the initial use of String#<=> to check whether the range is "valid" , and then reaching string2 by calling String#succ on string1 .


If you want to generate that list: ["B", "C", ... , "Z", "AA", "AB"] , then you could specify a "valid" range and then remove the first element(s):

('A'..'AB').drop(1)

Suppose instead that you want to use two strings that are in the "wrong order" - eg "BA" and "B" . Note that this list will be counting "backwards":

["BA", "AZ", "AY", ... "AA", "Z", "Y", "X", ..., "C", "B"]

First note that this will not work , because "BA" > "B" :

("BA".."B").to_a
  # => []

Instead, you could convert the reversed range to an array and reverse it again:

("B".."BA").to_a.reverse

Or if you plan to loop through this list, it's more performant to use reverse_each :

("B".."BA").reverse_each { |x| ... }

It's because of a discrepancy between String#succ and String#<=> :

'a'.succ       #=> 'b'
'a' < 'a'.succ #=> true

but:

'z'.succ       #=> 'aa'
'z' < 'z'.succ #=> false

Range utilizes both, succ and <=> when generating a sequence. It uses succ to generate each successive value and checks via <=> that the values are indeed successive (ending the sequence if not). 1

Even String#upto behaves this way. I've recently filed a bug report , because I'm under the impression that it should handle this properly.


1 This is the behavior for iterating custom objects . Range behaves more oddly for the built-in String class, maybe because of optimizations.

Range#to_a internally calls the method succ of its "elements". Here is a non-official implementation of Range#to_a

class Range
  def to_a
    ary = []
    item = self.begin
    compare = self.exclude_end? ? :< : :<=
    while item.public_send(compare, self.end)
      ary << item
      item = item.succ
    end
    ary
  end
end

You can try 'A'.succ , 'B'.succ , ... to see the sequence it generates.

Because 'B' is alphabetically greater than 'AB' , the loop ends before the first iteration, thus you get [] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM