Stefan
Stefan

Reputation: 114258

Method for splitting array into (element => remaining elements) pairs

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

Answers (5)

Cary Swoveland
Cary Swoveland

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

Cary Swoveland
Cary Swoveland

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

Arup Rakshit
Arup Rakshit

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

tessi
tessi

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

Piotr Kruczek
Piotr Kruczek

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

Related Questions