Range#member? Oddity

On Sun, 15 Jan 2006, David V. wrote:

You could theorethically check whether the range endpoints are comparable,
and whether the starting one defines #succ, and then mix in Enumerable to
the Ranges that indeed are to get more strictly correct behaviour.

but that prevents me from doing this

 harp:~ > cat a.rb
 require "yaml"

 e = Float::EPSILON
 r = 1.0 .. 1.0000000000000091038288019262836314737796783447265625

 class Float
   def succ() self + EPSILON end
 end

 a = r.to_a

 y "r.min" => ("%52.52f" % r.min)
 y "r.max" => ("%52.52f" % r.max)
 y "r.to_a.size" => r.to_a.size



 harp:~ > ruby a.rb
 ---
 r.min: "1.0000000000000000000000000000000000000000000000000000"
 ---
 r.max: "1.0000000000000091038288019262836314737796783447265625"
 ---
 r.to_a.size: 42

i can easily imagine uses for code just like that. i think it’s best if
ruby
will let us run quickly with knives.

regards.

-a

On Sat, 14 Jan 2006 23:29:18 +0100, [email protected] wrote:

i think it’s best if ruby
will let us run quickly with knives.

regards.

-a

Oh, I agree with that myself. I’m just objecting to the people that
complain when they trip :stuck_out_tongue_winking_eye:

David V.

I was messing around with this range behavior after the last discussion,
and
I came up some code that made ranges act, at least to me:
I made range.member?(x0 == range.to_a.member?(x) and range.spans?(x)

(range.first < x < range.last) .
I had to implement string.follows? s which works like the spaceship
operator but uses the string succession rules:

class Range
def member?(x)
f=first.follows?(x) and f<0 && l=last.follows?(x) and l>0
end
def spans?(x)
(first<=>x) <=0 && (last<=>x) >=0
end
end

class Object
def follows? other
self<=>other if self.respond_to? :succ
end
end

class String
def is_numeric?
/^-?\d+$/.match(self) ? true : false
end

def case
return :upcase if self==self.upcase
return :downcase if self==self.downcase
return :mixcase
end

def follows? str
return 0 if str == self
return 1 if self.is_after? str
return -1 if str.is_after? self
return nil
end

def is_after? str #not pretty, but it seems to work…
#split into groups of letters and digits
firstparts = str.split(/\W/).map{|c|c.split(/(\d+)/)}.flatten
lastparts = self.split(/\W/).map{|c|c.split(/(\d+)/)}.flatten
matching = nil
return false if lastparts.size != firstparts.size
if firstparts == [] && lastparts == [] #both strings are all
punctuation
firstparts = [str]; lastparts = [self]
end
#return false on first non-matching or non-following group
firstparts.each_index do |i|
next if firstparts[i] == lastparts[i]
firstparts[i].split_by_case.zip(lastparts[i].split_by_case).each {
|f,l|
return false if f==nil || l==nil
next if (matching && l.format_match(f))
if (f.is_numeric?)
return false unless l.is_numeric? and l.to_i > f.to_i
else
return false if l.case != f.case || l.cmp(f) == -1
end
matching = true #this group is in the right order - the rest
just
have to match by format
}
end #each
true
end

def split_by_case
letters = split("").zip(downcase.split(""))
same = (letters[0][0]!=letters[0][1])
letters.inject([]){|a,l|
if (same == (l[0]==l[1]))
a[-1]+=l[0]
else
a<<l[0]
end
same = (l[0]==l[1])
a
}
end
def cmp str
n = self.length<=>str.length
return self<=>str if n==0
n
end
def format_match str
is_numeric? == str.is_numeric? && length == str.length
end
end

require ‘test/unit’
class TestRange < Test::Unit::TestCase
def test_memberspan
assert_equal(true, (‘a’…‘bb’).member?(‘z’) )
assert_equal(false, (‘a’…‘bb’).spans?(‘z’))
assert_equal(false,(‘a’…‘bb’).member?(‘aardvark’))
assert_equal(true,(‘a’…‘bb’).spans?(‘aardvark’))
assert_equal(true,(‘a’…‘bb’).spans?(‘a’))
assert_equal(true,(‘a’…‘bb’).spans?(‘bb’))
assert_equal(false,(1…100).member?(101))
assert_equal(false,(1…100).spans?(101))
assert_equal(true,(1…100).member?(11))
assert_equal(true,(1…100).spans?(11))
assert_equal(true,(1.5…100).spans?(11))
assert_equal(nil,(1.5…100).member?(11))
end
end

-Adam