String range membership

All,

I ran across some code that was trying to validate that an integer

was in a given range, however the integer and the range were Strings.
The problem boils down to this:

ruby -e “p (‘1’…‘10’).member?(‘2’)”
false

Given that...

ruby -v -e “(‘1’…‘10’).each {|s| p s}”
ruby 1.8.2 (2004-12-25) [i386-mswin32]
“1”
“2”
“3”
“4”
“5”
“6”
“7”
“8”
“9”
“10”

...it seems like ('1'..'10').member?('2') should return true.  The

problem lies in range.c, in the range_each_func() method. This method
starts with the first value, then calls succ() to get the next value,
breaking out of the loop when the value is no longer less than or equal
to the ending value (or strictly less than the ending value on an
exclusive range). Unfortunately, for the given string range this
happens immediately, since ‘2’ > ‘10’.

I suppose that it could be argued that this is not a bug, but that

would be a difficult argument to win. Also, I need to make sure that
this is still a bug in the latest version of Ruby. Unfortunately, I’m
too sleepy to investigate further or create a patch for this tonight,
but I’ll try to work on it some more tomorrow night (assuming nobody
else fixes it first).

- Warren B.

On 23/11/05, Warren B. [email protected] wrote:

“9”
I suppose that it could be argued that this is not a bug, but that
would be a difficult argument to win. Also, I need to make sure that
this is still a bug in the latest version of Ruby. Unfortunately, I’m
too sleepy to investigate further or create a patch for this tonight,
but I’ll try to work on it some more tomorrow night (assuming nobody
else fixes it first).

- Warren B.

I’d argue that it is not a bug, as there is no unique isomorphie from
strings to integers. Some well known functions would be hex, octal and
decimal encoding. I.e. ‘2’ … ‘10’ could be understood as 2 … 8, or
2 … 16 or 2 … 10 or error … 2 depending on the base.

Only you can now what the string means, so convert it to an integer
and do the range test on integers.

So I think you should save your time on creating the patch and
preferably fix the application code.

Brian


http://ruby.brian-schroeder.de/

Stringed instrument chords: http://chordlist.brian-schroeder.de/

Brian Schröder wrote:

I’d argue that it is not a bug, as there is no unique isomorphie from
strings to integers. Some well known functions would be hex, octal and
decimal encoding. I.e. ‘2’ … ‘10’ could be understood as 2 … 8, or
2 … 16 or 2 … 10 or error … 2 depending on the base.

Only you can now what the string means, so convert it to an integer
and do the range test on integers.

I was going to make that argument, but I realized that #each (by way of
#succ) does have some extra knowledge (or assumptions) about strings,
and it’s pretty smart:

irb(main):003:0> (‘1.1’…‘10.1’).each {|s| p s}
“1.1”
“1.2”
“1.3”
“1.4”


“9.6”
“9.7”
“9.8”
“9.9”
“10.0”
“10.1”
=> “1.1”…“10.1”
irb(main):004:0> (‘1.6.4’…‘1.8.3’).each {|s| p s}
“1.6.4”
“1.6.5”
“1.6.6”
“1.6.7”
“1.6.8”
“1.6.9”
“1.7.0”
“1.7.1”
“1.7.2”
“1.7.3”
“1.7.4”
“1.7.5”
“1.7.6”
“1.7.7”
“1.7.8”
“1.7.9”
“1.8.0”
“1.8.1”
“1.8.2”
“1.8.3”
=> “1.6.4”…“1.8.3”

I guess it is just impractical for Range#member? to test using #succ.

Just playing with it around but…

irb(main):057:0> (‘1’…‘10’).each { |i| puts i }
1
2
3
4
5
6
7
8
9
10
=> “1”…“10”
irb(main):058:0> (‘1’…‘10’).each { |i| case when i == ‘2’ ; puts “two”
else puts i end}
1
two
3
4
5
6
7
8
9
10
=> “1”…“10”
irb(main):059:0>

Hi,

In message “Re: [BUG] string range membership”
on Wed, 23 Nov 2005 15:41:32 +0900, “Warren B.”
[email protected] writes:
| I ran across some code that was trying to validate that an integer
|was in a given range, however the integer and the range were Strings.

include? and member? compares with beg <= val <= end, which is
dictionary order for strings. Unfortunately strings generated from
using succ is not in dictionary order. I’m not sure how to solve
this.

						matz.

I’m not sure how to solve this.

This was discussed sometime ago. The solution (mostly arrived at by
Peter V.) is to use a different comparision method. In
Facets you’ll find the #cmp method, which is part of the base methods,
and which is used by the Interval class --a true Interval as opposed to
what Range is.

def cmp(other)
return -1 if length < other.length
return 1 if length > other.length
self <=> other
end

Of course this won’t be of use to tuple forms like “1.18.12”, but in
such cases a Tuple object is in order anyway.

T.