Reputation: 114258
Given an array literal, I would like to create a hash where the keys are the elements from the array and the values are arrays containing the other / remaining elements.
Input:
[1, 2, 3]
Output:
{1=>[2, 3], 2=>[1, 3], 3=>[1, 2]}
It's easy if I introduce a variable:
arr = [1, 2, 3]
arr.map { |i| [i, arr - [i]] }.to_h
But with an array literal, the only solution I could come up with involves instance_exec
or instance_eval
, which seems hackish:
[1, 2, 3].instance_exec { map { |i| [i, self - [i]] } }.to_h
Am I overlooking a built-in method or an obvious solution? group_by
, combination
, permutation
and partition
don't seem to help.
Upvotes: 12
Views: 498
Reputation: 110755
[1,2,3,4].each_with_object({}) do |n,h|
h.each_key { |k| h[k] << n }
h[n] = h.keys
end
#=> {1=>[2, 3, 4], 2=>[1, 3, 4], 3=>[1, 2, 4], 4=>[1, 2, 3]}
Upvotes: 1
Reputation: 110755
Here are three ways to use Object#tap. Is there an argument that prohibits its use?
All three methods work if the array contains dups; for example:
[1,2,2]....
#=> {1=>[1, 2], 2=>[1, 1]}
#1
[1,2,2].tap do |a|
a.replace(a.cycle.each_cons(a.size).first(a.size).map { |k,*v| [k,v] })
end.to_h
#=> {1=>[2, 3], 2=>[3, 1], 3=>[1, 2]}
#2
[1,2,3].tap do |a|
@h = a.map do |i|
b = a.dup
j = b.index(i)
b.delete_at(j)
[i,b]
end.to_h
end
@h #=> {1=>[2, 3], 2=>[1, 3], 3=>[1, 2]}
#3
[1,2,3].map.with_index { |*e| e.reverse }.to_h.tap do |h|
a = h.values
h.replace(a.each_with_object({}) do |e,g|
b = a.dup
i = b.index(e)
b.delete_at(i)
g.update(e=>b)
end)
end
#=> {1=>[2, 3], 2=>[1, 3], 3=>[1, 2]}
Addendum
The code in the latter two methods can be simplified by using the much-needed method Array#difference
, as defined it in my answer here. #3, for example, becomes:
[1,2,3].map.with_index { |*e| e.reverse }.to_h.tap do |h|
a = h.values
h.replace(a.each_with_object({}) { |e,g| g.update(e=>a.difference([e])) })
end
#=> {1=>[2, 3], 2=>[1, 3], 3=>[1, 2]}
Upvotes: 1
Reputation: 118299
I got another idea. Here is this :
a = [1, 2, 3]
a.combination(2).with_object({}) { |ar, h| h[(a - ar).first] = ar }
# => {3=>[1, 2], 2=>[1, 3], 1=>[2, 3]}
A modified version of Piotr Kruczek .
[1,2,3].permutation.with_object({}) { |(k, *v), h| h[k] = v }
# => {1=>[3, 2], 2=>[3, 1], 3=>[2, 1]}
Upvotes: 2
Reputation: 13574
I'd go with Piotr's solution, but for the fun of it, I have a different approach:
[1,2,3].inject([[],{}]) do |h_a, i|
h_a[0] << i
h_a[1].default_proc = ->(h,k){ h_a[0] - [k]}
h_a
end.last
It's much more of a hack and less elegant, though.
Upvotes: 1
Reputation: 2390
I've come up with something like this:
[1,2,3].permutation.to_a.map{ |e| [e.shift, e] }.to_h
However this has a flaw: it assigns the same key many times, but since you don't care about the sequence of the elements inside this might be a "good enough" solution.
Upvotes: 6