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.