# Consecutive sort on Array of Hashes

**URL:** <https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299>\
**Category:** Ruby\
**Created:** [August 14, 2006, 11:04pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299 "2006-08-14T23:04:41Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 14, 2006, 11:04pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/1 "2006-08-14T23:04:41Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 1:17am UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/2 "2006-08-15T01:17:53Z")

</div>

Laurent Colloud wrote:

> 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

I suspect the problem is that sort does not keep the order of equal keys  
– this is standard depending on which algorithim used.

you need to do one sort with a more complex comparison routine:

{|a,b| if b[:pts] == a[:pts] then  
if b[:w] == a[:w] then  
b[:d] \<=\> a[:d]  
else  
b[:w] \<=\> a[:w]  
end  
b[:pts] \<=\> a[:pts]  
end  
}

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 2:20pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/3 "2006-08-15T14:20:20Z")

</div>

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

> myArray \<\< {:team\_id=\>1, :pts=\>5, :w=\>0, :d=\>5, :l=\>5}  
> 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 G. II

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 2:20pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/4 "2006-08-15T14:20:22Z")

</div>

> myArray \<\< {:team\_id=\>1, :pts=\>5, :w=\>0, :d=\>5, :l=\>5}  
> myArray \<\< {:team\_id=\>13, :pts=\>5, :w=\>0, :d=\>5, :l=\>5}  
> be the Ruby way. Doing it that way gives me a complete mess.  
> puts "#{row[:team\_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},  
> 4, 6, 1, 3, 6  
> 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:

[DISCLAIMER: Untested code bellow]

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

Or even cooler 😉

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

Gennady.

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 2:21pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/5 "2006-08-15T14:21:01Z")

</div>

Hi,

> number of draws and number of defeats such as:  
> […]
> 
> 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

try

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

or (even better):

myArray.sort\_by {|a| [a[:d], a[:w], a[:pts]]}

cheers

Simon

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 2:20pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/6 "2006-08-15T14:20:52Z")

</div>

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

> > myArray \<\< {:team\_id=\>1, :pts=\>5, :w=\>0, :d=\>5, :l=\>5}  
> > myArray \<\< {:team\_id=\>13, :pts=\>5, :w=\>0, :d=\>5, :l=\>5}  
> > be the Ruby way. Doing it that way gives me a complete mess.  
> > puts "#{row[:team\_id]}, #{row[:pts]}, #{row[:w]}, #{row[:d]},  
> > 4, 6, 1, 3, 6  
> > 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.

> **[Sorting algorithm](https://en.wikipedia.org/wiki/Sorting_algorithm#List_of_sorting_algorithms)**
>
> In computer science, a sorting algorithm is an algorithm that puts elements of a list into an order. The most frequently used orders are numerical order and lexicographical order, and either ascending or descending. Efficient sorting is important for optimizing the efficiency of other algorithms (such as search and merge algorithms) that require input data to be in sorted lists. Sorting is also often useful for canonicalizing data and for producing human-readable output.
> Formally, the output of ...

–Ken

---

<div class="post-metadata">

**Author:** ![fifoo](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/82dd89/32.png) [@fifoo](https://www.ruby-forum.com/u/fifoo)\
**Post date:** [August 15, 2006, 11:48pm UTC](https://www.ruby-forum.com/t/consecutive-sort-on-array-of-hashes/102299/7 "2006-08-15T23:48:35Z")

</div>

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! 🙂
