victoroux
victoroux

Reputation: 286

Consolidating Multidimensional Array in Ruby

I have an array of "votes" set up as [ID, Rating] inside another array

[["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "4"],
 ["1250", "5"],
 ["1250", "5"],
 ["1252", "2"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "3"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "4"],
 ["1254", "5"],
 ["1254", "4"],
 ["1254", "4"],
 ["1257", "5"],
 ["1257", "5"],
 ["1257", "4"],
 ["1257", "5"],
 ...]

There are multiples of x that I want to merge and keep all y's pertaining to that x accessible. Basically I have to average all votes (y) for a particular id (x) and am unsure how to do that. In addition these votes (y) have to be "weighted" by different amounts later, so I think keeping access to them would be helpful down the road.

Frankly I don't even know what this is called so don't know what to look up :/ I tried merging, pushing y's onto array[x], and some complicated 'for unique x do |y|'. Just stumped on how to handle this problem.

End goal could be something like this:

[["1250", ["5", "5", "5", "4", "5", "5"]],
 ["1252", ["2", "5", "4", "3", "5", "4", "4"]],
 ["1254", ["5", "4", "4"]],
 ["1257", ["5", "5", "4", "5"]],
...]

Upvotes: 1

Views: 90

Answers (6)

Cary Swoveland
Cary Swoveland

Reputation: 110675

Assuming your array is ordered by the first element of each (two-element array) element of votes, as it is in the example, you could make use of Enumerable#slice_when which we were given in v.2.2:

votes.slice_when { |(v1,_),(v2,_)| v1 != v2 }
     .each_with_object({}) { |a,h| h[a.first.first] = a.map(&:last) }
  #=> {"1250"=>["5", "5", "5", "5", "4", "5", "5"],
  #    "1252"=>["2", "5", "4", "3", "5", "4", "4"],
  #    "1254"=>["5", "4", "4"],
  #    "1257"=>["5", "5", "4", "5"]} 

where:

votes =
[["1250", "5"],
 ["1250", "5"],
 ...
 ["1257", "4"],
 ["1257", "5"]]

Upvotes: 1

sschmeck
sschmeck

Reputation: 7685

One short solution.

my_array = [ .... ]
my_array.group_by(&:first).map { |k,v| [k, v.map { |_,y| [y] }.reduce(:+)] }

It uses the Enumerable methods group_by(), map(), reduce().

EDIT: Additional notes

With a small adaption the solution above also accumulates the values of the ys. I guessed it was the primary intension of the question but wasn't.

my_array.group_by(&:first).map { |k,v| [k, v.map { |_,y| y.to_i }.reduce(:+)] }

Upvotes: 3

steenslag
steenslag

Reputation: 80065

[["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "4"],
 ["1250", "5"],
 ["1250", "5"],
 ["1252", "2"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "3"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "4"],
 ["1254", "5"],
 ["1254", "4"],
 ["1254", "4"],
 ["1257", "5"],
 ["1257", "5"],
 ["1257", "4"],
 ["1257", "5"]]

hsh = Hash.new{|h,k| h[k] = []} 
# hsh stores the key with an empty array if it does not "know" a key
votes.each_with_object(hsh){|(id, vote), h| h[id] << vote}
# add vote to the array when hsh "knows" the key.

p hsh
# =>{"1250"=>["5", "5", "5", "5", "4", "5", "5"], "1252"=>["2", "5", "4", "3", "5", "4", "4"]...}

Upvotes: 1

Joey Hammer
Joey Hammer

Reputation: 76

You could build a Hash where the ID is the key and the value could be an array of the ratings:

table = Hash.new()
list.each do |id_rating_pair|
  id = id_rating_pair[0]to_sym
  rating = id_rating_pair[1].to_i

  if !table.has_key?( id )
    table[id] = Array.new()
  end

  table[id].push( rating )
end

Now with this table you can perform your statistics.

I know it's not short, but it is clear and can be revised to suit your needs.

Upvotes: 0

Horacio
Horacio

Reputation: 2965

Maybe you can do that with a hash.

votes= [["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "5"],
 ["1250", "4"],
 ["1250", "5"],
 ["1250", "5"],
 ["1252", "2"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "3"],
 ["1252", "5"],
 ["1252", "4"],
 ["1252", "4"],
 ["1254", "5"],
 ["1254", "4"],
 ["1254", "4"],
 ["1257", "5"],
 ["1257", "5"],
 ["1257", "4"],
 ["1257", "5"]]


resume={}

votes.each do |vote|
  resume[vote[0]]=[] unless resume.include?(vote[0])
  resume[vote[0]] << vote[1]
end

puts resume.to_s

and then you can do anything you want with that hash.

Upvotes: 1

spickermann
spickermann

Reputation: 106802

I would do something like this:

array.group_by(&:first).map { |k, v| [k, v.map(&:last)] }

Upvotes: 1

Related Questions