0

A similar question has been answered here However I'd like to know how I can add up/group the numbers from one array based on the duplicate values of another array.

test_names = ["TEST1", "TEST1", "TEST2", "TEST3", "TEST2", "TEST4", "TEST4", "TEST4"]
numbers = ["5", "4", "3", "2", "9", "7", "6", "1"]

The ideal result I'd like to get is a hash or an array with:

{"TEST1" => 9, "TEST2" => 12, "TEST3" => 2, "TEST4" => 14}
Community
  • 1
  • 1
sylvian
  • 709
  • 2
  • 9
  • 19

3 Answers3

2

Another way I found you can do:

test_names.zip(numbers).each_with_object(Hash.new(0)) {
    |arr, hsh| hsh[arr[0]] += arr[1].to_i }
squiguy
  • 32,370
  • 6
  • 56
  • 63
  • I suggest you disambiguate (a useful word, I suppose, but I hate it) `arr` and write `...|(name,nbr),hsh| hsh[name] += nbr.to_i` }. – Cary Swoveland Jun 04 '14 at 03:48
  • @CarySwoveland I was unaware that you could unpack like that. But that looks neater, good tip. – squiguy Jun 04 '14 at 18:33
1

You can do it like this:

my_hash = Hash.new(0)
test_names.each_with_index {|name, index| my_hash[name] += numbers[index].to_i}
my_hash
#=> {"TEST1"=>9, "TEST2"=>12, "TEST3"=>2, "TEST4"=>14}
Mike H-R
  • 7,726
  • 5
  • 43
  • 65
  • in my real code the numbers are floats so after they all add up in my_hash[name] I get a long list of digits after the floating point. Is there a way to nicely add .round(3) somewhere in the same line of code or do I have to again iterate over the hash and round_up each number individually? – sylvian Jun 05 '14 at 22:19
  • sure, that's pretty basic, you can see the part where you're adding the numbers, so instead of just adding `numbers[index].to_i` just add `numbers[index].to_f.round(3)` so that they stay floats and you round them. You should be able to see that from the code. – Mike H-R Jun 05 '14 at 23:11
  • yeah, I get that, but I was talking about the final result, so even if I do numbers[index].round(3) for each number the final result can still have a lot of digits after the floating point. I want my_hash[name] to be rounded to 3 – sylvian Jun 05 '14 at 23:56
  • No, it is impossible to add two numbers that have 3 decimal places together and get a number with 4 decimal places. – Mike H-R Jun 06 '14 at 00:11
  • However, the answer doing it that way would have a larger inaccuracy than just looping afterwards and rounding which is what I would do. (the inaccuracy `5*10^-4*n > 5*10^-4` where n is the number of terms in the summation) – Mike H-R Jun 06 '14 at 00:14
1

I wish to follow @squidguy's example and use Enumerable#zip, but with a different twist:

{}.tap { |h| test_names.zip(numbers.map(&:to_i)) { |a|
  h.update([a].to_h) { |_,o,n| o+n } } } 
  #=> {"TEST1"=>9, "TEST2"=>12, "TEST3"=>2, "TEST4"=>14}
  • Object#tap is here just a substitute for Enumerable#each_with_object or for having h={} initially and a last line with just h.

  • I'm using the form of Hash#update (aka merge!) that takes a block for determining the merged value for each key that is present in both the original hash (h) and the hash being merged ([a].to_h). There are three block variables, the shared key (which we don't use here, so I've replaced it with the placeholder _), and the values for that key for the original hash (o) and for the hash being merged (n).

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100