3

I'm trying to get a Ruby Array of Arrays and group it in order to count its values.

The Array has a month, and a boolean:

array = [["June", false], ["June", false], ["June", false], ["October", false]]

I will like to end with a new Array that let me know how many false vs trues in each month.

Example: (where the first item in the Array is month, the second is the count of false and the third is the count of true)

new_array = [["June", 3, 0]], ["October", 1, 0]]

5 Answers 5

3

Using Enumerable#group_by:

Array.group_by { |month, b| month }.map{ |month, xs|
  [month,
   xs.count {|_, b| !b},  # false
   xs.count {|_, b| b}]   # true
}
# => [["June", 3, 0], ["October", 1, 0]]
Sign up to request clarification or add additional context in comments.

4 Comments

Enumerable#group_by returns a Hash, not an Array.
@Jordan, That's why I chained the return value with map call. (and need further processing to count true, falses)
Ah, so you did. My bad.
Nice...hope to keep learning and understand 100% how you did that :)
1
array.map(&:dup) # we’ll mutate it
     .group_by(&:shift)
     .flat_map { |k, v| [k, v.partition { |e| e }.map(&:count)] }
#⇒ [ ['June', [0, 3] ], ['October', [0, 1] ] ] 

Comments

1

Enumerable#group_by is a useful tool, but in this case it's not the best one.

If we start with a Hash whose default value is [0, 0] we can do (almost) everything—look up the month (or get the default value of it's a new month) and add 1 to the appropriate index (0 for false and 1 for true)—in a single step:

array = [["June", false], ["June", false], ["June", false], ["October", false]]

hsh = Hash.new {|h,k| h[k] = [0,0] }
array.each {|mo, bool| hsh[mo][bool ? 1 : 0] += 1 }

p hsh.map(&:flatten)
# => [["June", 3, 0], ["October", 1, 0]]

2 Comments

This works as well. Why it is better that Enumerable#group_by?
With group_by we have to iterate over the data four times: Once to do the grouping, once to count the trues, once more for the falses, and then again to turn the Hash into an Array. With the above, we only have to iterate twice: Once to do the counting and once to turn it into an Array.
0

I think this would make a good use case for a reduce operation. You would do something like:

arr = [["June", false], ["June", false], ["June", false], ["October", false]]
logical_group = Hash.new{|h, k| h[k] = Hash.new(0) }

arr.reduce(logical_group) do |red, ele|
  red[ele.first][:false_count] += 1 unless ele.last
  red[ele.first][:true_count] += 1 if ele.last
  red
end.reduce([]) do |red, (key, value)|
  red << [key, value[:false_count], value[:true_count]]
  red
end

Comments

-3
Array.group_by {|(Array, boolean)| Array }.map {|Array, match| [Array, match.count] }

2 Comments

Did you try this code before you posted it? Are you aware that it doesn't work?
It throws the following error: formal argument cannot be a constant

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.