Clean way to push results of multiple methods into an array - ruby

I have the following Ruby code, which calls the same function multiple times with different arguments, and pushes the results into a common array.
people_relations = []
people.zip(people_addresses).map do |person, address|
people_relations.push(createRelation(person, address))
end
people.zip(people_ph_numbers).map do |person, phone_number|
people_relations.push(createRelation(person, phone_number))
end
people.zip(people_aliases).map do |person, _alias|
people_relations.push(createRelation(person, _alias))
end
def createRelation(e1, e2)
[true, false].sample ? CurrentRelation.new(e1, e2) : PastRelation.new(e1, e2)
end
This code works just fine, but I feel like this is not the idiomatic Ruby way of doing things, and can be improved by compressing the code into less lines or made to look cleaner.
Is there a better way to write the code that appears above?

You could create an array that contains all the people "attributes" you're going to use, and with Enumerable#each_with_object you can assign an initial array to fill with the result of each call to createRelation():
attributes = [people_addresses, people_ph_numbers, people_aliases]
relations = people.each_with_object([]).with_index do |(person, memo), index|
attributes.each do |attribute|
memo << createRelation(person, attribute[index])
end
end

I'd probably go with a transpose -> flat_map solution for this myself, for instance given:
def CreateRelation(person, relationship)
if [true, false].sample
"#{person} is currently related to #{relationship}"
else
"#{person} used to be related to #{relationship}"
end
end
people = ['Person 1', 'Person 2', 'Person 3']
addresses = ['Person 1 Address', 'Person 2 Address', 'Person 3 Address']
phone_numbers = ['Person 1 Phone', 'Person 2 Phone', 'Person 3 Phone']
aliases = ['Person 1 AKA', 'Person 2 AKA', 'Person 3 AKA']
We can stick those 4 arrays into a single array and then transpose them, so the first element of each ends up in an array with each other, the second in another, and the last in a third:
[people, addresses, phone_numbers, aliases].transpose # => [
# ["Person 1", "Person 1 Address", "Person 1 Phone", "Person 1 AKA"],
# ["Person 2", "Person 2 Address", "Person 2 Phone", "Person 2 AKA"],
# ["Person 3", "Person 3 Address", "Person 3 Phone", "Person 3 AKA"]]
and then you can flat_map those by calling CreateRelation:
result = [people, addresses, phone_numbers, aliases].transpose.flat_map do |person, *relations|
relations.map { |relationship| CreateRelation(person, relationship) }
end
#["Person 1 used to be related to Person 1 Address",
# "Person 1 used to be related to Person 1 Phone",
# "Person 1 used to be related to Person 1 AKA",
# "Person 2 is currently related to Person 2 Address",
# "Person 2 used to be related to Person 2 Phone",
# "Person 2 is currently related to Person 2 AKA",
# "Person 3 is currently related to Person 3 Address",
# "Person 3 used to be related to Person 3 Phone",
# "Person 3 used to be related to Person 3 AKA"]
Or, at that point you could stick with just iterating and pushing, if you don't want to map/flat_map.
The more I think about it, the more I think I'd go with transpose -> each_with_object, instead of flat_map...less "create an array and then throw it away", I'll leave this with flat_map though because it is another option and #Sebastian Palma has each_with_object covered.

Related

Ruby -- Create hash with custom keys and values from an existing array

I have a response from api as a parsed JSON which is an array of hashes, I need to create a new hash with custom keys and the values that I will take from that api response
#array of hashes looks like this:
[{:id=>1,
:name=>"Leanne Graham",
:username=>"Bret",
:email=>"Sincere#april.biz",
:address=>
{:street=>"Kulas Light",
:suite=>"Apt. 556",
:city=>"Gwenborough",
:zipcode=>"92998-3874",
:geo=>{:lat=>"-37.3159", :lng=>"81.1496"}},
:phone=>"1-770-736-8031 x56442",
:website=>"hildegard.org",
:company=>
{:name=>"Romaguera-Crona", :catchPhrase=>"Multi-layered client-server neural-net", :bs=>"harness real-time e-markets"}}]
(and there are 4 more people). I only need 2 keys and the new hash should look something like this
ideal_hash = {
:full_name => ["Leanne Graham", "another name", "another name", "etc"]
:email => ["Sincere#april.biz", "some email", "another one", "etc"]
}
there are gonna be more values in array but just these two custom keys.
I tried taking values from hash and zipping it with an array of keys but the problem is that I only get 2 values instead of 4 because there are only 2 keys, I tried to map but it didnt quite work either. please help
I only need 2 keys .. :full_name and :email
input.each_with_object({full_name: [], email: []}) do |e, a|
a[:full_name] << e[:name]
a[:email] << e[:email]
end

Enumerator consisting of a repeating (but changing) array [duplicate]

This question already has answers here:
Count iteration on the Enumerable cycle
(5 answers)
Closed 6 years ago.
I have a set of objects that needs to repeat an indeterminate number of times. This would be easy enough to set up if the array were fixed:
>> enum = ['Start', 'Peak', 'Finish'].cycle
>> enum.first(7)
=> ['Start', 'Peak', 'Finish', 'Start', 'Peak', 'Finish', 'Start']
But the result I want is this:
>> enum = <Enumerator magic here>
>> enum.first(7)
=> ['Start Lap 1', 'Peak Lap 1', 'Finish Lap 1', 'Start Lap 2', 'Peak Lap 2', 'Finish Lap 2', 'Start Lap 3']
It seems like I should be able to start with (1..Float::INFINITY) and get the right result with #map or #each, but I'm having no luck. I know I could use (1..arbitrary_big_number) and make a big array, but hoping for a lazy-evaluated solution.
Thanks in advance.
Try this one
enum = Enumerator.new do |y|
lap = 1
ss = %w(Start Peak Finish).cycle
loop do
3.times { y << "#{ss.next} Lap #{lap}" }
lap += 1
end
end
enum.first(7)
=> ["Start Lap 1", "Peak Lap 1", "Finish Lap 1", "Start Lap 2", "Peak Lap 2", "Finish Lap 2", "Start Lap 3"]

Grouping data from nested arrays in ruby

Assuming the following data tuple containing a person's name, age and the books he has read:
list = [
["Peter", 21, ["Book 1", "Book 2", "Book 3", "Book 4"],
["Amy", 19, ["Book 3", "Book 4"],
["Sanders", 32, ["Book 1", "Book 2",],
["Charlie", 21, ["Book 4", "Book 5", "Book 6"],
["Amanda", 21, ["Book 2", "Book 5"]
]
What is the optimal way to extract names grouped by the books read, into the following format (basically a an array of arrays containing the book name and an array of names of people who read it)
results = [
["Book 1", ["Sanders", "Peter"]],
["Book 2", ["Sanders" "Amanda", "Peter"]],
["Book 3", ["Peter", "Amy"]],
["Book 4", ["Charlie", "Peter", "Amy"]],
["Book 5", ["Amanda","Charlie"]],
["Book 6", ["Charlie"]]
]
I've tried the following iterating method which extracts the lists of names and puts them into a hash, with the book title as the keys.
book_hash = Hash.new([])
list.each { |name,age,books|
books { |x| book_hash[x] = book_hash[x] + [name] }
}
results = book_hash.to_a.sort
However, the above method seems rather inefficient when handling large datasets containing millions of names. I've attempted to use the Array.group_by, but so far I'm unable to make it work with nested arrays.
Does anyone have any idea about the above?
Hash output. More suitable.
list.each_with_object({}) do |(name, age, books), hash|
books.each do |book|
(hash[book] ||= []) << name
end
end
If you must make it an array, then append a .to_a to the output of the above.

Every combination of elements in two Ruby arrays

I have three Ruby arrays:
color = ['blue', 'green', 'yellow']
names = ['jack', 'jill']
combination = []
I need the following concatenation inserted into the combination array:
FOR EACH names value: [name value] + " wants " + [color value]
So the outcome will be:
combination = ['jack wants blue','jack wants green','jack wants yellow','jill wants blue','jill wants green','jill wants yellow']
I can't figure out how to do this. I've tried this to start off with but no avail:
name.each do |name|
puts "#{name} wants #{color}"
end
You can use Array#product:
names = ['jack', 'jill']
colors = ['blue', 'green', 'yellow']
names.product(colors).map { |name, color| "#{name} wants #{color}" }
#=> ["jack wants blue", "jack wants green", "jack wants yellow", "jill wants blue", "jill wants green", "jill wants yellow"]
Interpolation would work as in other answers, but I like string format better in cases like this. The freeze method is for optimization. It will also work without it.
names.product(colors).map{|a| "%s wants %s".freeze % a}
names = ['jack', 'jill']
colors = ['blue', 'green', 'yellow']
# Note - renamed to "colors" plural
names.collect { |name| colors.collect { |color| "#{name} wants #{color}" } }.flatten
=> ["jack wants blue", "jack wants green", "jack wants yellow", "jill wants blue", "jill wants green", "jill wants yellow"]

Ruby array trouble

I am trying to learn Ruby, and arrays are giving me some trouble.
I have input that I flatten down to the pattern "name, number, name, number". I then want to make an array of 2-element arrays, each containing a name and the next number.
When I push these 2-element arrays into another array the seem to automatically flatten to a 0-dimensional array. What I want is the final array to be of size [N/2][2], N being number of names, or numbers in the input.
http://pastie.org/3542269
The puts with the comment does not happen until all of the elements from the pairs array has been printed, so it looks like this:
Name
1
Name
2
Name
3
When I expected this:
Name
1
Name
2
Name
3
I guess my questions are:
How do I put arrays inside an array, to make a jagged one?
How do I keep track of how many dimensions my arrays are in Ruby? It's so much easier when you have to declare a size.
some_array = [[["Name 1", "value 1"], ["Name 2", "value 2"]], [["Name 3", "value 3"], ["Name 4", "value 4"]]]
array = some_array.flatten
new_array = array.each_slice(2).map do |a, b|
[a,b]
end
#=> [["Name 1", "value 1"],
#=> ["Name 2", "value 2"],
#=> ["Name 3", "value 3"],
#=> ["Name 4", "value 4"]]
which is similar to some_array.flatten(1)

Resources