From: Adam Shelly Date: 2006-01-16T14:33:26+09:00 Subject: Re: Range#member? Oddity ------=_Part_3318_12230970.1137389593030 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Content-Disposition: inline I was messing around with this range behavior after the last discussion, an= d I came up some code that made ranges act, at least to me: I made range.member?(x0 =3D=3D range.to_a.member?(x) and range.spans?(x) = =3D=3D (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=3Dfirst.follows?(x) and f<0 && l=3Dlast.follows?(x) and l>0 end def spans?(x) (first<=3D>x) <=3D0 && (last<=3D>x) >=3D0 end end class Object def follows? other self<=3D>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=3D=3Dself.upcase return :downcase if self=3D=3Dself.downcase return :mixcase end def follows? str return 0 if str =3D=3D 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 =3D str.split(/\W/).map{|c|c.split(/(\d+)/)}.flatten lastparts =3D self.split(/\W/).map{|c|c.split(/(\d+)/)}.flatten matching =3D nil return false if lastparts.size !=3D firstparts.size if firstparts =3D=3D [] && lastparts =3D=3D [] #both strings are all pu= nctuation firstparts =3D [str]; lastparts =3D [self] end #return false on first non-matching or non-following group firstparts.each_index do |i| next if firstparts[i] =3D=3D lastparts[i] firstparts[i].split_by_case.zip(lastparts[i].split_by_case).each { |f,l| return false if f=3D=3Dnil || l=3D=3Dnil 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 !=3D f.case || l.cmp(f) =3D=3D -1 end matching =3D 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 =3D split("").zip(downcase.split("")) same =3D (letters[0][0]!=3Dletters[0][1]) letters.inject([]){|a,l| if (same =3D=3D (l[0]=3D=3Dl[1])) a[-1]+=3Dl[0] else a<str.length return self<=3D>str if n=3D=3D0 n end def format_match str is_numeric? =3D=3D str.is_numeric? && length =3D=3D 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 ------=_Part_3318_12230970.1137389593030--