Forum: Ruby Newbie question about nested sort

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
9715f75919af16e170dda694178942e6?d=identicon&s=25 Grehom (Guest)
on 2006-01-04 13:58
(Received via mailing list)
what am I doing wrong?  I wanted to sort primarily on count (second
position in array), then sub-sort alphabetically (first position in
array)

char_freq = [["c", 2],["b", 5],["a", 2]]
sorted_freq = char_freq.sort {|a, b| b[1]<=>a[1] || a[0]<=>b[0] }
sorted_freq.each do |d|
   print d[0]*d[1]
end

produces >>  bbbbbccaa

when I was rather hoping for >> bbbbbaacc

I tried using brackets and using 'or' instead of '||' even tried the
sort_by method, any help much appreciated I know this works in perl
(add 13 dollar signs, etc and shake well!):

use strict;
use warnings;
my @char_freq = (["c", 2], ["b", 5], ["a", 2]);
foreach my $d (sort {$$b[1] <=> $$a[1] || $$a[0] cmp $$b[0] }
@char_freq) {
   print $$d[0] x $$d[1]
}

produces >> bbbbbaacc
5befe95e6648daec3dd5728cd36602d0?d=identicon&s=25 Robert Klemme (Guest)
on 2006-01-04 14:19
(Received via mailing list)
Grehom wrote:
> produces >>  bbbbbccaa
> foreach my $d (sort {$$b[1] <=> $$a[1] || $$a[0] cmp $$b[0] }
> @char_freq) {
>    print $$d[0] x $$d[1]
> }
>
> produces >> bbbbbaacc

Generally you need to do conditional evaluation based on higher prio
results.  Using "||" or "or" won't help here (dunno what Perl does
here).

You want something like

char_freq = [["c", 2],["b", 5],["a", 2]]
sorted_freq = char_freq.sort do |a, b|
  c = b[1]<=>a[1]
  c == 0 ? a[0]<=>b[0] : c
end
sorted_freq.each do |d|
   print d[0]*d[1]
end


You can make your life easier (though less efficient) with a helper:

def multi_compare(*results)
  results.each {|c| return c if c != 0}
  0
end

char_freq = [["c", 2],["b", 5],["a", 2]]
sorted_freq = char_freq.sort {|a,b| multi_compare(b[1]<=>a[1],
a[0]<=>b[0])}
sorted_freq.map {|a,b| a*b}.join

>> char_freq = [["c", 2],["b", 5],["a", 2]]
=> [["c", 2], ["b", 5], ["a", 2]]
>> sorted_freq = char_freq.sort {|a,b| multi_compare(b[1]<=>a[1],
a[0]<=>b[0])}
=> [["b", 5], ["a", 2], ["c", 2]]
>> sorted_freq.map {|a,b| a*b}.join
=> "bbbbbaacc"

You can also do something like this which avoids evaluation of all
conditions:

class Integer
  # condition chain
  def cc()
    self == 0 ? yield : self
  end
end

char_freq = [["c", 2],["b", 5],["a", 2]]
sorted_freq = char_freq.sort {|a,b| (b[1]<=>a[1]).cc { a[0]<=>b[0] }}
sorted_freq.map {|a,b| a*b}.join

>> char_freq.sort {|a,b| (b[1]<=>a[1]).cc { a[0]<=>b[0] }}.map {|a,b|
a*b}.join
=> "bbbbbaacc"

HTH

Kind regards

    robert
37a3c73ffbf864e4b28f7f2384ee12ce?d=identicon&s=25 Timothy Hunter (tim-hunter)
on 2006-01-04 14:25
(Received via mailing list)
Grehom wrote:
> produces >>  bbbbbccaa
>
> when I was rather hoping for >> bbbbbaacc
>
Is this what you want?

char_freq = [["c", 2],["b", 5],["a", 2]]
sorted_freq = char_freq.sort_by {|a| [-a[1], a[0]] }
sorted_freq.each do |d|
    print d[0]*d[1]
end
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-01-04 14:25
(Received via mailing list)
Hi --

On Wed, 4 Jan 2006, Grehom wrote:

> produces >>  bbbbbccaa
>
> when I was rather hoping for >> bbbbbaacc

Since 0 is true in Ruby (unlike Perl, where it's false), the || test
will never be performed, because b[1] <=> a[1] is always true.

You can use sort_by, though:

   sorted_freq = char_freq.sort_by {|a| [-a[1], a[0]] }

(You can also do explicit tests for a 0 result, but sort_by is much
more concise.)


David

--
David A. Black
dblack@wobblini.net

"Ruby for Rails", from Manning Publications, coming April 2006!
http://www.manning.com/books/black
9715f75919af16e170dda694178942e6?d=identicon&s=25 Grehom (Guest)
on 2006-01-04 14:28
(Received via mailing list)
Wow! thanks that's neat, I need to go back to my pickaxe book and study
the differences between sort and sort_by they are more subtle than I
noticed.
31af45939fec7e3c4ed8a798c0bd9b1a?d=identicon&s=25 Matthew Smillie (Guest)
on 2006-01-04 14:31
(Received via mailing list)
On Jan 4, 2006, at 13:17, Robert Klemme wrote:

> Grehom wrote:
>> what am I doing wrong?  I wanted to sort primarily on count (second
>> position in array), then sub-sort alphabetically (first position in
>> array)
>
> Generally you need to do conditional evaluation based on higher prio
> results.  Using "||" or "or" won't help here (dunno what Perl does
> here).

In Perl, 0 counts as 'false'.  Not so in Ruby.  For example:

         Perl  Ruby
1 || 0   1     1
0 || 1   1     0

So, in Perl, if the first comparison was equal, the result for the
second comparison would be returned by the disjunction.

matthew smillie.
9715f75919af16e170dda694178942e6?d=identicon&s=25 Grehom (Guest)
on 2006-01-04 14:34
(Received via mailing list)
Thanks! interesting stuff I'll read through this closely there are a
rew bits in there that have gone clear over my head at first reading
(the a*b}.join for example).
E7559e558ececa67c40f452483b9ac8c?d=identicon&s=25 unknown (Guest)
on 2006-01-04 14:46
(Received via mailing list)
On Jan 4, 2006, at 8:24 AM, dblack@wobblini.net wrote:
>
> You can use sort_by, though:
>
>   sorted_freq = char_freq.sort_by {|a| [-a[1], a[0]] }

The light bulb will go on with respect to this solution when
you discover that

	x <=> y

works as expected when x and y are Arrays.  I think this is
one of those cases where Ruby just makes you feel warm all over.


Gary Wright
9715f75919af16e170dda694178942e6?d=identicon&s=25 Grehom (Guest)
on 2006-01-04 18:12
(Received via mailing list)
Thanks everyone, very interesting.  The sort_by method certainly will
earn it's keep in my application as my test results show below

require 'benchmark'
include Benchmark

alphabet = ("abcdefghij")
char_freq = (1..100000).map {[alphabet[rand(9)].chr, rand(8)+1]}
bm(10) do |b|
   b.report("Sort by") { char_freq.sort_by {|a| [-a[1], a[0]] } }
   b.report("Sort")    { char_freq.sort {|a, b| (b[1]<=>a[1]).nonzero?
|| a[0]<=>b[0] } }
end

alphabet = ("abcdef")
char_freq = (1..100000).map {[alphabet[rand(5)].chr, rand(4)+1]}
bm(10) do |b|
   b.report("Sort by") { char_freq.sort_by {|a| [-a[1], a[0]] } }
   b.report("Sort")    { char_freq.sort {|a, b| (b[1]<=>a[1]).nonzero?
|| a[0]<=>b[0] } }
end

produces >>
C:\rubysrcs\Yahtzee>ruby -w test3.rb
                user     system      total        real
Sort by     1.469000   0.000000   1.469000 (  1.469000)
Sort        2.703000   0.000000   2.703000 (  2.703000)
                user     system      total        real
Sort by     0.766000   0.000000   0.766000 (  0.766000)
Sort        2.125000   0.016000   2.141000 (  2.141000)
9dfe8c734b0f9b37a4e218425c0a2138?d=identicon&s=25 Gene Tani (Guest)
on 2006-01-04 18:17
(Received via mailing list)
Grehom wrote:
> Thanks everyone, very interesting.  The sort_by method certainly will

just as background info, these techniques are called Schwartzian
transforms, or decorate/sort/undecorate // Guttman-Rosler transform (at
least in other languages' manuals)
This topic is locked and can not be replied to.