tags:

views:

235

answers:

3

I have the following array:

votes_array = [["2", "1"], ["2", "4"], ["4", "3"], ["3", "4"], ["1", "N"], ["3", "1"], ["1", "2"], ["4", "1"], ["0", "1"], ["0", "2"], ["1", "3"], ["1", "4"]]

And I want to create a new array (or hash) that stores the votes_array items by their first option so that the new array looks like this:

candidate_votes = [
                   {"id" => "0", "votes" => [["0", "1"],["0","2"]]}, 
                   {"id" => "1", "votes" => [["1", "N"],["1","2"],["1","3"],["1","4"]]},
                   etc,
                   etc]

The order of the votes within the 'votes' key is not important, just that all the votes get split into the relevant ids.

I've got it working with the following code:

first_preferences = votes_array.map(&:first)
valid_candidates = first_preferences.uniq
valid_candidates.each do |candidate|
   these_votes = votes_array.find_all { |i| i[0] == candidate }
   candidate_votes << {"id" => candidate, "votes" => these_votes}
end

But wondered if there's a more elegant, cleaner or Rubiyst way?

+1  A: 

Here's one that creates candidate_votes as a Hash. It's probably a little faster because you need to iterate votes_array only once:

candidate_votes = {}
votes_array.each do |id, vote|
  candidate_votes[id] ||= {"id" => id, "votes" => []}
  candidate_votes[id]["votes"] << [id, vote]
end

This will give results like this:

candidate_votes = {
                    "0" => {"votes" => [["0", "1"], ["0", "2"]], "id" => "0"},
                    ... etc ...
                  }
Pesto
Thanks Pesto, maybe I should add that the id may not be sequential - that should still work with your suggestion tho right? And is there anything 'wrong' with the way I've done it?
Les
My solution does not rely on the id's being sequential or the votes being in any particular order. Your answer is logically fine, but it's inefficient. You pass through `votes_array` to create another array of just the first characters. Then you pass through that array to generate the unique entries. Then you pass through *that* array, and for each item, you pass through `votes_array` yet again. It may not be an actual problem in your situation, but it bears mentioning. Mine goes through `votes_array` only once.
Pesto
A: 

I think you can organize the output a lot more concisely using something like this:

# Use Enumerable#inject to convert Array to Hash
results = votes_array.inject({ }) do |hash, vote|
  # Create hash entry for candidate if not defined (||=)
  hash[vote[0]] ||= { :votes => [ ] }

  # Add vote to candidate's array of votes
  hash[vote[0]][:votes] << vote[1]

  # Keep hash for next round of inject
  hash
end

# => {"0"=>{:votes=>["1", "2"]}, "1"=>{:votes=>["N", "2", "3", "4"]}, "2"=>{:votes=>["1", "4"]}, "3"=>{:votes=>["4", "1"]}, "4"=>{:votes=>["3", "1"]}}

As a note it's advantageous to use Symbols for Hash keys in Ruby since they are generally more efficient than Strings for that kind of application.

tadman
I thought about doing similar, but this doesn't produce output in the format requested.
Pesto
I was just trying to make "a more elegant, cleaner or Rubiyst way" kind of solution! :)
tadman
+2  A: 

With Ruby 1.8.7+, it's a one-liner:

candidate_votes = votes_array.group_by {|pair| pair[0]}

Pretty printing candidate_votes returns

{"0"=>[["0", "1"], ["0", "2"]],
 "1"=>[["1", "N"], ["1", "2"], ["1", "3"], ["1", "4"]],
 "2"=>[["2", "1"], ["2", "4"]],
 "3"=>[["3", "4"], ["3", "1"]],
 "4"=>[["4", "3"], ["4", "1"]]}
glenn jackman
Impressive feature. +1
Brent.Longborough