chaining comparisons

K

Kurt M. Dresner

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...

-Kurt
 
D

Daniel Carrera

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I don't know if there's a good reason for it, but I think it'd be cool if
Ruby had it.

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...

-Kurt

- --
Daniel Carrera | OpenPGP fingerprint:
Mathematics Dept. | 6643 8C8B 3522 66CB D16C D779 2FDD 7DAC 9AF7 7A88
UMD, College Park | http://www.math.umd.edu/~dcarrera/pgp.html
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (SunOS)

iD8DBQE/GN7LnxE8DWHf+OcRAthlAJ4zuAlY0EjdVHEwFLohg/G8OwOsXwCgq63a
blsFqUDYa9irZbjUPNyEdDI=
=bI3R
-----END PGP SIGNATURE-----
 
P

Phil Tomson

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I don't know if there's a good reason for it, but I think it'd be cool if
Ruby had it.


I can see how it would be cool, however:

1 < 2 #this gives a value of true
1 < 2 < 3 #this calls the '<(3)' method on true

It essentially looks like:
(true) < 3

Which is meaningless.

to do this you've got to somehow change the parser so that for the special cases of
<,>,<=,>= you instead call the method on the middle value in the chain of thee values.
Or you've got to somehow make '1 < 2' return 2 instead of/or in addition to true.
Seems problematic and could very likely break things.

2.between?(1,3)
May not look as pretty, but works fine without potential parser headaches.

Phil
 
G

Gavin Sinclair

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...

I agree it would be cool, but it's pretty clear why Ruby doens't
support it:

2 < 3 == true
1 < true == error
therefore 1 < 2 < 3 == error

Ruby is a very expression-oriented language, and derives its strength
from conceptual purity. If an expression evaluated to X in some
circumstances and Y in others, a small part of Ruby would be lost.

Gavin
 
D

Daniel Carrera

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I agree it would be cool, but it's pretty clear why Ruby doens't
support it:

2 < 3 == true
1 < true == error
therefore 1 < 2 < 3 == error

Ruby is a very expression-oriented language, and derives its strength
from conceptual purity. If an expression evaluated to X in some
circumstances and Y in others, a small part of Ruby would be lost.

I realize that this is a dumb question, but what is an expression-oriented
language?

Can you contrast Ruby with a language that is not expression-oriented?


- --
Daniel Carrera | OpenPGP fingerprint:
Mathematics Dept. | 6643 8C8B 3522 66CB D16C D779 2FDD 7DAC 9AF7 7A88
UMD, College Park | http://www.math.umd.edu/~dcarrera/pgp.html
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (SunOS)

iD8DBQE/GOp2nxE8DWHf+OcRAjzWAJ0c6ZKLbvxueD6lSX4T7dWBhS0eMQCeJA54
IFr+5WRHMr+tDTeIz+YDNwo=
=Y+Y0
-----END PGP SIGNATURE-----
 
S

Simon Strandgaard

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...


You can do this:

if [1, 2, 3].ordered?
puts "ok"
end


It will require that you extend the Array class yourself, like this:
expand -t4 b.rb
class Array
def ordered?
return true if self.empty?
a = self.first
self[1..-1].each { |b|
return false if a > b
a = b
}
true
end
end

p [].ordered? #=> true
p [2].ordered? #=> true
p [-1, 2, 3, 5].ordered? #=> true
p [17, 13, 11].ordered? #=> false
p [1, -1, 1, 0].ordered? #=> false
The output is:
 
M

Martin DeMello

Gavin Sinclair said:
I agree it would be cool, but it's pretty clear why Ruby doens't
support it:

2 < 3 == true
1 < true == error
therefore 1 < 2 < 3 == error

Ruby is a very expression-oriented language, and derives its strength
from conceptual purity. If an expression evaluated to X in some
circumstances and Y in others, a small part of Ruby would be lost.

One way to do it would be to have 'if' call to_boolean on its argument,
and have < return an object that carried some state around.

martin
 
G

Gavin Sinclair

I realize that this is a dumb question, but what is an expression-oriented
language?

Don't worry, I've never seen/heard that assortment of words either.
What I mean is that Ruby gives primacy to expressions (as opposed to
statements). For instance (not tested):

extractor =
case opt
when "-r" then ReceiptExtractor
when "-t" then TransactionExtractor
end.new(filename)

Every chunk of code has a "return value" that can be used to build
larger expressions.
Can you contrast Ruby with a language that is not expression-oriented?

Try the concepts in the above code in just about any language :)

Many/most languages enforce a difference between "statement" and
"expression". Pascal is an example.

Gavin
 
B

Brian Candler

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...

You can also use range objects for a clean alternative to a <= b <= c :

x = 2 # or 1 or 3
if (1..3) === x
puts "It's in the range"
end

The strange 'backwards' ordering is to support case statements, which make
it look much clearer:

case x
when (1..3)
puts "It's in the lower range"
when (4..6)
puts "It's in the upper range"
else
puts "It's out of range"
end

In the above, 3.5 is also 'out of range'. But the three-dot form of range
supports a <= b < c

puts "nope" if (1...3) === 3.0 # false
puts "yep " if (1...3) === 2.99 # true

case x
when (1...4) # 1 to 3.999
when (4...7) # 4 to 6.999
when (7...10) # 7 to 9.999
end

Cheers,

Brian.
 
S

Simon Strandgaard

You can do this:

if [1, 2, 3].ordered?
puts "ok"
end


It will require that you extend the Array class yourself, like this:
expand -t4 b.rb
class Array
def ordered?
self == sort
end
end

(I'm a lazy typist :)

Work smarter, Not harder.
I know sort, But this solution didn't come to mind.

Both versions generate a temporary copy of the array, e.g. self[1..-1] does
that too. But you can avoid it:

Yes. Avoiding temporary copy were my goal when I started writing it.
As you can see I apparently forgot it in the hurry :)


[snip enum#ordered?]
You can then check whether all the lines in a file are ordered, for example,
without reading it into memory. (I am a big fan of Enumerable :)

Enum is Nice.

Some more thoughts on #ordered?

Supplying an operator, could be useful?

[3, 2, 1].ordered? :> #=> true
[2, 2, 2].ordered? :> #=> false
[2, 2, 2].ordered? :>= #=> true


Supplying an block could also be useful?

[2, 2, 2].ordered? { |a, b| (((a-b) ^ (b-a)) % 3) == 0 }
#=> true

[1, 2, 3].ordered? { |a, b| (((a-b) ^ (b-a)) % 3) == 0 }
#=> false
 
M

Martin DeMello

Simon Strandgaard said:
class Array
def ordered?
return true if self.empty?
a = self.first
self[1..-1].each { |b|
return false if a > b
a = b
}
true
end
end

This (anti?)pattern always bothers me - surely a 2-element sliding
window is a common enough pattern that we should capture it once and for
all in Enumerable.

It's in Joel VanderWerf's excellent EnumerableTools, but people tend not
to include external packages when writing quick scripts. For instance,
the current script would simply be

class Array
def ordered?
each_cluster(2){|i,j| return false if i > j}
true
end
end

Personally, I'd like to see an each and an eachn, both taking block
arity into account:

a = *(1..6)
a.eachn {|x,y| p [x,y]} #=> [1,2] [3,4] [5,6]
a.each {|x,y| p [x,y]} #=> [1,2] [2,3] [3,4] [4,5] [5,6]

This would also preserve the semantics of 'each' only consuming one
element per iteration.

martin
 
P

Pit Capitain

Both versions generate a temporary copy of the array, e.g. self[1..-1] does
that too. But you can avoid it:

module Enumerable
def ordered?
first = true
prev = nil
each do |item|
if first
first = false
else
return false if prev > item
end
prev = item
end
return true
end
end

I'm not Dave Thomas :) but you can also use inject here:

module Enumerable
def ordered?
inject { | last, item | return false unless last < item; item }
true
end
end

Regards,
Pit
 
B

Brian Candler

I'm not Dave Thomas :) but you can also use inject here:

module Enumerable
def ordered?
inject { | last, item | return false unless last < item; item }
true
end
end

Which implementation of 'inject' are you using? The one in PragProg requires
an initial value to be passed as a parameter.

With an array of N elements you either need to do N-1 comparisons, or you
need to start with a sentinel value which is guaranteed to be less than all
other elements (nil and 0 both aren't suitable). So I don't see how the
above works, but without its partner 'inject' implementation I can't comment
further.

Regards,

Brian.
 
P

Pit Capitain

Which implementation of 'inject' are you using? The one in PragProg requires
an initial value to be passed as a parameter.

It's Andy's Windows installer version:

D:\Temp>ruby -v
ruby 1.8.0 (2003-05-26) [i386-mswin32]


And ri says about inject:

D:\Temp>ri inject
This is a test 'ri'. Please report errors and omissions
on http://www.rubygarden.org/ruby?RIOnePointEight

------------------------------------------------------
Enumerable#inject
enumObj.inject(initial) {| memo, obj | block } -> anObject
enumObj.inject {| memo, obj | block } -> anObject
---------------------------------------------------------------------
Combines the elements of enumObj by applying the block to an
accumulator value (memo) and each element in turn. At each step,
memo is set to the value returned by the block. The first form lets
you supply an initial value for memo. The second form uses the
first element of the collection as a the initial value (and skips
that element while iterating).
With an array of N elements you either need to do N-1 comparisons,

This is what the second form does.
or you
need to start with a sentinel value which is guaranteed to be less than all
other elements (nil and 0 both aren't suitable).

This is what the first form would do.

BTW, if you wanted to add the compare method as a parameter as
someone else suggested, you could define

module Enumerable
def ordered?( compare_method = :< )
inject { | last, item |
return false unless last.send( compare_method, item )
item
}
true
end
end

and then call it like

p [ 1, 2, 2 ].ordered? # => false
p [ 1, 2, 2 ].ordered?( :<= ) # => true

Isn't it fun to code in Ruby :< )

Regards,
Pit
 
J

Josef 'Jupp' Schugt

Saluton!

* Kurt M. Dresner; 2003-07-19, 11:33 UTC:
When I learned python I was overjoyed that I could evaluate 1 < 2 <
3 and get "true". I just realized that you can't do that in Ruby.
Is there a reason why?

a < b and b < c imply a < c. But what if you want to evaluate
a < b > c? Which conditons do you require to be met?

(1) a < b, b > c and a < c
(2) a < b, b > c and a > c
(3) a < b, b > c and nothing more

Some examples:

2 < 3 > 1 -> (2), (3)
1 < 3 > 2 -> (1), (3)
2 < 3 > 2 -> (3)

It seems to be better *not* to use '<' and the like. Better use
methods that can be applied to an arbitrary number of elements:

def increasing(*list)
return true if list.length < 2
y = nil
list.each {|x|
return false unless y.nil? or y < x
y = x
}
true
end

Gis,

Josef 'Jupp' Schugt
 
J

Jason Creighton

When I learned python I was overjoyed that I could evaluate 1 < 2 < 3
and get "true". I just realized that you can't do that in Ruby. Is
there a reason why? Is it good? I know I can use "between", but
still...

http://www.rubygarden.org/article.php?sid=286

So basically, it's because it's hard to implement, even more so because
true/false/nil are singleton objects. (So you can't, for instance, save state
in a particular instance of 'true', because there's only one.)

Jason Creighton
 
J

Jason Creighton

Which implementation of 'inject' are you using? The one in PragProg requires
an initial value to be passed as a parameter.

The inject in 1.8 defaults its initial value to the first item in the collection.

Jason Creighton
 
J

Joel VanderWerf

Jason said:
http://www.rubygarden.org/article.php?sid=286

So basically, it's because it's hard to implement, even more so because
true/false/nil are singleton objects. (So you can't, for instance, save state
in a particular instance of 'true', because there's only one.)

As you say, there is only one true; but you still can save state in it:

irb(main):001:0> true.instance_eval {@x = 1}
1
irb(main):002:0> true.instance_eval {@x}
1

Not that it's relevant to the 1 < 2 < 3 discussion, but it's kinda cute...
 
M

Mauricio Fernández

As you say, there is only one true; but you still can save state in it:

irb(main):001:0> true.instance_eval {@x = 1}
1
irb(main):002:0> true.instance_eval {@x}
1

It's hard to achieve thread-safety that way.


--
_ _
| |__ __ _| |_ ___ _ __ ___ __ _ _ __
| '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \
| |_) | (_| | |_\__ \ | | | | | (_| | | | |
|_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

- DDD no longer requires the librx library. Consequently, librx
errors can no more cause DDD to crash.
-- DDD
 
J

Joel VanderWerf

Mauricio said:
It's hard to achieve thread-safety that way.

No worse than a global var, anyway. It wasn't a serious suggestion, just
a statement of surprise that it was even possible to set attributes of
literals. You can do it for Fixums, too. (From reading the source, I
seem to remember that it's not done by storing anything within the
object, which is impossible with a Fixnum, but by a big hash mapping
objects and instance var names to values.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top