3

I have the following 2 arrays:

fields = ["name", "team", "number", "name", "team", "number", "name", "team", "number"]
values = ["Patrick Ewing", "New York Knicks", 33, "Rik Smits", "Indiana Pacers", 45, "Bill Russell", "Boston Celtics", 6]

The fields will always be only name, team and number, but the number of values (i.e., number of players) will vary each time.

I want to create new arrays for each field type, so that I will get the following:

names = ["Patrick Ewing", "Rik Smits", "Bill Russell"]
teams = ["New York Knicks", "Indiana Pacers", "Boston Celtics"]
numbers = [33,45,6]

What's a good way to do this? I have tried the following, but want to know if there are other solutions that would perform better with larger arrays (up to 300). Or would the difference be negligible?

names = values.values_at(*(fields.each_index.select{ |i| fields[i] == "name"}))
teams = values.values_at(*(fields.each_index.select{ |i| fields[i] == "team"}))
numbers = values.values_at(*(fields.each_index.select{ |i| fields[i] == "number"}))
8
  • 4
    What have you tried? We prefer that this isn't just a code-writing service. That said, this looks like a job for zip. Commented Apr 29, 2016 at 20:50
  • 2
    Is the fields list always the same? This is an odd way to organize data on the way in, so if you could adjust that it might require less processing. Commented Apr 29, 2016 at 20:58
  • @DaveSchweisguth I tried something and posted it below. I understand why you feel that way, but I'm a newbie and like learning by seeing what other people would do while I try out a solution myself. Commented Apr 29, 2016 at 21:20
  • @tadman Yes, fields always the same. I do an XML query on a system which returns a bunch of result elements with the fields and values as attributes, so I put them into fields and values arrays first and go from there. Commented Apr 29, 2016 at 21:28
  • 1
    "but I'm a newbie and like learning by seeing what other people would do while I try out a solution myself." I'd suggest reading "How to Ask", codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question and catb.org/esr/faqs/smart-questions.html. Commented Apr 29, 2016 at 21:30

5 Answers 5

5

Here is another/concise way:

> name, team, number = values.each_slice(3).to_a.transpose
=> name
> ["Patrick Ewing", "Rik Smits", "Bill Russell"]
=> team
> ["New York Knicks", "Indiana Pacers", "Boston Celtics"]
=> number
> [33, 45, 6]

The beauty of Ruby's Array#transpose comes into play, particulary for your problem.

Sign up to request clarification or add additional context in comments.

5 Comments

That is a pretty clever one-liner. Nice.
What's the difference between using name, team, number = values.each_slice(3).to_a.transpose and name, team, number = values.each_slice(3).each_with_object([]) { |_, acc| acc << _ }.transpose ?
@massaskillz, since both return the same array, the simpler of the two--the former--is preferable. vee, you should consider incorporating massaskillz' suggestion.
@CarySwoveland, not sure if you saw edit made earlier today but I did include massaskillz' suggestion. Please check the last few lines in the answer. Thanks for the suggestion.
Sorry, I missed that. If you were writing a book you would consider suggested changes and adopt those you considered to be improvements, replacing what you had before.Consider adopting that model. If you wish, you can give credit in the revised answer or (my preference) in a comment. It seems to me that we should strive to provide the best answers, regardless of who contributes what.
3

You can do it in one line just use group_by and with_index:

grouped = values.group_by.with_index {|_, i| fields[i] }
#=> {"name"=>["Patrick Ewing", "Rik Smits", "Bill Russell"],'
    "team"=>["New York Knicks", "Indiana Pacers", "Boston Celtics"],            
    "number"=>[33, 45, 6]}

Then you can simple produce your arrays from this hash:

name = grouped["name"]
#=> ["Patrick Ewing", "Rik Smits", "Bill Russell"]

Or just:

names, teams, numbers = grouped.values

Comments

2

If the input is consistently in groups of three you can use each_slice:

values = ["Patrick Ewing", "New York Knicks", 33, "Rik Smits", "Indiana Pacers", 45, "Bill Russell", "Boston Celtics", 6]

names = [ ]
teams = [ ]
numbers = [ ]

values.each_slice(3) do |name, team, number|
  names << name
  teams << team
  numbers << number
end

That's not the most optimal way of organizing things. A more Ruby-oriented style would be:

keys = [ :name, :team, :number ]

players = values.each_slice(3).collect do |set|
  Hash[keys.zip(set)]
end
#=> [{:name=>"Patrick Ewing", :team=>"New York Knicks", :number=>33}, {:name=>"Rik Smits", :team=>"Indiana Pacers", :number=>45}, {:name=>"Bill Russell", :team=>"Boston Celtics", :number=>6}]

That gives you easy access to each record without having to cross-reference multiple arrays. Even if you're displaying this in a table and need the three things separately you can still do this to select out an individual value:

keys.each do |key|
  players.each do |player|
    print '%-20s' % player[key]
  end

  puts
end

That gives you output like:

Patrick Ewing       Rik Smits           Bill Russell        
New York Knicks     Indiana Pacers      Boston Celtics      
33                  45                  6                   

Comments

1

Yet another way: using Facets Enumerable#map_by:

require 'facets'
names, teams, numbers = fields.zip(values).
  map_by { |field, value| [field, value] }.
  values_at("name", "team", "number")

Comments

1

Here is one way to group the array values by their "type":

fields = ["name", "team", "number", "name", "team", "number", "name", "team", "number"]
values = ["Patrick Ewing", "New York Knicks", 33, "Rik Smits", "Indiana Pacers", 45, "Bill Russell", "Boston Celtics", 6]

field_values = fields.zip(values)

names = field_values.select { |f, v| f == "name" }.map(&:last)
# => ["Patrick Ewing", "Rik Smits", "Bill Russell"]

teams = field_values.select { |f, v| f == "team" }.map(&:last)
# => ["New York Knicks", "Indiana Pacers", "Boston Celtics"]

numbers = field_values.select { |f, v| f == "number" }.map(&:last)
# => [33, 45, 6]

1 Comment

select { |f,v| f == 'name' } might be more concise here.

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.