Cascading <=> comparisons

Let's say I have a hash with some values in it, and I want to
print out that hash in some sort order. I can:

korder = vals.keys.sort { |ka, kb|
    vals[kb] <=> vals[ka]
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

That's pretty easy, but what if I want to cascade comparisons
in that sort, such that if the data-values are equal, then I want
the sort order to be based on some other comparison? It
seems to me that I have to:

korder = vals.keys.sort { |ka, kb|
    sres = vals[kb] <=> vals[ka]
    sres = ka <=> kb if sres == 0
    sres
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

In perl, I can just casade the '<=>' comparisons, because it
will treat the zero value as "false". Thus, I could get away
with a one-liner somewhat similar to:

vals[kb] <=> vals[ka] or a <=> kb

but that doesn't work in ruby. Is there some other way I could
collapse multiple <=> comparisons into a single statement?
I do not mind the multi-statement form (now that I've convinced
myself that I can't cascade them the way I could in perl), but I'm
just wondering if there's a better way to do it.

···

--
Garance Alistair Drosehn = drosihn@gmail.com
Senior Systems Programmer or gad@FreeBSD.org
Rensselaer Polytechnic Institute; Troy, NY; USA

Hi --

···

On Sat, 16 Jul 2005, Garance A Drosehn wrote:

Let's say I have a hash with some values in it, and I want to
print out that hash in some sort order. I can:

korder = vals.keys.sort { |ka, kb|
   vals[kb] <=> vals[ka]
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

That's pretty easy, but what if I want to cascade comparisons
in that sort, such that if the data-values are equal, then I want
the sort order to be based on some other comparison? It
seems to me that I have to:

korder = vals.keys.sort { |ka, kb|
   sres = vals[kb] <=> vals[ka]
   sres = ka <=> kb if sres == 0
   sres
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

You can use sort_by and put your sort keys in an array. For example,
this sorts a hash by value and then by key:

   hash = { "a" => 1, "b" => 4, "c" => 3, "d" => 1 }
   p hash.sort_by {|k,v| [v,k] }
     => [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

David

--
David A. Black
dblack@wobblini.net

(vals[kb] <=> vals[ka]).nonzero? or a <=> kb

···

--- Garance A Drosehn <drosihn@gmail.com> wrote:

Let's say I have a hash with some values in it, and I want to
print out that hash in some sort order. I can:

korder = vals.keys.sort { |ka, kb|
    vals[kb] <=> vals[ka]
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

That's pretty easy, but what if I want to cascade comparisons
in that sort, such that if the data-values are equal, then I
want
the sort order to be based on some other comparison? It
seems to me that I have to:

korder = vals.keys.sort { |ka, kb|
    sres = vals[kb] <=> vals[ka]
    sres = ka <=> kb if sres == 0
    sres
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

In perl, I can just casade the '<=>' comparisons, because it
will treat the zero value as "false". Thus, I could get away
with a one-liner somewhat similar to:

vals[kb] <=> vals[ka] or a <=> kb

but that doesn't work in ruby. Is there some other way I
could
collapse multiple <=> comparisons into a single statement?
I do not mind the multi-statement form (now that I've
convinced
myself that I can't cascade them the way I could in perl),
but I'm
just wondering if there's a better way to do it.

____________________________________________________
Start your day with Yahoo! - make it your home page
http://www.yahoo.com/r/hs

Hi,

At Sat, 16 Jul 2005 11:08:57 +0900,
Garance A Drosehn wrote in [ruby-talk:148321]:

In perl, I can just casade the '<=>' comparisons, because it
will treat the zero value as "false". Thus, I could get away
with a one-liner somewhat similar to:

vals[kb] <=> vals[ka] or a <=> kb

(vals[kb] <=> vals[ka]).nonzero? or (ka <=> kb)

···

--
Nobu Nakada

[vals[kb], ka] <=> [vals[ka], kb]

note that the array approach scales for any number of things, not only two.
you can always do

   [a,b,c] <=> [x,y,z]

even if a, b, and c are different types. so long as you can

   a <=> x
   b <=> y
   c <=> z

hth.

-a

···

On Sat, 16 Jul 2005, Garance A Drosehn wrote:

Let's say I have a hash with some values in it, and I want to
print out that hash in some sort order. I can:

korder = vals.keys.sort { |ka, kb|
   vals[kb] <=> vals[ka]
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

That's pretty easy, but what if I want to cascade comparisons
in that sort, such that if the data-values are equal, then I want
the sort order to be based on some other comparison? It
seems to me that I have to:

korder = vals.keys.sort { |ka, kb|
   sres = vals[kb] <=> vals[ka]
   sres = ka <=> kb if sres == 0
   sres
}
korder.each { |key| printf " %3d - %s\n", vals[key], key }

In perl, I can just casade the '<=>' comparisons, because it
will treat the zero value as "false". Thus, I could get away
with a one-liner somewhat similar to:

vals[kb] <=> vals[ka] or a <=> kb

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
My religion is very simple. My religion is kindness.
--Tenzin Gyatso

===============================================================================

Ooo. Very nice. Thanks!

The suggestions to look at sort_by (from Enumerable) and
the idea of: (vals[kb] <=> vals[ka]).nonzero? or (ka <=> kb)
were also very useful. Thanks to all.

···

On 7/15/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Sat, 16 Jul 2005, Garance A Drosehn wrote:

> That's pretty easy, but what if I want to cascade comparisons
> in that sort, such that if the data-values are equal, then I want
> the sort order to be based on some other comparison? ...

[vals[kb], ka] <=> [vals[ka], kb]

note that the array approach scales for any number of things,
not only two. you can always do

   [a,b,c] <=> [x,y,z]

even if a, b, and c are different types. so long as you can

   a <=> x
   b <=> y
   c <=> z

--
Garance Alistair Drosehn = drosihn@gmail.com
Senior Systems Programmer or gad@FreeBSD.org
Rensselaer Polytechnic Institute; Troy, NY; USA

That's pretty easy, but what if I want to cascade comparisons
in that sort, such that if the data-values are equal, then I want
the sort order to be based on some other comparison? ...

[vals[kb], ka] <=> [vals[ka], kb]

note that the array approach scales for any number of things,
not only two. you can always do

   [a,b,c] <=> [x,y,z]

even if a, b, and c are different types. so long as you can

   a <=> x
   b <=> y
   c <=> z

Ooo. Very nice. Thanks!

The suggestions to look at sort_by (from Enumerable) and
the idea of: (vals[kb] <=> vals[ka]).nonzero? or (ka <=> kb)
were also very useful. Thanks to all.

Noone explicitely commented on the use of hash.keys.sort: this is quite inefficient. Instead doing the sort on the hash directly is much more efficient:

hash = { "a" => 1, "b" => 4, "c" => 3, "d" => 1 }

=> {"a"=>1, "b"=>4, "c"=>3, "d"=>1}

hash.sort {|a,b| a.reverse <=> b.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

This technique can be applied to sort_by, too:

hash.sort_by {|a| a.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

But this shows a strange anomaly - this seems like a bug in 1.8.2.

hash.sort_by {|a| a.reverse!}

=> [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]

You can also directly chain printing:

hash.sort_by {|a| a.reverse}.each {|k,v| printf "%3d %s\n", v, k}

  1 a
  1 d
  3 c
  4 b
=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

Kind regards

    robert

···

Garance A Drosehn <drosihn@gmail.com> wrote:

On 7/15/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Sat, 16 Jul 2005, Garance A Drosehn wrote:

Hi --

···

On Sat, 16 Jul 2005, Robert Klemme wrote:

This technique can be applied to sort_by, too:

hash.sort_by {|a| a.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

But this shows a strange anomaly - this seems like a bug in 1.8.2.

hash.sort_by {|a| a.reverse!}

=> [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]

I'll probably figure this out as soon as I hit 'send'... but what's
the anomaly here?

David

--
David A. Black
dblack@wobblini.net

I can see how this would be faster wrt execution, but if I use
hashobj.sort then I end up with an array of arrays which have
both the key-names and their values. Memory-wise, isn't that
going to be significantly larger than an array which only has
the key-names? When I wrote this, I mainly thought about the
memory usage, and I just assumed that the performance would
be "about-the-same" either way...

I'll check over my actual script wrt this idea. It's probably true
that I would rather optimize the speed than the memory usage,
in which case I might take advantage of this. I'll certainly use
it in other scripts I write! Thanks.

(note that in the actual script, the values are not simple integers,
but objects which in turn have a lot of instance-variables...)

···

On 7/16/05, Robert Klemme <bob.news@gmx.net> wrote:

Garance A Drosehn <drosihn@gmail.com> wrote:

Noone explicitely commented on the use of hash.keys.sort:
this is quite inefficient. Instead doing the sort on the hash
directly is much more efficient:

--
Garance Alistair Drosehn = drosihn@gmail.com
Senior Systems Programmer or gad@FreeBSD.org
Rensselaer Polytechnic Institute; Troy, NY; USA

That doesn't make a difference as they are not copied. Mem overhead is just the temp arrays.

Kind regards

    robert

···

Garance A Drosehn <drosihn@gmail.com> wrote:

On 7/16/05, Robert Klemme <bob.news@gmx.net> wrote:

Garance A Drosehn <drosihn@gmail.com> wrote:

Noone explicitely commented on the use of hash.keys.sort:
this is quite inefficient. Instead doing the sort on the hash
directly is much more efficient:

I can see how this would be faster wrt execution, but if I use
hashobj.sort then I end up with an array of arrays which have
both the key-names and their values. Memory-wise, isn't that
going to be significantly larger than an array which only has
the key-names? When I wrote this, I mainly thought about the
memory usage, and I just assumed that the performance would
be "about-the-same" either way...

I'll check over my actual script wrt this idea. It's probably true
that I would rather optimize the speed than the memory usage,
in which case I might take advantage of this. I'll certainly use
it in other scripts I write! Thanks.

(note that in the actual script, the values are not simple integers,
but objects which in turn have a lot of instance-variables...)

And, did you figure in the meantime? It's the different result - IMHO anything that sort_by does should have no such effect on the result.

Cheers

    robert

···

David A. Black <dblack@wobblini.net> wrote:

Hi --

On Sat, 16 Jul 2005, Robert Klemme wrote:

This technique can be applied to sort_by, too:

hash.sort_by {|a| a.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

But this shows a strange anomaly - this seems like a bug in 1.8.2.

hash.sort_by {|a| a.reverse!}

=> [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]

I'll probably figure this out as soon as I hit 'send'... but what's
the anomaly here?

"Robert Klemme" <bob.news@gmx.net> writes:

···

David A. Black <dblack@wobblini.net> wrote:
> Hi --
>
> On Sat, 16 Jul 2005, Robert Klemme wrote:
>
>> This technique can be applied to sort_by, too:
>>
>>>> hash.sort_by {|a| a.reverse}
>> => [["a", 1], ["d", 1], ["c", 3], ["b", 4]]
>>
>> But this shows a strange anomaly - this seems like a bug in 1.8.2.
>>
>>>> hash.sort_by {|a| a.reverse!}
>> => [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]
>
> I'll probably figure this out as soon as I hit 'send'... but what's
> the anomaly here?

And, did you figure in the meantime? It's the different result -
IMHO anything that sort_by does should have no such effect on the
result.

Even if the operation is a "bang" one?

--
I tend to view "truly flexible" by another term: "Make everything
equally hard". -- DHH

IMO yes.

    robert

···

Michael Campbell <michael.campbell@gmail.com> wrote:

"Robert Klemme" <bob.news@gmx.net> writes:

David A. Black <dblack@wobblini.net> wrote:

Hi --

On Sat, 16 Jul 2005, Robert Klemme wrote:

This technique can be applied to sort_by, too:

hash.sort_by {|a| a.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]

But this shows a strange anomaly - this seems like a bug in 1.8.2.

hash.sort_by {|a| a.reverse!}

=> [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]

I'll probably figure this out as soon as I hit 'send'... but what's
the anomaly here?

And, did you figure in the meantime? It's the different result -
IMHO anything that sort_by does should have no such effect on the
result.

Even if the operation is a "bang" one?

"Robert Klemme" <bob.news@gmx.net> writes:

···

Michael Campbell <michael.campbell@gmail.com> wrote:
> "Robert Klemme" <bob.news@gmx.net> writes:
>
>> David A. Black <dblack@wobblini.net> wrote:
>>> Hi --
>>> On Sat, 16 Jul 2005, Robert Klemme wrote:
>>>
>>>> This technique can be applied to sort_by, too:
>>>>
>>>>>> hash.sort_by {|a| a.reverse}
>>>> => [["a", 1], ["d", 1], ["c", 3], ["b", 4]]
>>>> But this shows a strange anomaly - this seems like a bug in 1.8.2.
>>>>
>>>>>> hash.sort_by {|a| a.reverse!}
>>>> => [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]
>>> I'll probably figure this out as soon as I hit 'send'... but what's
>>> the anomaly here?
>> And, did you figure in the meantime? It's the different result -
>> IMHO anything that sort_by does should have no such effect on the
>> result.

> Even if the operation is a "bang" one?

IMO yes.

Wow. How do you tell ruby to not do what it normally would? How do
reconcile that sort_by {|a| <x>} is different than sort_by {|a| <x!> }
for any given <x>? What about OTHER side effects of <X> (like, a
database call or something)?

--
I tend to view "truly flexible" by another term: "Make everything
equally hard". -- DHH

Note that in this special example we are sorting a hash and x is an array of two elements (key and value) - not necessarily something I'd expect to be part of the internal storage structure of a Hash. I didn't find documentation that the x in this case is actually part of the internal structure of the Hash and must not me modified. There are several solutions:

i) Make a copy (this is inefficient if done for each pair)

ii) Make the two element arrays immutable (this might be difficult because normally frozen instances cannot be heated again)

iii) Document the current behavior.

Kind regards

    robert

···

Michael Campbell <michael.campbell@gmail.com> wrote:

"Robert Klemme" <bob.news@gmx.net> writes:

Michael Campbell <michael.campbell@gmail.com> wrote:

"Robert Klemme" <bob.news@gmx.net> writes:

David A. Black <dblack@wobblini.net> wrote:

Hi --
On Sat, 16 Jul 2005, Robert Klemme wrote:

This technique can be applied to sort_by, too:

hash.sort_by {|a| a.reverse}

=> [["a", 1], ["d", 1], ["c", 3], ["b", 4]]
But this shows a strange anomaly - this seems like a bug in
1.8.2.

hash.sort_by {|a| a.reverse!}

=> [[1, "a"], [1, "d"], [3, "c"], [4, "b"]]

I'll probably figure this out as soon as I hit 'send'... but
what's the anomaly here?

And, did you figure in the meantime? It's the different result -
IMHO anything that sort_by does should have no such effect on the
result.

Even if the operation is a "bang" one?

IMO yes.

Wow. How do you tell ruby to not do what it normally would? How do
reconcile that sort_by {|a| <x>} is different than sort_by {|a| <x!> }
for any given <x>? What about OTHER side effects of <X> (like, a
database call or something)?