Every combination of elements in two Ruby arrays - ruby

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"]

Related

Loop though multi-dimensional array in ruby

This is the question i'm having trouble with.
"Loop through the multi-dimensional Array and print out the full information of even items in the Array (ie the 2nd and 4th array in your multidimensional array)".I'm tasked with outputting all the data in the even numbered array which should be [1] [3], which would output all the information from array "derrick" & "andrew" only.
kristopher = ["kris", "palos hills", "708-200", "green"]
derrick = ["D-Rock", "New York", "773-933", "green"]
willie = ["William", "Humbolt Park", "773-987", "Black"]
andrew = ["drew", "northside", "773-123","blue"]
friends = [kristopher, derrick, willie, andrew]
friends.each do |arr|
print arr [0..4]
end
#Output
["kris", "palos hills", "708-200", "green"]["D-Rock", "New York", "773-933", "green"]["William", "Humbolt Park", "773-987", "Black"]["drew", "northside", "773-123", "blue"]
Something like this:
kristopher = ["kris", "palos hills", "708-200", "green"]
derrick = ["D-Rock", "New York", "773-933", "green"]
willie = ["William", "Humbolt Park", "773-987", "Black"]
andrew = ["drew", "northside", "773-123","blue"]
friends = [kristopher, derrick, willie, andrew]
(1...friends.length).step(2).each do |friendIndex|
friend = friends[friendIndex]
print friend
end
You can check Enumerable#partition and Enumerable#each_with_index which are helpful for splitting the array by a condition on the index of elements. If you use Integer#even? you can make a partition between even and odd indexes (+ 1 in this case).
friends.partition.with_index { |_, i| (i + 1).even? }
#=> [[["D-Rock", "New York", "773-933", "green"], ["drew", "northside", "773-123", "blue"]], [["kris", "palos hills", "708-200", "green"], ["William", "Humbolt Park", "773-987", "Black"]]]
So, for your case, take the first element:
friends.partition.with_index { |_, i| (i + 1).even? }.first
Or you can go straight with Enumerable#select:
friends.select.with_index { |_, i| (i + 1).even? }

Clean way to push results of multiple methods into an array

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.

To find out uniq values from an array of hashes based on an input hash

I have an array and a hash:
a = [
{ :color => "blue", :name => "wind" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "yellow", :name => "wind" },
{ :color => "green", :name => nil },
{ :color => "black", :name => "snow" }
]
b = { blue: 'blue', white: 'white', red: 'red', green: 'green', black: 'black' }
I need to find out unique names based on the input hash to get this:
['wind', 'fire', 'snow']
I've tried:
names = a.map { |i| [i[:color], i[:name]] }
.delete_if { |key, value| value.nil? }
resultant_names = []
b.values.each do |c|
if names[c]
resultant_names << names[c]
end
end
resultant_names.uniq
I need a better approach for this. This one has too many loops.
While your result does not make sense to me (e.g. it is missing snow) this will work
a.map(&:values).reverse.to_h.values_at(*b.values).compact.uniq
#=> ["wind","fire"]
To break it down:
a.map(&:values).reverse.to_h
#=> {"white"=>"wind", "green"=>nil, "yellow"=>"wind", "red"=>"fire", "blue"=>"wind"}
You'll notice snow is missing because when we reverse the list ["white","wind"] will overwrite ["white","snow"] when converted to a Hash
Then we just collect the values for the given colors from
b.values
#=> ["blue", "white", "red", "green"]
a.map(&:values).reverse.to_h.values_at(*b.values)
#=> ["wind", "wind", "fire", nil]
Then Array#compact will remove the nil elements and Array#uniq will make the list unique.
If snow was intended you could skip the reversal
a.map(&:values).to_h.values_at(*b.values).compact.uniq
#=> ["wind", "snow", "fire"]
Either way this is a strange data structure and these answers are only to help with the problem provided as the duplicate colors can cause differing results based on the order in a.
I believe you want 'snow' to be in your output array, as there is no other logical explanation. Your code would work if you were to add .to_h on the end of line 2, but as you note, it is not very clean or efficient. Also, by converting to a Hash, as a result of duplicate keys, you would potentially lose data.
Here's a tighter construct that avoids the data loss problem:
def unique_elements(a, b)
color_set = b.values.to_set
a.map { |pair| pair[:name] if color_set.include?(pair[:color]) }.compact.uniq
end
First we take the values of b and convert them to a set, so that we can efficiently determine if a given element is a member of the set.
Next we map over a choosing the names of those members of a for which the [:color] is included in our color set.
Finally we eliminate nils (using compact) and choose unique values.
>> unique_elements(a, b)
#> ["wind", "fire", "snow"]
I would begin by converting a to a more useful data structure.
h = a.each_with_object({}) { |g,h| h[g[:color]] = g[:name] }
#=> {"blue"=>"wind", "red"=>"fire", "white"=>"wind", "yellow"=>"wind",
# "green"=>nil, "black"=>"snow"}
We may then simply write
h.values_at(*b.values).compact.uniq
# => ["wind", "fire", "snow"]
This approach has several desireable characteristics:
the creation of h makes the method easier to read
debugging and testing is made easier by creating h as a separate step
h need only be created once even if several values of b are to be evaluated (in which case we may wish to make h an instance variable).
h could be chained to the second statement but I've chosen not to do so for the reasons given above (especially the last one).

Sorting multiple values by ascending and descending

I'm trying to sort an array of objects based upon different attributes. Some of those attributes I would like to sort in ascending order and some in descending order. I have been able to sort by ascending or descending but have been unable to combine the two.
Here is the simple class I am working with:
class Dog
attr_reader :name, :gender
DOGS = []
def initialize(name, gender)
#name = name
#gender = gender
DOGS << self
end
def self.all
DOGS
end
def self.sort_all_by_gender_then_name
self.all.sort_by { |d| [d.gender, d.name] }
end
end
I can then instantiate some dogs to be sorted later.
#rover = Dog.new("Rover", "Male")
#max = Dog.new("Max", "Male")
#fluffy = Dog.new("Fluffy", "Female")
#cocoa = Dog.new("Cocoa", "Female")
I can then use the sort_all_by_gender_then_name method.
Dog.sort_all_by_gender_then_name
=> [#cocoa, #fluffy, #max, #rover]
The array it returns includes females first, then males, all sorted by name in ascending order.
But what if I want to have gender be descending, and then name ascending, so that it would be males first and then sorted by name ascending. In this case:
=> [#max, #rover, #cocoa, #fluffy]
Or, if I wanted it by gender ascending, but name descending:
=> [#fluffy, #cocoa, #rover, #max]
When sorting numerical values, you can prepend a - to make it sort in reverse. However, I have been unable to find a way to do this with strings. Any help or ideas would be appreciated. Thanks.
Here's one way to do it using .sort instead of .sort_by:
dogs = [
{ name: "Rover", gender: "Male" },
{ name: "Max", gender: "Male" },
{ name: "Fluffy", gender: "Female" },
{ name: "Cocoa", gender: "Female" }
]
# gender asc, name asc
p(dogs.sort do |a, b|
[a[:gender], a[:name]] <=> [b[:gender], b[:name]]
end)
# gender desc, name asc
p(dogs.sort do |a, b|
[b[:gender], a[:name]] <=> [a[:gender], b[:name]]
end)
# gender asc, name desc
p(dogs.sort do |a, b|
[a[:gender], b[:name]] <=> [b[:gender], a[:name]]
end)
Output:
[{:name=>"Cocoa", :gender=>"Female"}, {:name=>"Fluffy", :gender=>"Female"}, {:name=>"Max", :gender=>"Male"}, {:name=>"Rover", :gender=>"Male"}]
[{:name=>"Max", :gender=>"Male"}, {:name=>"Rover", :gender=>"Male"}, {:name=>"Cocoa", :gender=>"Female"}, {:name=>"Fluffy", :gender=>"Female"}]
[{:name=>"Fluffy", :gender=>"Female"}, {:name=>"Cocoa", :gender=>"Female"}, {:name=>"Rover", :gender=>"Male"}, {:name=>"Max", :gender=>"Male"}]
Basically, this is doing something similar to negating numbers (as you mentioned in the question), by swapping the property to the other element if it needs to be sorted in descending order.
This ReversedOrder mixin can help you accomplish the mixed direction sorts on seperate attributes, using sort_by:
module ReversedOrder
def <=>(other)
- super
end
end
Use example:
dogs = [
{ name: "Rover", gender: "Male" },
{ name: "Max", gender: "Male" },
{ name: "Fluffy", gender: "Female" },
{ name: "Cocoa", gender: "Female" }
]
dogs.sort_by {|e| [e[:gender], e[:name]] }
=> [{:name=>"Cocoa", :gender=>"Female"},
{:name=>"Fluffy", :gender=>"Female"},
{:name=>"Max", :gender=>"Male"},
{:name=>"Rover", :gender=>"Male"}]
dogs.sort_by {|e| [e[:gender].dup.extend(ReversedOrder), e[:name]] }
=> [{:name=>"Max", :gender=>"Male"},
{:name=>"Rover", :gender=>"Male"},
{:name=>"Cocoa", :gender=>"Female"},
{:name=>"Fluffy", :gender=>"Female"}]
Note: Be careful to dup the reversed element. Without that, you will mixin the comparison inverter to the actual object instead of just the key being made for sort_by and it will forever produce reversed comparisons.

Compare multiple values in a Ruby array

[
{
"name": "John Doe",
"location": {
"name": "New York, New York",
"id": 12746342329
},
"hometown": {
"name": "Brooklyn, New York",
"id": 43453644
}
},
{
"name": "Jane Doe",
"location": {
"name": "Miami, Florida",
"id": 12746342329
},
"hometown": {
"name": "Queens, New York",
"id": 12746329
}
}
]
Given this piece of JSON, how would I be able to loop through and pull out all of the "hometown" and "location" keys and see which people had the value of New York?
My issue is I can Array.each through these items, but I don't know how to traverse both location && hometown with my criteria ("New York").
people.select {|person|
person.any? {|k, v|
%w[location hometown].include?(k) && /New York/ =~ v['name']
}}
This basically says the following: select all entries in the array for which the following condition is true. The condition is: is it true for any of the key-value pairs that the key is either 'hometown' or 'location' and the name property of the value belonging to that key matches the Regexp /New York/?
However, your object model seems to be in a serious need of refactoring. In fact, the main problem is that your object model isn't even an object model, it's a hash and array model.
Here's what I mean by a proper object model:
class Person
attr_reader :name, :location, :hometown
def initialize(name, location=nil, hometown=nil)
#name, #location, #hometown = name, location, hometown
end
def cities
return #location, #hometown
end
end
class City
attr_reader :id, :name
def initialize(id, name)
#id, #name = id, name
end
def =~(other)
name =~ other
end
end
nyc = City.new(12746342329, 'New York, New York')
brooklyn = City.new(43453644, 'Brooklyn, New York')
miami = City.new(12746342329, 'Miami, Florida')
queens = City.new(12746329, 'Queens, New York')
john = Person.new('John Doe', nyc, brooklyn)
jane = Person.new('Jane Doe', miami, queens)
people = [john, jane]
If you have such a proper object model, your code becomes much cleaner, because instead of teasing apart the nuts of bults of a nested maze of hashes and arrays, you have nice little objects that you can simply ask some questions:
people.select {|person| person.cities.any? {|city| city =~ /New York/ }}
You can almost read this like English: from the array select all people for which any of their cities matches the Regexp /New York/.
If we improve the object model further, it gets even better:
class Person
def lived_in?(pattern)
cities.any? {|city| city =~ pattern }
end
end
people.select {|person| person.lived_in?(/New York/) }
This basically says "From the people, select the ones which at one time lived in New York". That's much better than "from the people select all for which the first element of the key value pair is either the string 'hometown' or the string 'location' and the second element of the key value pair matches the Regexp /New York/".
I think Jörg's solution has a minor bug - 'location' and 'hometown' are not used, so, for example, following "person" would pass the test:
{
'name' => 'Foo Bar',
'favourite movie' => {
name => 'New York, New York!'
}
}
Here's a shot at correcting it, along with comments:
ary.select {|person| # get me every person satisfying following condition
%w[location hometown].any? {|key| # check if for any of the strings 'location' and 'hometown'
# person should have that key, and its 'name' should contain /New York/ regexp
person[key] && person[key]['name'] && /New York/ =~ person[key]['name']
}}

Resources