Consecutive sort on Array of Hashes

Discussion in 'Ruby' started by Laurent Colloud, Aug 14, 2006.

  1. Hi,

    Here my - strange - problem.

    To explain it, let's take the example of football. I construct an array
    of hashes of the results with team_id, total of pts, number of wins,
    number of draws and number of defeats such as:

    myArray = Array.new
    myArray << {:team_id=>1, :pts=>5, :w=>0, :d=>5, :l=>5}
    myArray << {:team_id=>2, :pts=>7, :w=>1, :d=>4, :l=>5}
    myArray << {:team_id=>3, :pts=>4, :w=>0, :d=>4, :l=>6}
    myArray << {:team_id=>4, :pts=>6, :w=>1, :d=>3, :l=>6}
    myArray << {:team_id=>5, :pts=>5, :w=>0, :d=>5, :l=>5}
    myArray << {:team_id=>6, :pts=>5, :w=>0, :d=>5, :l=>5}
    myArray << {:team_id=>8, :pts=>10, :w=>2, :d=>4, :l=>4}
    myArray << {:team_id=>9, :pts=>5, :w=>1, :d=>2, :l=>7}
    myArray << {:team_id=>10, :pts=>8, :w=>1, :d=>5, :l=>4}
    myArray << {:team_id=>11, :pts=>9, :w=>2, :d=>3, :l=>5}
    myArray << {:team_id=>12, :pts=>6, :w=>1, :d=>3, :l=>6}
    myArray << {:team_id=>13, :pts=>5, :w=>0, :d=>5, :l=>5}

    Now, from this array, I want to get the table.
    So what I want to do is to sort the array, first by total of pts, then
    by number of wins (if 2 teams have the same total of points I put first
    the team with more wins) and then by number of draws.

    That's how - logically - I would do it anyway. But that does not seem to
    be the Ruby way. Doing it that way gives me a complete mess.

    No worries, doing it the other way round works (first sort by draw, then
    wins, then pts)! Here is what I run:

    puts "Team, Pts, W, D, L"
    myArray = myArray.sort { |a,b| b[:d] <=> a[:d]
    }.sort { |a,b| b[:w] <=> a[:w]
    }.sort { |a,b| b[:pts] <=> a[:pts]
    }.each do |row|
    puts "#{row[:team_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},
    #{row[:l]}"
    end

    Did I say it works? Well almost! Here is my output:

    Team, Pts, W, D, L
    8, 10, 2, 4, 4
    11, 9, 2, 3, 5
    10, 8, 1, 5, 4
    2, 7, 1, 4, 5
    4, 6, 1, 3, 6
    12, 6, 1, 3, 6
    16, 5, 0, 5, 5
    1, 5, 0, 5, 5
    9, 5, 1, 2, 7
    5, 5, 0, 5, 5
    6, 5, 0, 5, 5
    3, 4, 0, 4, 6

    Good news, we kept the correct team_is with the correct result.
    But can anyone explain me what on earth is the line 9, 5, 1, 2, 7 doing
    there in the middle?

    Thanks for your help!

    --
    Posted via http://www.ruby-forum.com/.
     
    Laurent Colloud, Aug 14, 2006
    #1
    1. Advertising

  2. On Aug 14, 2006, at 4:04 PM, Laurent Colloud wrote:

    > Hi,
    >
    > Here my - strange - problem.
    >
    > To explain it, let's take the example of football. I construct an
    > array
    > of hashes of the results with team_id, total of pts, number of wins,
    > number of draws and number of defeats such as:
    >
    > myArray = Array.new
    > myArray << {:team_id=>1, :pts=>5, :w=>0, :d=>5, :l=>5}
    > myArray << {:team_id=>2, :pts=>7, :w=>1, :d=>4, :l=>5}
    > myArray << {:team_id=>3, :pts=>4, :w=>0, :d=>4, :l=>6}
    > myArray << {:team_id=>4, :pts=>6, :w=>1, :d=>3, :l=>6}
    > myArray << {:team_id=>5, :pts=>5, :w=>0, :d=>5, :l=>5}
    > myArray << {:team_id=>6, :pts=>5, :w=>0, :d=>5, :l=>5}
    > myArray << {:team_id=>8, :pts=>10, :w=>2, :d=>4, :l=>4}
    > myArray << {:team_id=>9, :pts=>5, :w=>1, :d=>2, :l=>7}
    > myArray << {:team_id=>10, :pts=>8, :w=>1, :d=>5, :l=>4}
    > myArray << {:team_id=>11, :pts=>9, :w=>2, :d=>3, :l=>5}
    > myArray << {:team_id=>12, :pts=>6, :w=>1, :d=>3, :l=>6}
    > myArray << {:team_id=>13, :pts=>5, :w=>0, :d=>5, :l=>5}
    >
    > Now, from this array, I want to get the table.
    > So what I want to do is to sort the array, first by total of pts,
    > then
    > by number of wins (if 2 teams have the same total of points I put
    > first
    > the team with more wins) and then by number of draws.


    Does this do what you were after?

    $ irb -r pp
    >> pp myArray.sort_by { |team| [team[:pts], team[:w], team

    [:d]] }.reverse
    [{:l=>4, :team_id=>8, :pts=>10, :w=>2, :d=>4},
    {:l=>5, :team_id=>11, :pts=>9, :w=>2, :d=>3},
    {:l=>4, :team_id=>10, :pts=>8, :w=>1, :d=>5},
    {:l=>5, :team_id=>2, :pts=>7, :w=>1, :d=>4},
    {:l=>6, :team_id=>12, :pts=>6, :w=>1, :d=>3},
    {:l=>6, :team_id=>4, :pts=>6, :w=>1, :d=>3},
    {:l=>7, :team_id=>9, :pts=>5, :w=>1, :d=>2},
    {:l=>5, :team_id=>13, :pts=>5, :w=>0, :d=>5},
    {:l=>5, :team_id=>6, :pts=>5, :w=>0, :d=>5},
    {:l=>5, :team_id=>5, :pts=>5, :w=>0, :d=>5},
    {:l=>5, :team_id=>1, :pts=>5, :w=>0, :d=>5},
    {:l=>6, :team_id=>3, :pts=>4, :w=>0, :d=>4}]
    => nil

    Hope that helps.

    James Edward Gray II
     
    James Edward Gray II, Aug 14, 2006
    #2
    1. Advertising

  3. > Hi,
    >=20
    > Here my - strange - problem.
    >=20
    > To explain it, let's take the example of football. I=20
    > construct an array=20
    > of hashes of the results with team_id, total of pts, number of wins,=20
    > number of draws and number of defeats such as:
    >=20
    > myArray =3D Array.new
    > myArray << {:team_id=3D>1, :pts=3D>5, :w=3D>0, :d=3D>5, :l=3D>5}
    > myArray << {:team_id=3D>2, :pts=3D>7, :w=3D>1, :d=3D>4, :l=3D>5}
    > myArray << {:team_id=3D>3, :pts=3D>4, :w=3D>0, :d=3D>4, :l=3D>6}
    > myArray << {:team_id=3D>4, :pts=3D>6, :w=3D>1, :d=3D>3, :l=3D>6}
    > myArray << {:team_id=3D>5, :pts=3D>5, :w=3D>0, :d=3D>5, :l=3D>5}
    > myArray << {:team_id=3D>6, :pts=3D>5, :w=3D>0, :d=3D>5, :l=3D>5}
    > myArray << {:team_id=3D>8, :pts=3D>10, :w=3D>2, :d=3D>4, :l=3D>4}
    > myArray << {:team_id=3D>9, :pts=3D>5, :w=3D>1, :d=3D>2, :l=3D>7}
    > myArray << {:team_id=3D>10, :pts=3D>8, :w=3D>1, :d=3D>5, :l=3D>4}
    > myArray << {:team_id=3D>11, :pts=3D>9, :w=3D>2, :d=3D>3, :l=3D>5}
    > myArray << {:team_id=3D>12, :pts=3D>6, :w=3D>1, :d=3D>3, :l=3D>6}
    > myArray << {:team_id=3D>13, :pts=3D>5, :w=3D>0, :d=3D>5, :l=3D>5}
    >=20
    > Now, from this array, I want to get the table.
    > So what I want to do is to sort the array, first by total of=20
    > pts, then=20
    > by number of wins (if 2 teams have the same total of points I=20
    > put first=20
    > the team with more wins) and then by number of draws.
    >=20
    > That's how - logically - I would do it anyway. But that does=20
    > not seem to=20
    > be the Ruby way. Doing it that way gives me a complete mess.
    >=20
    > No worries, doing it the other way round works (first sort by=20
    > draw, then=20
    > wins, then pts)! Here is what I run:
    >=20
    > puts "Team, Pts, W, D, L"
    > myArray =3D myArray.sort { |a,b| b[:d] <=3D> a[:d]
    > }.sort { |a,b| b[:w] <=3D> a[:w]
    > }.sort { |a,b| b[:pts] <=3D> a[:pts]
    > }.each do |row|
    > puts "#{row[:team_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},=20
    > #{row[:l]}"
    > end
    >=20
    > Did I say it works? Well almost! Here is my output:
    >=20
    > Team, Pts, W, D, L
    > 8, 10, 2, 4, 4
    > 11, 9, 2, 3, 5
    > 10, 8, 1, 5, 4
    > 2, 7, 1, 4, 5
    > 4, 6, 1, 3, 6
    > 12, 6, 1, 3, 6
    > 16, 5, 0, 5, 5
    > 1, 5, 0, 5, 5
    > 9, 5, 1, 2, 7
    > 5, 5, 0, 5, 5
    > 6, 5, 0, 5, 5
    > 3, 4, 0, 4, 6
    >=20
    > Good news, we kept the correct team_is with the correct result.
    > But can anyone explain me what on earth is the line 9, 5, 1,=20
    > 2, 7 doing=20
    > there in the middle?
    >=20
    > Thanks for your help!


    Each of your sort's just re-arranges the array by the new criteria,
    completely unrelated to previous sorts. Why not simply to create an
    appropriate criteria for a single sort:

    [ DISCLAIMER: Untested code bellow ]

    myArray =3D myArray.sort { |b, a|=20
    diff =3D 0
    [ :pts, :w, :d ].each do |_criteria|
    diff =3D a[_criteria] - b[_criteria]
    break unless diff.zero?
    end
    diff
    }

    Or even cooler ;-)

    myArray =3D myArray.sort { |a, b|
    [ :pts, :w, :d ].inject(0) { |_m, _c|
    _m =3D=3D 0 ? b[_c] - a[_c] : _m
    }
    }

    Gennady.

    >=20
    > --=20
    > Posted via http://www.ruby-forum.com/.
    >=20
    >=20
     
    Gennady Bystritsky, Aug 14, 2006
    #3
  4. Laurent Colloud

    Ken Bloom Guest

    On Tue, 15 Aug 2006 07:02:52 +0900, Gennady Bystritsky wrote:

    >> Hi,
    >>
    >> Here my - strange - problem.
    >>
    >> To explain it, let's take the example of football. I
    >> construct an array
    >> of hashes of the results with team_id, total of pts, number of wins,
    >> number of draws and number of defeats such as:
    >>
    >> myArray = Array.new
    >> myArray << {:team_id=>1, :pts=>5, :w=>0, :d=>5, :l=>5}
    >> myArray << {:team_id=>2, :pts=>7, :w=>1, :d=>4, :l=>5}
    >> myArray << {:team_id=>3, :pts=>4, :w=>0, :d=>4, :l=>6}
    >> myArray << {:team_id=>4, :pts=>6, :w=>1, :d=>3, :l=>6}
    >> myArray << {:team_id=>5, :pts=>5, :w=>0, :d=>5, :l=>5}
    >> myArray << {:team_id=>6, :pts=>5, :w=>0, :d=>5, :l=>5}
    >> myArray << {:team_id=>8, :pts=>10, :w=>2, :d=>4, :l=>4}
    >> myArray << {:team_id=>9, :pts=>5, :w=>1, :d=>2, :l=>7}
    >> myArray << {:team_id=>10, :pts=>8, :w=>1, :d=>5, :l=>4}
    >> myArray << {:team_id=>11, :pts=>9, :w=>2, :d=>3, :l=>5}
    >> myArray << {:team_id=>12, :pts=>6, :w=>1, :d=>3, :l=>6}
    >> myArray << {:team_id=>13, :pts=>5, :w=>0, :d=>5, :l=>5}
    >>
    >> Now, from this array, I want to get the table.
    >> So what I want to do is to sort the array, first by total of
    >> pts, then
    >> by number of wins (if 2 teams have the same total of points I
    >> put first
    >> the team with more wins) and then by number of draws.
    >>
    >> That's how - logically - I would do it anyway. But that does
    >> not seem to
    >> be the Ruby way. Doing it that way gives me a complete mess.
    >>
    >> No worries, doing it the other way round works (first sort by
    >> draw, then
    >> wins, then pts)! Here is what I run:
    >>
    >> puts "Team, Pts, W, D, L"
    >> myArray = myArray.sort { |a,b| b[:d] <=> a[:d]
    >> }.sort { |a,b| b[:w] <=> a[:w]
    >> }.sort { |a,b| b[:pts] <=> a[:pts]
    >> }.each do |row|
    >> puts "#{row[:team_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},
    >> #{row[:l]}"
    >> end
    >>
    >> Did I say it works? Well almost! Here is my output:
    >>
    >> Team, Pts, W, D, L
    >> 8, 10, 2, 4, 4
    >> 11, 9, 2, 3, 5
    >> 10, 8, 1, 5, 4
    >> 2, 7, 1, 4, 5
    >> 4, 6, 1, 3, 6
    >> 12, 6, 1, 3, 6
    >> 16, 5, 0, 5, 5
    >> 1, 5, 0, 5, 5
    >> 9, 5, 1, 2, 7
    >> 5, 5, 0, 5, 5
    >> 6, 5, 0, 5, 5
    >> 3, 4, 0, 4, 6
    >>
    >> Good news, we kept the correct team_is with the correct result.
    >> But can anyone explain me what on earth is the line 9, 5, 1,
    >> 2, 7 doing
    >> there in the middle?
    >>
    >> Thanks for your help!

    >
    > Each of your sort's just re-arranges the array by the new criteria,
    > completely unrelated to previous sorts. Why not simply to create an
    > appropriate criteria for a single sort:


    Not of .sort is a stable sort. Then it will preserve the order of items
    that are equal according to the current comparison. You would sort from
    least significant to most significant exactly as he has done here.

    From the results though, I conclude that .sort isn't a stable sort.

    Looking through the C code, I see that .sort is a quicksort.

    http://en.wikipedia.org/wiki/Sorting_algorithm#List_of_sorting_algorithms

    --Ken

    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
     
    Ken Bloom, Aug 15, 2006
    #4
  5. Hi,=20

    > From: Laurent Colloud
    > Sent: Monday, August 14, 2006 11:05 PM
    >=20
    > Hi,
    >=20
    > Here my - strange - problem.
    >=20
    > To explain it, let's take the example of football. I=20
    > construct an array=20
    > of hashes of the results with team_id, total of pts, number of wins,=20
    > number of draws and number of defeats such as:
    > [...]
    >=20
    > puts "Team, Pts, W, D, L"
    > myArray =3D myArray.sort { |a,b| b[:d] <=3D> a[:d]
    > }.sort { |a,b| b[:w] <=3D> a[:w]
    > }.sort { |a,b| b[:pts] <=3D> a[:pts]
    > }.each do |row|
    > puts "#{row[:team_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},=20
    > #{row[:l]}"
    > end


    try

    myArray.sort {|a,b|=20
    (b[:d] <=3D> a[:d]).nonzero? ||
    (b[:w] <=3D> a[:w]).nonzero? ||
    (b[:pts] <=3D> a[:pts])}

    or (even better):

    myArray.sort_by {|a| [a[:d], a[:w], a[:pts]]}

    cheers

    Simon
     
    Kroeger, Simon (ext), Aug 15, 2006
    #5
  6. Hi guys,

    First thank you very much for helping me out on that one.

    So here are the results of all your suggestions. And the winner is...

    -----------------------------------
    THEY WORK
    -----------------------------------

    By James:

    myArray.sort_by { |team| [team[:pts], team[:w], team[:d]] }.reverse


    By Gennady (nice one!):

    myArray.sort { |b, a|
    diff = 0
    [ :pts, :w, :d ].each do |_criteria|
    diff = a[_criteria] - b[_criteria]
    break unless diff.zero?
    end
    diff
    }


    By Gennady (nice one again!):

    myArray.sort { |a, b|
    [ :pts, :w, :d ].inject(0) { |_m, _c|
    _m == 0 ? b[_c] - a[_c] : _m
    }
    }

    By Russell (with a few synthax corrections):

    myArray.sort {|a,b|
    if b[:pts] == a[:pts]
    if b[:w] == a[:w]
    b[:d] <=> a[:d]
    else
    b[:w] <=> a[:w]
    end
    else
    b[:pts] <=> a[:pts]
    end
    }

    -----------------------------------------------
    THEY DON'T WORK
    -----------------------------------------------

    myArray.sort_by {|a| [a[:d], a[:w], a[:pts]]}

    -> Sorted by draws :)d) ASC (and then :w ASC, :pts ASC) but I could
    deduct James'solution from the output of this so thanks anyway :).

    myArray.sort {|a,b|
    (b[:d] <=> a[:d]).nonzero? ||
    (b[:w] <=> a[:w]).nonzero? ||
    (b[:pts] <=> a[:pts])}

    ->Sorted by :d DESC (and then :w DESC, :pts DESC)

    So thanks again very much.

    Pfiouuu, I've learned a lot of Ruby in just one night! :)


    --
    Posted via http://www.ruby-forum.com/.
     
    Laurent Colloud, Aug 15, 2006
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ben Holness

    Hashes of Hashes via subs

    Ben Holness, Oct 5, 2003, in forum: Perl
    Replies:
    8
    Views:
    576
    Ben Holness
    Oct 8, 2003
  2. kazaam
    Replies:
    12
    Views:
    281
    Matthias Wächter
    Sep 13, 2007
  3. Adgar Marks

    sort Array of Hashes

    Adgar Marks, Jul 27, 2008, in forum: Ruby
    Replies:
    1
    Views:
    119
    David A. Black
    Jul 27, 2008
  4. Matt Brooks
    Replies:
    16
    Views:
    235
    Matt Brooks
    Sep 16, 2009
  5. Tim O'Donovan

    Hash of hashes, of hashes, of arrays of hashes

    Tim O'Donovan, Oct 27, 2005, in forum: Perl Misc
    Replies:
    5
    Views:
    221
Loading...

Share This Page