Chaining comparisons

I’m not Dave Thomas :slight_smile: 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

···

On 19 Jul 2003 at 17:41, Brian Candler wrote:

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

Expression-oriented languages return values from the expressions.
Statement-oriented languages don’t. Perl and Ruby are expression-
oriented; C and Pascal are not.

# Ruby

def foo
“Foo”
end
puts foo

# Perl

sub foo {
“Foo”
}
print &foo;

/* C */

char *foo() {
return “Foo”;
}
printf(“%s\n”, foo());

{ Pascal }

Function foo: Return String;
Begin
Return “Foo”;
End;
WriteLn(Foo());

The C and the Pascal require explicit return statements to work. The
Perl and Ruby do not.

-austin

···

On Sat, 19 Jul 2003 15:51:42 +0900, Daniel Carrera wrote:

On Sat, Jul 19, 2003 at 03:47:38PM +0900, Gavin Sinclair wrote:

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?


austin ziegler * austin@halostatue.ca * Toronto, ON, Canada
software designer * pragmatic programmer * 2003.07.19
* 12:06:45

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

···

On Sun, Jul 20, 2003 at 02:22:37PM +0900, Joel VanderWerf wrote:

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


_ _

__ __ | | ___ _ __ ___ __ _ _ __
'_ \ / | __/ __| '_ _ \ / ` | ’ \
) | (| | |
__ \ | | | | | (| | | | |
.__/ _,
|_|/| || ||_,|| |_|
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

Jason Creighton wrote:

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.)

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…

Yeah, it is kinda cute. How does that work? It works for Fixnum objects,
too…

(0…16).each { |n| n.instance_eval { @square = n*n } }
=> 0…16
5.instance_eval { @square }
=> 25

How can that work when Fixnum is stored in VALUE? And that’s another thing:
VALUE is either an immediate value (Fixnum/true/false/nil) or a pointer to an
object. Doesn’t that mean there’s only 31 bits (on 32 bit machins) available
for the pionter? How can that work? I always thought that pointers would have
to be able to cover the whole address space…

Jason Creighton

···

On Sun, 20 Jul 2003 14:22:37 +0900 Joel VanderWerf vjoel@PATH.Berkeley.EDU wrote:

On Sat, 19 Jul 2003 14:58:51 +0900 > > “Kurt M. Dresner” kdresner@cs.utexas.edu wrote:

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.

···

On Sat, Jul 19, 2003 at 11:23:04PM +0900, Pit Capitain wrote:

I’m not Dave Thomas :slight_smile: but you can also use inject here:

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

I think that’s a misleading characterization, especially if it lumps
C and Pascal together. They’re similar languages, but they have
some important differences, and while I would agree that Pascal is
not expression-oriented, I would say that C is. The fact that a
function call requires an explicit return is somewhat irrelevant to
the larger point that single statements in C, including assignments,
have values, whereas in Pascal no statements have values.

C is not as thoroughly expression-oriented as Perl and Ruby,
to be sure. Compound statements don’t have a return value
(which is why the ?:: operator exists; you can’t do
{x = if (a) b; else (c);}), and you can declare void functions which
are likewise valueless (although this was a later addition to the language).

Chained comparisons don’t work in Perl5, either, though they may work
in Perl6. But then Perl is not known to eschew features just because they
may be more complicated to compile; on the language scale of
“regular”, “context-free”, “context-sensitive”, Perl may be described
as “context-downright-touchy”.

-Mark

···

On Sun, Jul 20, 2003 at 01:13:00AM +0900, Austin Ziegler wrote:

Expression-oriented languages return values from the expressions.
Statement-oriented languages don’t. Perl and Ruby are expression-
oriented; C and Pascal are not.

Mauricio Fernández wrote:

···

On Sun, Jul 20, 2003 at 02:22:37PM +0900, Joel VanderWerf wrote:

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.

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.)

Jason Creighton wrote:

How can that work when Fixnum is stored in VALUE? And that’s another thing:
VALUE is either an immediate value (Fixnum/true/false/nil) or a pointer to an
object. Doesn’t that mean there’s only 31 bits (on 32 bit machins) available
for the pionter? How can that work? I always thought that pointers would have
to be able to cover the whole address space…

You don’t need the low-order bit if all allocations are on even
addresses (some archtectures even force this).

How can that work when Fixnum is stored in VALUE? And that's another thing:
VALUE is either an immediate value (Fixnum/true/false/nil) or a pointer to an
object. Doesn't that mean there's only 31 bits (on 32 bit machins) available
for the pionter?

You have effectively only 31 bits available for an ID, but not for the
reason that you think. This will give you the maximum number of ID that
you can have in a script.

On a 32 bits machins, you can have only (2**20 - 1) ID for a script, if
I'm right

Guy Decoux

An alternative

def less(*args); compare_chain(proc{|a,b|a<b}, *args); end
def greater(*args); compare_chain(proc{|a,b|a>b}, *args); end

def compare_chain(comp, *args)
last = nil

args.each do |i|
return false if last and not comp.call(last,i)
last = i
end

true
end

less 1,10,22
less 2,1,14
less -2,1,14
greater 10,3,-100
greater 2,1,14

robert

“Simon Strandgaard” 0bz63fz3m1qt3001@sneakemail.com schrieb im
Newsbeitrag news:pan.2003.07.19.09.12.52.551696@sneakemail.com

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 :slight_smile:

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 :slight_smile:

[snip enum#ordered?]

You can then check whether all the lines in a file are ordered, for
example,

···

On Sat, 19 Jul 2003 18:41:33 +0900, Brian Candler wrote:

On Sat, Jul 19, 2003 at 05:08:03PM +0900, Simon Strandgaard wrote:
without reading it into memory. (I am a big fan of Enumerable :slight_smile:

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


Simon Strandgaard

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

svg% cat b.rb
#!/usr/bin/ruby -v
module Enumerable
   def ordered?
      inject { | last, item | return false unless last < item; item }
      true
   end
end

p [1,2,3,1].ordered?
p [1,2,3].ordered?
svg%

svg% b.rb
ruby 1.8.0 (2003-07-18) [i686-linux]
false
true
svg%

Guy Decoux

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

···

On 20 Jul 2003 at 0:10, Brian Candler wrote:

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


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

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

Jason Creighton

···

On Sun, 20 Jul 2003 00:10:25 +0900 Brian Candler B.Candler@pobox.com wrote:

On Sat, Jul 19, 2003 at 11:23:04PM +0900, Pit Capitain wrote:

I’m not Dave Thomas :slight_smile: 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.

OK… this is the nicest solution so far :slight_smile:

I didn’t thought of using proc’s for binary compares.

···

On Mon, 21 Jul 2003 14:54:10 +0200, Robert Klemme wrote:

An alternative

def less(*args); compare_chain(proc{|a,b|a<b}, *args); end
def greater(*args); compare_chain(proc{|a,b|a>b}, *args); end

def compare_chain(comp, *args)
last = nil

args.each do |i|
return false if last and not comp.call(last,i)
last = i
end

true
end

less 1,10,22
less 2,1,14
less -2,1,14
greater 10,3,-100
greater 2,1,14


Simon Strandgaard

“Simon Strandgaard” 0bz63fz3m1qt3001@sneakemail.com schrieb im
Newsbeitrag news:pan.2003.07.21.12.42.13.887241@sneakemail.com

OK… this is the nicest solution so far :slight_smile:

I’ve found an even nicer solution that allows user defined comparisons
nicely:

def less(*args); check_chain(*args){|a,b|a<b}; end
def greater(*args); check_chain(*args){|a,b|a>b}; end

def check_chain(*args, &comp)
raise “Block needed” unless comp

last = nil

args.each do |i|
return false if last and not comp.call(last,i)
last = i
end

true
end

less 1,10,22
less 2,1,14
less -2,1,14
greater 10,3,-100
greater 2,1,14

check_chain( 2, 4, 16 ){|a,b| aa==b}
check_chain( 2, 4, 17 ){|a,b| a
a==b}

The thing that bugged my about the last one was the first parameter to
compare_chain() which prevented nice user defined checks.

I didn’t thought of using proc’s for binary compares.

The proposed implementation of ordering? inspired my. Though while
writing this I see a more general pattern here that reminds a bit of
Enumerable#inject (Ruby 1.8):

module Enumerable
def connect(coll)
last = nil
first = true

each do |i|
  if first
    first = false
  else
    coll = yield(coll,last,i)
  end

  last = i
end

coll

end
end

def less(*args); args.connect( true ){|c,a,b|c && a<b}; end
def greater(*args); args.connect( true ){|c,a,b|c && a>b}; end

[1, 2, 3].connect( true ){|coll,a,b|coll && a<b}
[2, 4, 16].connect( true ){|coll,a,b|coll && a*a==b}

These versions are less efficient for sequences that fail the test but one
might consider to change the implementation to stop if coll is false or
nil.

Method Enumerable#connect might even be considert a library addenum, IMHO.
Maybe inject might be changed to yield one more parameter the way it is
shown here. What does Matz think? What do others think?

Regards

robert

“Robert Klemme” bob.news@gmx.net schrieb im Newsbeitrag
news:bfgvjq$e7itp$1@ID-52924.news.uni-berlin.de

(Now I start already at 36 talking to myself…)

Method Enumerable#connect might even be considert a library addenum, IMHO.
Maybe inject might be changed to yield one more parameter the way it is
shown here. What does Matz think? What do others think?

I’d like to propose a slightly different version of Enumerable#inject which
uses a sliding window on the enum and is compatible with the 1.8 version:

module Enumerable
def inject(val=nil, &bl)
raise “Block missing” unless bl

size = bl.arity
raise "Need block with at least 1 argument" if size == 0

size = size < 0 ? 1+size : size-1
args=[]

each do |e|
  args.push e

  if args.length == size
    val = yield val, *args
    args.shift
  end
end

val

end
end

Now, apart from the usual inject things like sum, product and max
calculations you can do a lot of nice things like

irb(main):374:0* a=[1, 20, 30, 40, 41, 42, 88, 123]
=> [1, 20, 30, 40, 41, 42, 88, 123]
irb(main):375:0> b=
=>
irb(main):376:0> # SMOOTH
irb(main):377:0* sm=
=>
irb(main):378:0> a.inject(0){|idx, x, y| sm[idx] = (x+y).to_f / 2; idx+1}
=> 7
irb(main):379:0> sm
=> [10.5, 25.0, 35.0, 40.5, 41.5, 65.0, 105.5]
irb(main):380:0> sm=
=>
irb(main):381:0> a.inject(0){|idx, x, y, z| sm[idx] = (x+y+z).to_f / 3;
idx+1}
=> 6
irb(main):382:0> sm
=> [17.0, 30.0, 37.0, 41.0, 57.0, 84.33333333]
irb(main):383:0> # WEIGHTED SMOOTHING
irb(main):384:0* sm=
=>
irb(main):385:0> a.inject(0){|idx, x, y, z| sm[idx] = (x+y+y+z).to_f / 4;
idx+1}
=> 6
irb(main):386:0> sm
=> [17.75, 30.0, 37.75, 41.0, 53.25, 85.25]
irb(main):387:0> # TEST SORTED
irb(main):388:0* a.inject(true) {|cond, x, y| cond && x<y}
=> true
irb(main):389:0> b.inject(true) {|cond, x, y| cond && x<y}
=> true
irb(main):390:0> def ordered?(*args); args.inject(true) {|cond, x, y| cond
&& x<
y}; end
=> nil
irb(main):391:0> ordered? *a
=> true
irb(main):392:0> ordered? 1,2,4
=> true
irb(main):393:0>

What do you think?

robert