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.

3
  • I'm curious what you think -v.size is doing. Commented Jun 13, 2020 at 18:42
  • It's sorting the counter by max value first. Commented Jun 13, 2020 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. Commented Jun 13, 2020 at 19:22

4 Answers 4

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"] 
Sign up to request clarification or add additional context in comments.

6 Comments

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"
@theTinMan (aka 'Sn'), I initially had h.sort_by(&:last).reverse for speed (recalling your previous remarks about performance), but thought -count read better.
Sorry I used Ruby 2.6.5 and tally required 2.7. ;-) Any workaround ?
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 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.
Thanks Cary. It works like a charm. I have now the good sorting and the number of occurrences in the same array.
|
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.

1 Comment

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. ;)
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

2 Comments

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. ;)
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.
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".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.