Range on strings

Hi,
If I do -

(‘A’…‘Z’).include?(‘AA’)

It returns “true”, while

(‘A’…‘Z’).to_a.include?(‘AA’)

(of course) returns “false”. Is it intentional or possibly a bug?
I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

On Fri, May 14, 2010 at 11:40 AM, Vikrant C. [email protected]
wrote:

I do not know if it is a bug, but the behavior has changed in Ruby1.9,
thus I guess it was at least considered, “surprising” in 1.8.
Cheers
R.

On Fri, May 14, 2010 at 4:40 AM, Vikrant C. [email protected]
wrote:

I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

I think the problem is that on 1.8 it checks include by seeing if the
value
is greater than or equal to the beginning value, and less then or equal
to
the end value, since string comparison would have “A” < “AA” < “Z”, it
returns true.

(http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8/range.c)

/*

  • call-seq:
  • rng === obj       =>  true or false
    
  • rng.member?(val)  =>  true or false
    
  • rng.include?(val) =>  true or false
    
  • Returns true if obj is an element of
  • rng, false otherwise. Conveniently,
  • === is the comparison operator used by
  • case statements.
  • case 79
    
  • when 1..50   then   print "low\n"
    
  • when 51..75  then   print "medium\n"
    
  • when 76..100 then   print "high\n"
    
  • end
    
  • produces:
  • high
    

*/

static VALUE
range_include(range, val)
VALUE range, val;
{
VALUE beg, end;

beg = rb_ivar_get(range, id_beg);
end = rb_ivar_get(range, id_end);
if (r_le(beg, val)) {

if (EXCL(range)) {
if (r_lt(val, end)) return Qtrue;
}
else {
if (r_le(val, end)) return Qtrue;
}
}
return Qfalse;
}

However, when it converts to an array, that is a method from Enumerable.
I’m
not initiated enough to figure out how it’s C code works, but my
understanding is that it uses #succ (I got that from this thread
http://www.ruby-forum.com/topic/192758#new, but please correct me if I
misunderstood) to iterate from the beginning element to the end element,
to
create the array. Here is it’s code, if you understand it, feel free to
explain.
(http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8/enum.c)

/*

  • call-seq:
  • enum.to_a      =>    array
    
  • enum.entries   =>    array
    
  • Returns an array containing the items in enum.
  • (1..7).to_a                       #=> [1, 2, 3, 4, 5, 6, 7]
    
  • { 'a'=>1, 'b'=>2, 'c'=>3 }.to_a   #=> [["a", 1], ["b", 2], ["c", 
    

3]]
*/
static VALUE
enum_to_a(argc, argv, obj)
int argc;
VALUE *argv;
VALUE obj;
{
VALUE ary = rb_ary_new();

rb_block_call(obj, id_each, argc, argv, collect_all, ary);

return ary;

}

Then Array#include iterates through each of it’s elements to check if it
contains the desired element
(http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8/array.c)

/*

  • call-seq:
  • array.include?(obj)   -> true or false
    
  • Returns true if the given object is present in
  • self (that is, if any object ==
    anObject),
  • false otherwise.
  • a = [ "a", "b", "c" ]
    
  • a.include?("b")   #=> true
    
  • a.include?("z")   #=> false
    

*/

VALUE
rb_ary_includes(ary, item)
VALUE ary;
VALUE item;
{
long i;

for (i=0; i<RARRAY(ary)->len; i++) {

if (rb_equal(RARRAY(ary)->ptr[i], item)) {
return Qtrue;
}
}
return Qfalse;
}

The elements the array contains are
$ ruby -e “p (‘A’…‘Z’).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”]
So “AA” is not found, and thus it returns false.

I honestly don’t know about 1.9, I tried looking at it’s code, but I
don’t
understand it.
(http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_9_1/range.c)

On Sat, May 15, 2010 at 8:25 AM, MrZombie [email protected]
wrote:

the range.

For example, given r = (‘A’…‘Z’): r.include?(‘RUBY’) # returns true
and: r.include?(‘Ruby’) # also true
but: r.include?(‘ruby’) # is false.

Thank you for your brain.
-MrZombie

I am not able to replicate that behaviour.

$ rvm use 1.9.1
Using ruby 1.9.1 p378

$ irb
ruby-1.9.1-p378 > r = ‘A’…‘Z’
=> “A”…“Z”
ruby-1.9.1-p378 > r.include? ‘Ruby’
=> false
ruby-1.9.1-p378 > r.include? ‘RUBY’
=> false
ruby-1.9.1-p378 > r.include? ‘ruby’
=> false
ruby-1.9.1-p378 > exit

$ ruby -v
ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.3.0]

On 2010-05-14 05:37:37 -0400, Vikrant C. said:

Is it intentional or possibly a bug?
I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

Mac OS X Snow Leopard running Ruby 1.9.1, same behavior.

I notice that it returns false if no element of the string is included
in the range.

For example, given r = (‘A’…‘Z’): r.include?(‘RUBY’) # returns true
and: r.include?(‘Ruby’) # also true
but: r.include?(‘ruby’) # is false.

Hi –

On Fri, 14 May 2010, Vikrant C. wrote:

I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

As per the other answers, it’s because (‘A’…‘Z’).to_a is an array of
discrete values, whereas (‘A’…‘Z’) is a range with a start and end.
I’ll just add that in Ruby 1.9, things have changed so that include?
on a range is, I think, the same as .to_a.include? and the behavior of
the old include? is found in Range#cover?

$ ruby191 -e ‘p((“A”…“Z”).include?(“AA”))’
false
$ ruby191 -e ‘p((“A”…“Z”).cover?(“AA”))’
true

include? is also available under the name member?, which I think is a
little clearer (both because include? used to mean something else, and
because being a “member” of a range doesn’t really make sense so it
almost has to mean something other than just being within the range).

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

THE Ruby training with Black/Brown/McAnally
COMPLEAT Coming to Chicago area, June 18-19, 2010!
RUBYIST http://www.compleatrubyist.com

On Mon, May 17, 2010 at 8:29 AM, David A. Black [email protected]
wrote:

(‘A’…‘Z’).to_a.include?(‘AA’)

(of course) returns “false”. Is it intentional or possibly a bug?
I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

As per the other answers, it’s because (‘A’…‘Z’).to_a is an array of
discrete values, whereas (‘A’…‘Z’) is a range with a start and end.
I’ll just add that in Ruby 1.9, things have changed so that include?
on a range is, I think, the same as .to_a.include?

Not exactly. Range#include? for Ruby 1.9 has a few special cases.

  1. If the beginning or end of the range is numeric, it just uses the
    right comparisons between the value being tested and the beginning and
    end.

Right comparisons here means that the value is >= the beginning and
either < or <= the end depending on whether the range excludes the
end.

  1. If the beginning and end of the range are both strings of length 1,
    then
    a. if the value is nil, or an empty string, or a string with
    length > 1 return false
    b. if the beginning, end and value are all ASCII characters
    then return the result of the right comparisons between the value, the
    beginning and the end.

At the end if it hasn’t short circuited it invokes super which uses
Enumeration#include? (which is an alias for member?) which does the
equivalent of

 self detect (|element| element == value}

Which is like to_a.include?(value) but short circuits the enumeration
when it finds a hit.

and the behavior of
the old include? is found in Range#cover?

$ ruby191 -e ‘p((“A”…“Z”).include?(“AA”))’
false
$ ruby191 -e ‘p((“A”…“Z”).cover?(“AA”))’
true

include? is also available under the name member?, which I think is a
little clearer (both because include? used to mean something else, and
because being a “member” of a range doesn’t really make sense so it
almost has to mean something other than just being within the range).

That’s a bit subjective I think, it depends on whether or not you
think of a Range as a collection or not. Personally I can think of it
either way depending on the situation.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Github: http://github.com/rubyredrick
Twitter: @RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

On May 14, 2:37 pm, Vikrant C. [email protected] wrote:

I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

Probably because the way Strings are compared, “AA” < “Z” #=> true

On 2010-05-17 07:53:34 -0400, Vikrant C. said:

(of course) returns “false”. Is it intentional or possibly a bug?
I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

Probably because the way Strings are compared, “AA” < “Z” #=> true

Just for the kicks, I tried with (‘a’…‘z’).include? ‘aa’

Guess what? False.

2010/5/17 David A. Black [email protected]:

the old include? is found in Range#cover?

$ ruby191 -e ‘p((“A”…“Z”).include?(“AA”))’
false
$ ruby191 -e ‘p((“A”…“Z”).cover?(“AA”))’
true

include? is also available under the name member?, which I think is a
little clearer (both because include? used to mean something else, and
because being a “member” of a range doesn’t really make sense so it
almost has to mean something other than just being within the range).

Throwing just my cent in here: I believe the solution in 1.9.* is
better because here behavior of #include? is consistent with other
collection types: e.include?(x) == true <=> e.any? {|y| x == y} in
other words, #include? is true only if the element is also seen during
iteration.

Kind regards

robert

On Mon, 17 May 2010, Rick DeNatale wrote:

It returns “true”, while
on a range is, I think, the same as .to_a.include?

Not exactly. Range#include? for Ruby 1.9 has a few special cases.

[snip]

Thanks. I was being lazy, so I’m glad for the further info :slight_smile:

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

THE Ruby training with Black/Brown/McAnally
COMPLEAT Coming to Chicago area, June 18-19, 2010!
RUBYIST http://www.compleatrubyist.com

Vikrant C. wrote:

I’m using ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux] on
Ubuntu 10.04 x64

Using ruby 1.9.1 and Windows Vista both examples return false.

Hi –

On Mon, 17 May 2010, Robert K. wrote:

It returns “true”, while
on a range is, I think, the same as .to_a.include? and the behavior of
almost has to mean something other than just being within the range).

Throwing just my cent in here: I believe the solution in 1.9.* is
better because here behavior of #include? is consistent with other
collection types: e.include?(x) == true <=> e.any? {|y| x == y} in
other words, #include? is true only if the element is also seen during
iteration.

Although as Rick points out:

$ ruby191 -e “p((1…10).include?(3.4))”
true

which I hadn’t taken into account in my too-simple summary of 1.9’s
Range#include?. I’m not sure what the underlying principle is that
causes that last case to be true while the “A”…“Z” including “AA” is
false, though.

David


David A. Black, Senior Developer, Cyrus Innovation Inc.

THE Ruby training with Black/Brown/McAnally
COMPLEAT Coming to Chicago area, June 18-19, 2010!
RUBYIST http://www.compleatrubyist.com