It gets better.
("100".."11").to_a
=> ["100"]
Now, that one is odd. I'd have predicted a result of:
=> ["100", "101", "102", "103", "104", "105", "106", "107", "108", "109"]
on the basis of staring with "100" and applying #succ until the value was
"11" like this loop does:
It's doing a comparison of the strings -- it has to do with the length of the string. "100" is longer than "11", it also happens to be less characters (and, based on #succ, it's "less").
In order to find the range, it's going to compare the two strings --
+ it compares for the string lengths to get whether the beginning is less than the end
+ It then uses #succ to try to expand the range, but since "100" has more characters than "11", it stops...
Hope I've not muddied it too much.....
Matt
Well, the Range#to_a is actually Enumerable#to_a and uses Range#each defined in range.c
After checking that the beginning of the range responds to :succ and if it is a Fixnum (which are special), it finds that the Range.begin is a String:
else if (TYPE(beg) == T_STRING) {
VALUE args[5];
long iter[2];
args[0] = beg;
args[1] = end;
args[2] = range;
iter[0] = 1;
iter[1] = 1;
rb_iterate(str_step, (VALUE)args, step_i, (VALUE)iter);
}
str_step calls rb_str_upto defined in string.c
VALUE
rb_str_upto(VALUE beg, VALUE end, int excl)
{
VALUE current, after_end;
ID succ = rb_intern("succ");
int n;
StringValue(end);
n = rb_str_cmp(beg, end);
if (n > 0 || (excl && n == 0)) return beg;
after_end = rb_funcall(end, succ, 0, 0);
current = beg;
while (!rb_str_equal(current, after_end)) {
rb_yield(current);
if (!excl && rb_str_equal(current, end)) break;
current = rb_funcall(current, succ, 0, 0);
StringValue(current);
if (excl && rb_str_equal(current, end)) break;
StringValue(current);
if (RSTRING_LEN(current) > RSTRING_LEN(end) || RSTRING_LEN(current) == 0)
break;
}
return beg;
}
Now, not having read a lot of Ruby's C code, I'm not sure what some bits are for (like calling StringValue(current) so much), but it does ultimately behave almost like Matt said. The difference being that the rb_yield(current) has already happened once before the length check (RSTRING_LEN(current) > RSTRING_LEN(end)). I think the RSTRING_LEN(current)==0 is there to catch "".succ == "", but that just means that (""..any).to_a is [""] and yet ("".."").to_a is (because after_end will be "" and the loop is never entered).
So it's the odd situation that String is given some special treatment and has the unusual property that there are strings a,b such that:
a < b && a.length > b.length
Knowing this, here's an even more bizzare-looking example:
"19".succ
=> "20"
("2".."19").to_a
=>
("2"..."20").to_a
=> ["2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]
-Rob
Rob Biedenharn http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
···
On Aug 4, 2009, at 3:45 PM, Matthew K. Williams wrote:
On Wed, 5 Aug 2009, Rob Biedenharn wrote: