How to combine elements from one array with each element from another array - ruby

I'm trying to combine elements from one array with every element from another array, I tried looking for some solutions but I couldn't figure it out.
Take these two arrays for example:
num = [1,2,3]
let = ["a","b","c"]
I want to combine them in order to obtain:
combined = [[1, "a"], [1, "b"], [1, "c"], [2, "a"], [2, "b"], [2, "c"],
[3, "a"], [3, "b"], [3, "c"]]

You can use #product:
num = [1,2,3]
let = ["a","b","c"]
num.product let
#=>[[1, "a"], [1, "b"], [1, "c"], [2, "a"], [2, "b"], [2, "c"], [3, "a"], [3, "b"], [3, "c"]]

Related

Create a hash from an array using group_by

I have the following array
ages = [["a", 15],["b", 16], ["c", 15], ["d", 16], ["e", 17], ["f", 20]]
I have to create a hash with ages as values such that it looks like this
{15 => ["a","c"], 16=> ["b","d]....}
when I run the group_by method:
puts ages.group_by {|list| list[1]}
this is what i get:
{15=>[["a", 15], ["c", 15]], 16=>[["b", 16], ["d", 16]], 17=>[["e", 17]], 20=>[["f", 20]]}
I'd really appreciate it if you can clarify how to make this cleaner and get the values as an array of names with same ages.
ages = [["a", 15],["b", 16], ["c", 15], ["d", 16], ["e", 17], ["f", 20]]
You can simplify your first step:
ages.group_by(&:last)
#=> {15=>[["a", 15], ["c", 15]],
# 16=>[["b", 16], ["d", 16]],
# 17=>[["e", 17]],
# 20=>[["f", 20]]}
You then need only transform the values to the desired arrays:
ages.group_by(&:last).transform_values { |arr| arr.map(&:first) }
#=> {15=>["a", "c"],
# 16=>["b", "d"],
# 17=>["e"],
# 20=>["f"]}
When
arr = [["a", 15], ["c", 15]]
for example,
arr.map(&:first)
#=> ["a", "c"]
See Hash#transform_values.
The first thing that comes to mind is
irb(main):012:0> ages.group_by {|e| e[1]}.map {|k, v| [k, v.map(&:first)]}.to_h
=> {15=>["a", "c"], 16=>["b", "d"], 17=>["e"], 20=>["f"]}
Here we map the hash to key-value pairs, map the values of each pair to first elements then convert the pairs back to a hash.

Need to sort array based on another array of array

I have two arrays which are to be compared and sorted based on another array. Here is as follows:
a = [["A", 1075000], ["C", 1750000], ["D", 0], ["E", 0], ["B", 0]]
b = ['A','B','C','D','E']
The array a should be sorted in order as follows(in which a is compared with b):
[["A", 1075000], ["B", 0], ["C", 1750000], ["D", 0], ["E", 0]]
I have tried this:
sort_by a.sort! {|a1,b1| a1[0] <=> b1[0]}
Another way of doing this:
lookup = {}
b.each_with_index { |el, i| lookup[el] = i }
a.sort_by { |el| lookup.fetch(el.first) }
# => [["A", 1075000], ["B", 0], ["C", 1750000], ["D", 0], ["E", 0]]
I assume you want to sort the elements in a according to their position in b and that the elements in b are the strings 'A', 'B', etc and not constants.
Then I would do something like this:
a = [["A", 1075000], ["C", 1750000], ["D", 0], ["E", 0], ["B", 0]]
b = ['A','B','C','D','E']
a.sort { |x, y| b.index(x.first) <=> b.index(y.first) }
#=> [["A", 1075000], ["B", 0], ["C", 1750000], ["D", 0], ["E", 0]]
Depending on the size of b it might make sense to use sort_by instead of sort. sort_by catches the return value of the block and does not evaluate the block multiple times:
a.sort_by { |x| b.index(x) }

Ruby - putting array elements into another array in order

array1 = [ [a], [b], [c], [d], [e] ]
array2 = [1, 2, 3, 4, 5, ...]
How can I put each of the elements of array2 into each the elements of array1 to get something like:
array3 = [ [a, 1], [b, 2], [c, 3], [d, 4], ... ]
I'm trying something like array1.map { |a| [a, array2.each { |b| b}] }, but not really sure how to get it yet.
Thanks!
Just try this using Array#flatten and Array#zip
array1 = [ ['a'], ['b'], ['c'], ['d'], ['e'] ]
array2 = [1, 2, 3, 4, 5]
array1.flatten.zip(array2)
# [["a", 1], ["b", 2], ["c", 3], ["d", 4], ["e", 5]]
More information about Array#zip can be found here.
array1 = [ ['a'], ['b'], ['c'], ['d','e'] ]
array2 = [1, 2, 3, 4]
If you do not wish to alter array1 or array2:
array1.zip(array2).map { |a1,e2| a1 + [e2] }
#=> [["a", 1], ["b", 2], ["c", 3], ["d", "e", 4]]
array1
#=> [ ['a'], ['b'], ['c'], ['d','e'] ]
If you do wish to alter array1 but not array2:
array1.zip(array2).map { |a1,e2| a1 << e2 }
#=> [["a", 1], ["b", 2], ["c", 3], ["d", "e", 4]]
array1
#=> [["a", 1], ["b", 2], ["c", 3], ["d", "e", 4]]
If you do wish to alter array1 and can also alter array2:
array1.map { |a| a << array2.shift }
#=> [["a", 1], ["b", 2], ["c", 3], ["d", "e", 4]]
array1
#=> [["a", 1], ["b", 2], ["c", 3], ["d", "e", 4]]
array2
#=> []
In the first two cases you could use Array#transpose instead of Array#zip by replacing array1.zip(array2) with [array1, array2].transpose.

Ruby: Invert a hash to also preserve non unique values

I have a hash that looks like this:
{"a" => [1, 2, 3], "b" => [4, 5, 6], "c" => [3, 4, 5], "d" => [7, 2, 3]}
What I want to do is to make a hash of all existing values with an array of all keys that included it, e.g. turn the above into this:
{1 => ["a"], 2 => ["a", "d"], 3 => ["a", "c", "d"], 4 => ["b", "c"]}
Try this:
module HashReverser
def invert_map
each_with_object({}) do |(key, value), result|
value.each { |v| (result[v] ||= []) << key }
end
end
end
original = {"a" => [1, 2, 3], "b" => [4, 5, 6], "c" => [3, 4, 5]}
original.extend(HashReverser).invert_map # => {1=>["a"], 2=>["a"], 3=>["a", "c"], 4=>["b", "c"], 5=>["b", "c"], 6=>["b"]}
I do prefer #Jikku's solution, but there's always another way. Here's one.
(I see this is very close to #Chris's solution. I will leave it for the last line, which is a little different.)
Code
def inside_out(h)
g = h.flat_map { |s,a| a.product([s]) }
.group_by(&:first)
g.merge(g) { |_,a| a.map(&:last) }
end
Example
h = {"a" => [1, 2, 3], "b" => [4, 5, 6], "c" => [3, 4, 5], "d" => [7, 2, 3]}
inside_out(h)
#=> {1=>["a"], 2=>["a", "d"], 3=>["a", "c", "d"], 4=>["b", "c"],
# 5=>["b", "c"], 6=>["b"], 7=>["d"]}
Explanation
For h above:
a = h.flat_map { |s,a| a.product([s]) }
#=> [[1, "a"], [2, "a"], [3, "a"], [4, "b"], [5, "b"], [6, "b"],
# [3, "c"], [4, "c"], [5, "c"], [7, "d"], [2, "d"], [3, "d"]]
g = a.group_by(&:first)
#=> {1=>[[1, "a"]], 2=>[[2, "a"], [2, "d"]],
# 3=>[[3, "a"], [3, "c"], [3, "d"]],
# 4=>[[4, "b"], [4, "c"]],
# 5=>[[5, "b"], [5, "c"]],
# 6=>[[6, "b"]],
# 7=>[[7, "d"]]}
g.merge(g) { |_,a| a.map(&:last) }
#=> {1=>["a"], 2=>["a", "d"], 3=>["a", "c", "d"], 4=>["b", "c"],
# 5=>["b", "c"], 6=>["b"], 7=>["d"]}
An alternate solution:
# Given
h = {"a" => [1, 2, 3], "b" => [4, 5, 6], "c" => [3, 4, 5], "d" => [7, 2, 3]}
h.flat_map {|k, v| v.product [k]}.group_by(&:first).each_value {|v| v.map! &:last }
Or:
h.flat_map {|k, v| v.product [k]}.reduce({}) {|o, (k, v)| (o[k] ||= []) << v; o}
The idea here is that we use Array#product to create a list of inverted single key-value pairs:
product = h.flat_map {|k, v| v.product([k]) }
# => [[1, "a"], [2, "a"], [3, "a"], [4, "b"], [5, "b"], [6, "b"], [3, "c"], [4, "c"], [5, "c"], [7, "d"], [2, "d"], [3, "d"]]
Group them by the value of the first item in each pair:
groups = product.group_by(&:first)
# => {1=>[[1, "a"]], 2=>[[2, "a"], [2, "d"]], 3=>[[3, "a"], [3, "c"], [3, "d"]], 4=>[[4, "b"], [4, "c"]], 5=>[[5, "b"], [5, "c"]], 6=>[[6, "b"]], 7=>[[7, "d"]]}
And then convert the values to a list of the last values in each pair:
result = groups.each_value {|v| v.map! &:last }
# => {1=>["a"], 2=>["a", "d"], 3=>["a", "c", "d"], 4=>["b", "c"], 5=>["b", "c"], 6=>["b"], 7=>["d"]}

Inject each element of an array to a different array

I have two arrays:
array1: [[1, 2], [2, 3]]
array2: ["a", "b", "c"]
I would like to combine those two and get the below result:
[[1, 2, "a"], [1, 2, "b"], [1, 2, "c"], [2, 3, "a"], [2, 3, "b"], [2, 3, "c"]]
You can use Array#product:
array1.product(array2).map &:flatten
#=> [[1, 2, "a"], [1, 2, "b"], [1, 2, "c"], [2, 3, "a"], [2, 3, "b"], [2, 3, "c"]]
Array#product is purpose-built for this, but this is one alternative:
array2.flat_map { |e| array1.map { |arr| arr+[e] } }

Resources