1

I'm currently sorting an array of arrays (of numbers) in Ruby with this code:

grapes_sorted = vintages_grapes.group_by(&:itself).sort_by do |k, v| -v.size end.map(&:first) 

It works very well. However, I want to store the counters corresponding to each sort in another array.

I tried:

grapes_sorted_counters = []
grapes_sorted = vintages_grapes.group_by(&:itself).sort_by do |k, v| 
                        -v.size 
                        grapes_sorted_counters << v.size 
                    end.map(&:first) 

It store the counters, however, the sort is broken, not ordered like it should be. I suppose v.size (and not -v.size) is the cause of the problem in the block.

How can I store the number of occurrences properly in grapes_sorted_counters[] ? Thanks.

alex.bour
  • 2,842
  • 9
  • 40
  • 66
  • I'm curious what you think `-v.size` is doing. – the Tin Man Jun 13 '20 at 18:42
  • It's sorting the counter by max value first. – alex.bour Jun 13 '20 at 18:57
  • Not really. `sort_by` sorts using the returned value of the block as its ordering value, which is `v.size`. `-v.size` isn't the last thing the block sees. Actually, for this use `sort_by` is the wrong method, you should use `sort` as it'd be faster. I'll explain in an answer. – the Tin Man Jun 13 '20 at 19:22

4 Answers4

3

I believe the problem with your code has been identified, so I will suggest an alternative approach.

grapes = %w|red blue red green yellow red blue blue green red|
  #> ["red", "blue", "red", "green", "yellow", "red", "blue",
  #   "blue", "green", "red"]

grapes.tally.sort_by { |_,count| -count }.map(&:first)
  #=> ["red", "blue", "green", "yellow"]

See Enumerable#tally.

The steps are as follows.

h = grapes.tally
  #=> {"red"=>4, "blue"=>3, "green"=>2, "yellow"=>1} 
a = h.sort_by { |_,count| -count }
  #=> [["red", 4], ["blue", 3], ["green", 2], ["yellow", 1]] 
a.map(&:first)
  #=> ["red", "blue", "green", "yellow"] 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Wouldn't `reverse` be faster than using `h.sort_by` with a negative index? It would force using `to_a` though. "[Sort an array in descending order](https://stackoverflow.com/q/2642182/128421)" – the Tin Man Jun 13 '20 at 18:47
  • @theTinMan (aka 'Sn'), I initially had `h.sort_by(&:last).reverse` for speed (recalling your previous remarks about performance), but thought `-count` read better. – Cary Swoveland Jun 13 '20 at 18:52
  • Sorry I used Ruby 2.6.5 and tally required 2.7. ;-) Any workaround ? – alex.bour Jun 13 '20 at 18:56
  • 1
    Alex, you can use `grapes.each_with_object(Hash.new(0)) { |grape,h| h[grape] += 1 }.sort_by { |_,count| -count }.map(&:first)`. This uses the form of [Hash::new](https://ruby-doc.org/core-2.7.0/Hash.html#method-c-new) that takes an argument referred to as the *default value* (the value returned by `h[k]` if the hash `h` does not have a key `k`). Actually, that was my original answer before @steenstag reminded me that I could use `tally`. – Cary Swoveland Jun 13 '20 at 18:59
  • @theTinMan, `max_by(grapes.size, &:last)` is another option. ¯\\_(ツ)_/¯ – Cary Swoveland Jun 13 '20 at 19:12
  • 1
    Thanks Cary. It works like a charm. I have now the good sorting and the number of occurrences in the same array. – alex.bour Jun 13 '20 at 19:50
1

it's this part that's messing you up:

sort_by do |k, v| 
  -v.size 
  grapes_sorted_counters << v.size 
end

You are trying to sort by -v.size. But the return value of the block is grapes_sorted_counters << v.size.

So just switch around the order of those lines.

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • I found your idea logical but after a test it's not working. The sorting is good but the number of occurences found is reversed. It's the contrary of what I wrote in my post. ;) – alex.bour Jun 13 '20 at 18:52
0

I think you have a bug in your sorting anyway.

I'm currently sorting an array of arrays (of numbers) in Ruby with this code:

I assume the input value is something like this

vintages_grapes = [[3,2], [3,2,1], [3]]

So in the first step you do a group_by

vintages_grapes.group_by(&:itself)
# -> {[3, 2]=>[[3, 2]], [3, 2, 1]=>[[3, 2, 1]], [3]=>[[3]]}

You can see that the values are all nested in another array [] (they all have size 1).

sort_by do |key, value| 
  -value.size # size 1 for all values
end

The group_by shouldn't be necessary, you can just do

result = []
vintages_grapes.sort_by do |array| 
  result << array.size
  result.sort!
  -array.size
end
  • Hi Christian. This code seems to only count the numbers in each sub-array. It does'nt count the number of occurences of each sub-array. ;) – alex.bour Jun 13 '20 at 18:51
  • This is doing what is requested in the question (sorting an array of arrays). Please update your question and also provide the input data otherwise it's just guessing what is requested. – Christian Bruckmayer Jun 13 '20 at 21:42
0

I'm curious what you think -v.size is doing. – the Tin Man 39 mins ago
It's sorting the counter by max value first.

That's not how sort or sort_by work. Both rely on the value returned by the block to indicate how to order the values.

This example is more like your code, where you try to negate the value of size but then return the array. This will result in an ascending sort:

foo = [1, 3, 2, 4]
foo.sort_by { |i| 
  -i
   i
 }
 # => [1, 2, 3, 4]

If you return the negated value to the block, then the results use a descending order:

 foo.sort_by { |i| 
  i
  -i
 }
 # => [4, 3, 2, 1]

Your code is doing this:

 bar = []
 foo.sort_by { |i|
  -i
  bar << i
}
# => [1, 3, 2, 4]

which results in bar having the original order as it was filled in the same order as they exist in foo, and then you pass that to map. And, assigning inside the sort block will always do that. You'd want to do it after that block has closed and Ruby has returned the ordered values.

All that said, sort_by is the wrong method to use, you should use sort instead. Why is explained in Ruby's sort_by documentation, so look there for more information.

Also, reversing the order is best done using the reverse method, not by negating the value. See "How to sort an array in descending order in Ruby".

the Tin Man
  • 158,662
  • 42
  • 215
  • 303