Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Range#member? Oddity

0 views
Skip to first unread message

James Edward Gray II

unread,
Jan 13, 2006, 3:49:56 PM1/13/06
to
I'm not understanding what I am seeing here. Can anyone please
explain why the last line of this session gives *false* as an answer?

>> range = ("1".."10")
=> "1".."10"
>> range.to_a
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
>> range.member?("1")
=> true
>> range.member?("2")
=> false

James Edward Gray II

Daniel Berger

unread,
Jan 13, 2006, 3:54:00 PM1/13/06
to

I cannot duplicate this with 1.8.2 or 1.8.4.

- Dan


James Edward Gray II

unread,
Jan 13, 2006, 3:58:53 PM1/13/06
to

Odd. I'm using 1.8.4:

$ ruby -v
ruby 1.8.4 (2005-12-24) [powerpc-darwin8.3.0]

James Edward Gray II


Justin Collins

unread,
Jan 13, 2006, 3:59:14 PM1/13/06
to
It happens in 1.8.2 for me. It shows "1" and "10" as being in the range,
but nothing else.

[justinc@justinc-dsktp ~]$ ruby -v
ruby 1.8.2 (2004-12-25) [i586-linux-gnu]
[justinc@justinc-dsktp ~]$ irb
irb(main):001:0> range = ("1".."10")
=> "1".."10"
irb(main):002:0> range.to_a


=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]

irb(main):003:0> range.member?("1")
=> true
irb(main):004:0> range.member?("2")
=> false
irb(main):005:0> range.member?("10")
=> true
irb(main):006:0>


Note that range _isn't_ getting converted into an array.

-Justin

Daniel Harple

unread,
Jan 13, 2006, 4:01:31 PM1/13/06
to
Daniel Berger wrote:
> James Edward Gray II wrote:
>> I'm not understanding what I am seeing here. Can anyone please
>> explain why the last line of this session gives *false* as an answer?
>> <snip>

>
> I cannot duplicate this with 1.8.2 or 1.8.4.
>
> - Dan

I'm getting this too. 1 and 10 both return true, everything else
returns false.

J. Ryan Sobol

unread,
Jan 13, 2006, 4:03:15 PM1/13/06
to

I can.

$ ruby -v
ruby 1.8.4 (2005-12-24) [powerpc-darwin8.3.0]

~ ryan ~


tokmak tokmak

unread,
Jan 13, 2006, 4:05:25 PM1/13/06
to
it gets odder:

irb(main):001:0> range = ("1".."10")
=> "1".."10"
irb(main):002:0> range.to_a


=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]

irb(main):003:0> range.member?("1")
=> true
irb(main):004:0> range.member?("2")
=> false
irb(main):005:0> range.member?("4")
=> false
irb(main):006:0> range.member?("9")
=> false
irb(main):007:0> range.member?("10")
=> true
irb(main):008:0> range.include?("2")
=> false
irb(main):009:0> range.include?("1")
=> true
irb(main):010:0> range === "1"
=> true
irb(main):011:0> range === "2"
=> false
irb(main):012:0> RUBY_VERSION
=> "1.8.2"


2006/1/13, James Edward Gray II <ja...@grayproductions.net>:

Daniel Berger

unread,
Jan 13, 2006, 4:07:18 PM1/13/06
to

Whoops, I misspoke. I was using numbers, not strings. I do, in fact, get the
same behavior in 1.8.4 (and 1.8.2).

/me guesses randomly that it has something to do with stringified numbers in
particular.

Regards,

Dan


Dan Hinz

unread,
Jan 13, 2006, 4:11:19 PM1/13/06
to
I don't know for sure but my guess has something to do with numbers vs
characters and whether "1".."10" expands to the same thing that 1..10
does.

>>range=(1..10)
1..10
>>range.member?(1)
true
>>range.member?(2)
true
>>range.member?(3)
true
>>puts range
1..10

Huh???

-dwh-

--
I not only live each endless day in grief, but live each day
thinking about living each day in grief.
-- C.S. Lewis

Daniel W. Hinz Xerox Corp: XOG/SEBU/MCD/EIDC/ISM&D
MS: 111-03J e-mail: dh...@eng.mc.xerox.com
800 Phillips Road TEL: 585.422.8078
Webster, NY 14580

Matthew Desmarais

unread,
Jan 13, 2006, 4:16:14 PM1/13/06
to
James Edward Gray II wrote:
Hi,

There was some discussion about this in the recent past. If my memory
serves me right (certainly an infrequent happening), the issue that
you're running into is that Range#member? is implemented as:
class Range
def member?(val)
if self.exclude_end?
(self.first <= val) and (val < self.last)
else
(self.first <= val) and (val <= self.last)
end
end
end

You should find this in both 1.8.2 and 1.8.4 I think.

There's a previous thread on ruby-talk about it, here's a link to
somewhere near the conclusion of the discussion:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/167194

Matthew


James Edward Gray II

unread,
Jan 13, 2006, 4:29:22 PM1/13/06
to
On Jan 13, 2006, at 3:16 PM, Matthew Desmarais wrote:

> There's a previous thread on ruby-talk about it, here's a link to
> somewhere near the conclusion of the discussion:
> http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/167194

This cleared it up for me. Thank you.

I was aware of the old system, where member?() and include?() had
separate meanings and just didn't know it had been abandoned.

James Edward Gray II


David Vallner

unread,
Jan 13, 2006, 4:58:04 PM1/13/06
to
On Fri, 13 Jan 2006 22:16:14 +0100, Matthew Desmarais <des...@gmail.com>
wrote:


This would seem to make it an oddity of String#succ, that behaving
automagically, not generating successors with respect to String comparison.

E.g. for any Integers i, i.succ > i. For some strings, that does't hold
true.

Bottom line: Don't use strings when you're really using numbers. Like in
mathemathical contexts. D'oh. You could possibly hack around that in Range
code to provide for data types where generating successors is inconsistent
with comparison, but I wouldn't like to see that.

David Vallner


James Edward Gray II

unread,
Jan 13, 2006, 5:00:37 PM1/13/06
to
On Jan 13, 2006, at 3:58 PM, David Vallner wrote:

> You could possibly hack around that in Range code to provide for
> data types where generating successors is inconsistent with
> comparison, but I wouldn't like to see that.

It's not too tough in this case:

>> ("1".."10").to_a.include?("2")
=> true

James Edward Gray II


David Vallner

unread,
Jan 13, 2006, 5:13:52 PM1/13/06
to


Yes, that always works, but it beats the point of having first class
ranges as opposed to just having a pythonesque range function in the first
place. I'd personally rather coerce the strings to numbers if I know they
represent such to get more type safety and possibly some execution speed
too.

David Vallner


J. Ryan Sobol

unread,
Jan 13, 2006, 5:46:26 PM1/13/06
to
The rdoc needs to be updated for Range#include? and Range#member? then.

~ ryan ~

David Vallner

unread,
Jan 13, 2006, 6:04:16 PM1/13/06
to
No, those methods work perfectly. The behaviour of String is the problem
here.

On Fri, 13 Jan 2006 23:46:26 +0100, J. Ryan Sobol <ryan...@gmail.com>
wrote:

James Edward Gray II

unread,
Jan 13, 2006, 6:17:26 PM1/13/06
to
On Jan 13, 2006, at 5:04 PM, David Vallner wrote:

> No, those methods work perfectly. The behaviour of String is the
> problem here.

??? How exactly is it that you believe String should behave?

James Edward Gray II


David Vallner

unread,
Jan 13, 2006, 6:54:04 PM1/13/06
to


Well, to work well in Ranges, for any String s, s.succ > s must hold true

James Edward Gray II

unread,
Jan 13, 2006, 7:01:21 PM1/13/06
to

> hold true.

I'm pretty sure we don't want to change the meaning of String
comparisons at this point. succ() just doesn't happened to be
defined under those terms, because then it would be a lot less useful
to us. It's hard for me to see any of that as "broken".

James Edward Gray II


gwt...@mac.com

unread,
Jan 13, 2006, 7:18:28 PM1/13/06
to

On Jan 13, 2006, at 6:54 PM, David Vallner wrote:
> Well, to work well in Ranges, for any String s, s.succ > s must
> hold true.

How about having Range use Object#strict_succ to generate
its sequence? Define String#strict_succ as needed to guarantee
s.succ > s and then alias strict_succ to succ for other classes
(such as Fixnum) so they don't break when used in a Range.


Gary Wright

Logan Capaldo

unread,
Jan 13, 2006, 7:26:22 PM1/13/06
to

This seems to me to make the problem worse. People expect the values
generated by String#succ to be in the array when doing a to_a for
instance. I believe the real solution would be to bring back the
distinction between member and include (possibly with a new name for
the method with the functionality of #member).


David Vallner

unread,
Jan 13, 2006, 7:34:10 PM1/13/06
to


I never said anything the like, the code breakage would be inexcusable.

My point is it's String that behaves erreneously in this context and isn't
suitable for use in symbolic ranges, not a bug in Range code - the posts
were an objection to people wanting to mess up the Range interface.

David Vallner


David Vallner

unread,
Jan 13, 2006, 7:36:49 PM1/13/06
to
On Sat, 14 Jan 2006 01:26:22 +0100, Logan Capaldo <loganc...@gmail.com>
wrote:


I agree that the change proposed would break Range#to_a for strings, which
I can imagine being used. The real solution would be people coding sanely
and using Integers to represent integers, and Strings to represent text -
using unsuitable data types resulting in a bug is arguably not a language
fault.

David Vallner


gwt...@mac.com

unread,
Jan 13, 2006, 7:53:37 PM1/13/06
to

On Jan 13, 2006, at 7:26 PM, Logan Capaldo wrote:
> This seems to me to make the problem worse. People expect the
> values generated by String#succ to be in the array when doing a
> to_a for instance. I believe the real solution would be to bring
> back the distinction between member and include (possibly with a
> new name for the method with the functionality of #member).

It seems strange to want Range to behave like an interval and to
also want Range#to_a to create a list of elements that don't all
belong in that same interval.

I understand the desire to generate a sequence of strings
as defined by the current behavior of String#succ but I don't understand
why you would want to use a Range object as a shortcut to that sequence.
That particular sequence really has nothing to do with an interval
or the ordering defined by String#<=>


Gary Wright

David Vallner

unread,
Jan 13, 2006, 8:04:42 PM1/13/06
to
On Sat, 14 Jan 2006 01:53:37 +0100, <gwt...@mac.com> wrote:

>
> It seems strange to want Range to behave like an interval and to
> also want Range#to_a to create a list of elements that don't all
> belong in that same interval.

It's a Convenient Hack (tm), and those are extremely hard to weed out once
they catch hold. It's the same as when people use #eval that must a
nightmare to the brave folks working on optimizing YARV (I want my clean
blocks!) instead of the much cleaner metaprogramming facilities in Ruby
that let you achieve 99% of what I've seen hacked with evals anyway. (Very
obfuscated eval hacks notwithstanding).

David Vallner


Logan Capaldo

unread,
Jan 13, 2006, 8:36:08 PM1/13/06
to

On Jan 13, 2006, at 7:53 PM, gwt...@mac.com wrote:

> I understand the desire to generate a sequence of strings
> as defined by the current behavior of String#succ but I don't
> understand
> why you would want to use a Range object as a shortcut to that
> sequence.
> That particular sequence really has nothing to do with an interval
> or the ordering defined by String#<=>

As far as I can tell two classes(Well 4ish) have #succ methods.
Integers and Strings. For all integers x, x has the property that
x.succ > x is true.
There exists at least one String x for which x.succ > x is true, and
at least one String x for which x.succ > x is false. So the question
is, what is a Range? Is it a pair of end-points for checking between-
ness? Is it the set of all values between those end points? If it is
the second, I would argue that #to_a is meaningless for strings
because there are end points for which the set would have infinite
cardinality (i.e. ("a".."b"), the set of strings matched by /^a+\z/
at least is in that "range".).

Bertram Scharpf

unread,
Jan 13, 2006, 8:52:20 PM1/13/06
to
Hi,

Am Samstag, 14. Jan 2006, 05:49:56 +0900 schrieb James Edward Gray II:
> I'm not understanding what I am seeing here. Can anyone please
> explain why the last line of this session gives *false* as an answer?
>
> >> range = ("1".."10")
> => "1".."10"
> >> range.to_a
> => ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
> >> range.member?("1")
> => true
> >> range.member?("2")
> => false

irb(main):006:0> 2 < 10
=> true
irb(main):007:0> "2" < "10"
=> false

Bertram


--
Bertram Scharpf
Stuttgart, Deutschland/Germany
http://www.bertram-scharpf.de


Hal Fulton

unread,
Jan 13, 2006, 10:08:43 PM1/13/06
to
James Edward Gray II wrote:
> I'm not understanding what I am seeing here. Can anyone please explain
> why the last line of this session gives *false* as an answer?
>
> >> range = ("1".."10")
> => "1".."10"
> >> range.to_a
> => ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
> >> range.member?("1")
> => true
> >> range.member?("2")
> => false

In short: Don't do this.

String ranges are very weird. They don't obey the rule that
x.succ > x.

Since "2" > "10", "2" is considered outside the range entirely.

If you really want to do this kind of thing, say
("1".."10").to_a.member?("2")


Hal

Christer Nilsson

unread,
Jan 14, 2006, 12:29:44 AM1/14/06
to

If the two strings have the same length, the weirdness disappears.

Christer

--
Posted via http://www.ruby-forum.com/.


Pit Capitain

unread,
Jan 14, 2006, 3:46:16 AM1/14/06
to
Christer Nilsson schrieb:

> Hal Fulton wrote:
>>String ranges are very weird. They don't obey the rule that
>>x.succ > x.
>>
>>Since "2" > "10", "2" is considered outside the range entirely.
>>
>>If you really want to do this kind of thing, say
>>("1".."10").to_a.member?("2")
>
>
> If the two strings have the same length, the weirdness disappears.

Very good point!

Regards,
Pit


dbl...@wobblini.net

unread,
Jan 14, 2006, 6:15:16 AM1/14/06
to
Hi --

On Sat, 14 Jan 2006, Christer Nilsson wrote:

> Hal Fulton wrote:
>> James Edward Gray II wrote:
>>> => false
>> In short: Don't do this.
>>
>> String ranges are very weird. They don't obey the rule that
>> x.succ > x.
>>
>> Since "2" > "10", "2" is considered outside the range entirely.
>>
>> If you really want to do this kind of thing, say
>> ("1".."10").to_a.member?("2")
>>
>>
>> Hal
>
> If the two strings have the same length, the weirdness disappears.

But other weirdnesses arise :-)

irb(main):019:0> ("A".."z").member?("g")
=> true
irb(main):020:0> ("A".."z").to_a.include?("g")
=> false


David

--
David A. Black
dbl...@wobblini.net

"Ruby for Rails", from Manning Publications, coming April 2006!
http://www.manning.com/books/black


Stefan Walk

unread,
Jan 14, 2006, 9:47:26 AM1/14/06
to
dbl...@wobblini.net wrote:
> But other weirdnesses arise :-)
>
> irb(main):019:0> ("A".."z").member?("g")
> => true
> irb(main):020:0> ("A".."z").to_a.include?("g")
> => false
>
>
> David
>

IMO, the thing at fault here is that Ranges include Enumerable. From a
mathematical POV, that's nonsense, and that's why oddities like this
exist. Things that can be used for ranges can not used like an
Enumarable (rational..rational, float..float), because those don't have
#succ instance methods... and String ranges behave oddly because the
member? checks use the <=> behaviour, and each uses succ...

Regards,
Stefan


Stefan Walk

unread,
Jan 14, 2006, 11:03:59 AM1/14/06
to
ara.t....@noaa.gov wrote:
> On Sat, 14 Jan 2006, Stefan Walk wrote:
> but enumerable means neither monotonically increasing nor finite. so
> that a
> range includes enumerable means only that one may start, but perhaps never
> finish, to count the elements in the range. i think this is accurate isn't
> it?
>
> cheers.
>
> -a

Enumerable means that you can map the natural numbers to your set in an
invertable way, and that's not possible for ranges of real numbers.
Being enumerable means the possibility of a "succ" operation, and
therefore of traversing the whole set with "each" (which may take
infinite time). Being enumerable implies a working each, now try
(1.0..2.0).each...

Regards,
Stefan


David Vallner

unread,
Jan 14, 2006, 12:41:43 PM1/14/06
to
On Sat, 14 Jan 2006 17:03:59 +0100, Stefan Walk <stefa...@gmail.com>
wrote:


There's nothing wrong with enumerating ranges, you can well have intervals
of natural numbers or other countably infinite sets. Bounded intervals of
countable or countably infinite sets are finite sets and may be (not
necessarily though) enumerated in order using the successor operation,
which exists if the elements of the interval are well-ordered. It's not
exactly an edge case, and distinguishing between ranges with finite and
infinite element counts

The issue with strings is that their ordering does _not_ make them such a
well-ordered countably infinite set. Because strings are compared using
textual comparison, as was already noted in this thread, for every two
given strings, there exists an infinite amount of strings between them.
For the sake of completion, strings in fact _are_ a countably infinite
set, but their ordering doesn't respect any mapping of strings to natural
numbers.

Given this, the string successor operation doesn't even make sense from
the mathemathical point of view. It is in this context defined ad hoc, and
it's usefulness lies in contexts of text processing.

Using a string Range is a convenient shortcut, but it's a _hack_. Nothing
more, nothing less. Strings don't have a solid foundation for use in
maths. If you expect strings to behave as numbers, you're wrong, they're
not supposed to, any bugs are your fault, not the fault of the Ruby core
API. Moan and suffer.

David Vallner


Stefan Walk

unread,
Jan 14, 2006, 1:54:55 PM1/14/06
to
David Vallner wrote:
> There's nothing wrong with enumerating ranges, you can well have
> intervals of natural numbers or other countably infinite sets. Bounded
> intervals of countable or countably infinite sets are finite sets and
> may be (not necessarily though) enumerated in order using the successor
> operation, which exists if the elements of the interval are
> well-ordered. It's not exactly an edge case, and distinguishing between
> ranges with finite and infinite element counts

Your messages seems to have been truncated here. Also, you're wrong,
bounded intervals of countably infinite sets do not have to be finite.
Consider [0,1] in the rational numbers. Rational numbers are countable,
the range is bounded, but the set is not finite (it is still countably
infinite).

> Using a string Range is a convenient shortcut, but it's a _hack_.
> Nothing more, nothing less. Strings don't have a solid foundation for
> use in maths. If you expect strings to behave as numbers, you're wrong,
> they're not supposed to, any bugs are your fault, not the fault of the
> Ruby core API. Moan and suffer.
>
> David Vallner

Range including Enumerable is a fault of the ruby core API, because
(1.0..20) pretends to be enumerable (is_a? returns true, responds_to?
:each returns true), but is not.

Regards, Stefan (who never had problems with string ranges, only with
Range including Enumerable)


Robert Retzbach

unread,
Jan 14, 2006, 1:55:56 PM1/14/06
to
James Edward Gray II schrieb:

> I'm not understanding what I am seeing here. Can anyone please
> explain why the last line of this session gives *false* as an answer?
>
> >> range = ("1".."10")
> => "1".."10"
> >> range.to_a
> => ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
> >> range.member?("1")
> => true
> >> range.member?("2")
> => false
>

> James Edward Gray II
>
>
>
I don't know why to_a is working here.
I know that a range from '1' to '10' could be like you described, but it
doesn't make a logical sense I think.
like ('a'..'ah') what should that be?
[a-255.chr] and all a[0.chr-255.chr]?

This is what I think, of course.
Dunno if it makes sense :>

MfG
Retze


David Vallner

unread,
Jan 14, 2006, 2:17:17 PM1/14/06
to
On Sat, 14 Jan 2006 19:54:55 +0100, Stefan Walk <stefa...@gmail.com>
wrote:

>
> [snip]


>
> Range including Enumerable is a fault of the ruby core API, because
> (1.0..20) pretends to be enumerable (is_a? returns true, responds_to?
> :each returns true), but is not.

That is, of course, misleading, but ranges are very commonly used in the
special cases when they are indeed enumerable, so I can understand why
it's defined that way in the core API. Library design isn't there to
replace programmer brains. 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.

David Vallner


ara.t....@noaa.gov

unread,
Jan 14, 2006, 4:41:44 PM1/14/06
to
On Sun, 15 Jan 2006, Stefan Walk wrote:

> Enumerable means that you can map the natural numbers to your set in an
> invertable way, and that's not possible for ranges of real numbers. Being
> enumerable means the possibility of a "succ" operation, and therefore of
> traversing the whole set with "each" (which may take infinite time). Being
> enumerable implies a working each, now try (1.0..2.0).each...

that is certainly true. however, there are no real numbers in computing -
only rational numbers and rationals are countably infinite

http://mathworld.wolfram.com/CountablyInfinite.html
http://mathworld.wolfram.com/RationalNumber.html

while (1.0..2.0).each{|x| p x} cannot work with real numbers it can certainly
work with a set of rational numbers - in computing terms you can certainly
count from 1.0 to 2.0 by adding the floating point epsilon. my opinion is
that we're talking computer languages here, not mathematicaly ones, and so any
idea of being 'correct' must be in the context of what computers can do.

in any case, ruby makes no guarantee that

(a..b).each{|x| p x}

will complete, so even if we had real numbers that would still be a valid
construct - though mixing in Enumerable into Range would be strange then - but
it not the case.

cheers.

-a
--
strong and healthy, who thinks of sickness until it strikes like lightning?
preoccupied with the world, who thinks of death, until it arrives like
thunder? -- milarepa

Logan Capaldo

unread,
Jan 14, 2006, 4:54:16 PM1/14/06
to

On Jan 14, 2006, at 4:41 PM, ara.t....@noaa.gov wrote:

> in any case, ruby makes no guarantee that
>
> (a..b).each{|x| p x}
>
> will complete, so even if we had real numbers that would still be a
> valid
> construct - though mixing in Enumerable into Range would be strange
> then - but
> it not the case.

Here's a thought, get rid of #each and Enumerable from Range, but add
in a #to_enum method (no args) that would return an Enumerator for
the range. This way programmers could say, "I solemnly swear that I
know iterating thru a range of strings doesn't make sense with
include, but I want to do it anyway."

("ab".."xyz").to_enum.to_a # etc...

On second thought, that looks unnecessary.

How about:

% cat a.rb
("abc".."wxyz").to_a

% ruby -w a.rb
warning: You fool! By doing this you know include? will make no sense!

Maybe not.

Stupid ranges. Hmph.

ara.t....@noaa.gov

unread,
Jan 14, 2006, 5:29:18 PM1/14/06
to
On Sun, 15 Jan 2006, David Vallner 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.

David Vallner

unread,
Jan 14, 2006, 6:12:14 PM1/14/06
to
On Sat, 14 Jan 2006 23:29:18 +0100, <ara.t....@noaa.gov> 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 ;P

David Vallner


Adam Shelly

unread,
Jan 16, 2006, 12:33:26 AM1/16/06
to
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

0 new messages