Create a hash from an array using group_by - ruby

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.

Related

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

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

How does one create a loop with indefinite nested loops?

say you have a list [ 1 , 2 ,3 ...... n]
if you needed to compare two elements so you would write something like
list = (0..9999).to_a
idx = 0
while idx < list.length
idx2 = idx
while idx2 < list.length
puts list[idx] + list[idx2] if (list[idx] + list[idx2]).odd?
idx2 += 1
end
idx += 1
end
But what if the number of comparisons is not constant and increases?
This code hard codes the comparison by having one loop inside another, but if you needed to compare 4 or more elements how does one write a loop or something that achieves this if you don't know the maximum number of comparisons?
We have a helpful method in ruby to do this, and that is Array#combination:
def find_odd_sums(list, num_per_group)
list.combination(num_per_group).to_a.map(&:sum).select(&:odd?)
end
You can re-implement combination, if you choose to. There are many versions of this function available at Algorithm to return all combinations of k elements from n
This question is not clear. Firstly, the title, which is vague, asks how a particular approach to an unstated problem can be implemented. What you need, at the beginning, is a statement in words of the problem.
I will make a guess as to what that statement might be and then propose a solution.
Given
an array arr;
a positive integer n, 1 <= n <= arr.size; and
a method m having n arguments that are distinct elements of arr that returns true or false,
what combinations of n elements of arr cause m to return true?
We can use the following method combined with a definition of the method m.
def combos(arr, n, m)
arr.combination(n).select { |x| public_send(m, *x) }
end
The key, of course, is the method Array#combination. See also the docs for the methods Enumerable#select and Object#public_send.
Here is its use with the example given in the question.
def m(*x)
x.sum.odd?
end
arr = [1,2,3,4,5,6]
combos(arr, 2, :m)
#=> [[1, 2], [1, 4], [1, 6], [2, 3], [2, 5], [3, 4], [3, 6], [4, 5], [5, 6]]
combos(arr, 3, :m)
#=> [[1, 2, 4], [1, 2, 6], [1, 3, 5], [1, 4, 6], [2, 3, 4], [2, 3, 6],
# [2, 4, 5], [2, 5, 6], [3, 4, 6], [4, 5, 6]]
combos(arr, 4, :m)
#=> [[1, 2, 3, 5], [1, 2, 4, 6], [1, 3, 4, 5], [1, 3, 5, 6], [2, 3, 4, 6], [2, 4, 5, 6]]
See the doc for Array#sum (which made it's debut in Ruby v2.4.
Here's a second example: given an array of letters, which combinations of five letters have two vowels?
VOWEL_COUNTER = %w| a e i o u |.product([1]).to_h.tap { |h| h.default=0 }
#=> {"a"=>1, "e"=>1, "i"=>1, "o"=>1, "u"=>1}
VOWEL_COUNTER['a']
#=> 1
By setting the hash's default value to zero, VOWEL_COUNTER[k] will return zero if it does not have a key k. For example,
VOWEL_COUNTER['r']
#=> 0
def m(*x)
x.sum { |c| VOWEL_COUNTER[c] } == 2
end
arr = %w| a r t u e v s |
combos(arr, 5, :m)
#=> [["a", "r", "t", "u", "v"], ["a", "r", "t", "u", "s"],
# ["a", "r", "t", "e", "v"], ["a", "r", "t", "e", "s"],
# ["a", "r", "u", "v", "s"], ["a", "r", "e", "v", "s"],
# ["a", "t", "u", "v", "s"], ["a", "t", "e", "v", "s"],
# ["r", "t", "u", "e", "v"], ["r", "t", "u", "e", "s"],
# ["r", "u", "e", "v", "s"], ["t", "u", "e", "v", "s"]]
Note that VOWEL_COUNTER is constructed as follows.
a = %w| a e i o u |
#=> ["a", "e", "i", "o", "u"]
b = a.product([1])
#=> [["a", 1], ["e", 1], ["i", 1], ["o", 1], ["u", 1]]
c = b.to_h
#=> {"a"=>1, "e"=>1, "i"=>1, "o"=>1, "u"=>1}
With this hash,
c['r']
#=> nil
so we need to set the default value to zero.
VOWEL_COUNTER = c.tap { |h| h.default=0 }
#=> {"a"=>1, "e"=>1, "i"=>1, "o"=>1, "u"=>1}
c['r']
#=> 0
Alternatively, we could have omitted the last step (setting the hash's default to zero), and written
x.sum { |c| VOWEL_COUNTER[c].to_i } == 2
because NilClass#to_i converts nil to zero.
See also the docs for the methods #select, #public_send
I feel like everyone is making this more complicated than it is. You sure got pointed to the right direction (Array#combination, Array#repeated_combination, Array#permutation, Array#repeated_permutation). To accomplish the exact thing you are doing, you can simply do:
list.repeated_combination(2) { |c| puts c.sum if c.sum.odd? }
Check the links above to see the difference between them.
If you want to create a helper method you can, but in my opinion it's not really needed in this case. Replace 2 with the number you are looking for and you got your answer.

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

How to output all possible combinations using loops in Ruby?

I just started to learn programming and am trying to write a function that outputs all possible combinations. So far I've been able to find all possible combinations of size 2 but I'm not sure how to leave the code open ended to deal with combinations of larger sizes. Would some sort of recursion would be useful?
I know I could use the built in combination method but I'm just trying to figure out how to write it from scratch. Any advice would be much appreciated. Thanks!
def two_combos(input)
list = []
for index1 in (0...input.length)
for index2 in (0...input.length)
if input[index1] != input[index2]
if list.include?([input[index2],input[index1]])==false
list << [input[index1],input[index2]]
end
end
end
end
return list
end
two_combos(["A","B","C"])
#outputs
=> [["A", "B"], ["A", "C"], ["B", "C"]]
#Missing
["A","B","C"]
This implementation is like counting recursively in binary:
def combinations(items)
return [] unless items.any?
prefix = items[0]
suffixes = combinations(items[1..-1])
[[prefix]] + suffixes + suffixes.map {|item| [prefix] + item }
end
> combinations(%w(a b c))
=> [["a"], ["b"], ["c"], ["b", "c"], ["a", "b"], ["a", "c"], ["a", "b", "c"]]
At each stage, the combinations are a concatenation of:
the first element alone
the combinations of the following elements (elements 1..n-1)
the first element combined with the combinations of the following elements
Here is recursive algorithm
def combinations(array, size)
fail "size is too big" if size > array.size
combination([], [], array, size)
end
def combination(result, step, array, size)
steps = size - step.size
array[0..-steps].each_with_index do |a, i|
next_step = step + [a]
if next_step.size < size
combination(result, next_step, array[i+1..-1], size)
else
result << next_step
end
end
result
end
a = ("A".."E").to_a
p combinations(a, 1)
# [["A"], ["B"], ["C"], ["D"], ["E"]]
p combinations(a, 2)
# [["A", "B"], ["A", "C"], ["A", "D"], ["A", "E"], ["B", "C"], ["B", "D"], ["B", "E"], ["C", "D"], ["C", "E"], ["D", "E"]]
p combinations(a, 3)
# [["A", "B", "C"], ["A", "B", "D"], ["A", "B", "E"], ["A", "C", "D"], ["A", "C", "E"], ["A", "D", "E"], ["B", "C", "D"], ["B", "C", "E"], ["B", "D", "E"], ["C", "D", "E"]]
p combinations(a, 4)
# [["A", "B", "C", "D"], ["A", "B", "C", "E"], ["A", "B", "D", "E"], ["A", "C", "D", "E"], ["B", "C", "D", "E"]]
I can think of a way to calculate combinations of a given size without using recursion, but it is not especially efficient. It is very efficient, however, if you want to obtain all combinations of all sizes (sometimes referred to as "power"). [Edit: evidently not. See the benchmark.] My understand is that the question concerns the latter, but I will give methods for each.
If index has n elements, each combination can be represented by an n-element array whose elements are each zero or one, 1 meaning the combination includes the element at that index, '0' (or a leading space) meaning it does not. We therefore can generate the set of all combinations of all sizes by simply generating all binary numbers of length n, converting each from its string representatation (with leading zeroes) to an array of "0"'s and "1"s, replacing the "1"'s with their index positions, removing the "0"'s and extracting the element of index at the given index positions.
Code
def all_combos(sz)
[*(0..2**sz-1)].map { |i| ("%0#{sz}b" % i).chars }
.map { |a| a.each_with_index
.select { |n,ndx| n=="1" }.map(&:last) }
end
def combos(input, n, all_combos)
all_combos.select { |c| c.size == n }.map { |c| input.values_at(*c) }
end
def power(input, all_combos)
all_combos.map { |c| input.values_at(*c) }
end
Example
input = %w{b e a r s}
#=> ["b", "e", "a", "r", "s"]
ac = all_combos(input.size)
#=> [[], [4], [3], [3, 4], [2], [2, 4], [2, 3], [2, 3, 4],
# [1], [1, 4], [1, 3], [1, 3, 4], [1, 2], [1, 2, 4], [1, 2, 3],
# [1, 2, 3, 4], [0], [0, 4], [0, 3], [0, 3, 4], [0, 2], [0, 2, 4],
# [0, 2, 3], [0, 2, 3, 4], [0, 1], [0, 1, 4], [0, 1, 3], [0, 1, 3, 4],
# [0, 1, 2], [0, 1, 2, 4], [0, 1, 2, 3], [0, 1, 2, 3, 4]]
(0..input.size).each { |i| puts "size #{i}"; p combos(input, i, ac) }
# size 0
# [[]]
# size 1
# [["s"], ["r"], ["a"], ["e"], ["b"]]
# size 2
# [["r", "s"], ["a", "s"], ["a", "r"], ["e", "s"], ["e", "r"],
# ["e", "a"], ["b", "s"], ["b", "r"], ["b", "a"], ["b", "e"]]
# size 3
# [["a", "r", "s"], ["e", "r", "s"], ["e", "a", "s"], ["e", "a", "r"],
# ["b", "r", "s"], ["b", "a", "s"], ["b", "a", "r"], ["b", "e", "s"],
# ["b", "e", "r"], ["b", "e", "a"]]
# size 4
# [["e", "a", "r", "s"], ["b", "a", "r", "s"], ["b", "e", "r", "s"],
# ["b", "e", "a", "s"], ["b", "e", "a", "r"]]
# size 5
# [["b", "e", "a", "r", "s"]]
power(input, ac)
#=> [[], ["s"], ["r"], ["r", "s"], ["a"], ["a", "s"], ["a", "r"],
# ["a", "r", "s"], ["e"], ["e", "s"], ["e", "r"], ["e", "r", "s"],
# ["e", "a"], ["e", "a", "s"], ["e", "a", "r"], ["e", "a", "r", "s"],
# ["b"], ["b", "s"], ["b", "r"], ["b", "r", "s"], ["b", "a"],
# ["b", "a", "s"], ["b", "a", "r"], ["b", "a", "r", "s"], ["b", "e"],
# ["b", "e", "s"], ["b", "e", "r"], ["b", "e", "r", "s"], ["b", "e", "a"],
# ["b", "e", "a", "s"], ["b", "e", "a", "r"], ["b", "e", "a", "r", "s"]]
Gentlemen, start your engines!
Methods compared
module Methods
def ruby(array)
(0..array.size).each_with_object([]) { |i,a|
a.concat(array.combination(i).to_a) }
end
def todd(input)
permutations(input) << []
end
private
def permutations(items)
return [] unless items.any?
prefix = items[0]
suffixes = permutations(items[1..-1])
[[prefix]] + suffixes + suffixes.map {|item| [prefix, item].flatten }
end
public
def fl00r(array)
(1..array.size).each_with_object([]) { |i,a|
a.concat(combinations(array, i)) } << []
end
private
def combinations(array, size)
fail "size is too big" if size > array.size
combination([], [], array, size)
end
def combination(result, step, array, size)
steps = size - step.size
array[0..-steps].each_with_index do |a, i|
next_step = step + [a]
if next_step.size < size
combination(result, next_step, array[i+1..-1], size)
else
result << next_step
end
end
result
end
public
def cary(input)
ac = all_combos(input.size)
ac.map { |c| input.values_at(*c) }
end
private
def all_combos(sz)
[*0..2**sz-1].map { |i| ("%0#{sz}b" % i).chars }
.map { |a| a.each_with_index.select { |n,ndx| n=="1" }.map(&:last) }
end
end
Test data
def test_array(n)
[*1..n]
end
Helpers
def compute(arr, meth)
send(meth, arr)
end
def compute_sort(arr, meth)
compute(arr, meth).map(&:sort).sort
end
Include module
include Methods
#methods = Methods.public_instance_methods(false)
#=> [:ruby, :todd, :fl00r, :cary]
Confirm methods return the same values
arr = test_array(8)
a = compute_sort(arr, #methods.first)
puts #methods[1..-1].all? { |m| a == compute_sort(arr, m) }
#=> true
Benchmark code
require 'benchmark'
#indent = methods.map { |m| m.to_s.size }.max
[10, 15, 20].each do |n|
puts "\nn = #{n}"
arr = test_array(n)
Benchmark.bm(#indent) do |bm|
#methods.each do |m|
bm.report m.to_s do
compute(arr, m).size
end
end
end
end
Tests (seconds)
n = 10
user system total real
ruby 0.000000 0.000000 0.000000 ( 0.000312)
todd 0.000000 0.000000 0.000000 ( 0.001611)
fl00r 0.000000 0.000000 0.000000 ( 0.002675)
cary 0.010000 0.000000 0.010000 ( 0.010026)
n = 15
user system total real
ruby 0.010000 0.000000 0.010000 ( 0.010742)
todd 0.070000 0.010000 0.080000 ( 0.081821)
fl00r 0.080000 0.000000 0.080000 ( 0.076030)
cary 0.430000 0.020000 0.450000 ( 0.450382)
n = 20
user system total real
ruby 0.310000 0.040000 0.350000 ( 0.350484)
todd 2.360000 0.130000 2.490000 ( 2.487493)
fl00r 2.320000 0.090000 2.410000 ( 2.405377)
cary 21.420000 0.620000 22.040000 ( 22.053303)
I draw only one definitive conclusion.

Resources