将数组拆分为(元素=>剩余元素)对的方法

12

给定一个 数组文字,我想创建一个哈希表,其中键是来自数组的元素,值是包含其他/剩余元素的数组。

输入:

[1, 2, 3]

输出:

{1=>[2, 3], 2=>[1, 3], 3=>[1, 2]}

如果我引入一个变量,这就很容易了:

arr = [1, 2, 3]
arr.map { |i| [i, arr - [i]] }.to_h

但是对于一个数组字面量,我能想到的唯一解决方案涉及到 instance_execinstance_eval,这似乎有些hackish:

[1, 2, 3].instance_exec { map { |i| [i, self - [i]] } }.to_h

我是否忽略了内置的方法或显而易见的解决方案?group_bycombinationpermutationpartition 似乎都无法帮助。


不相关,但由于您没有将任何参数传递给块,为什么不使用instance_eval - Andrey Deineko
@AndreyDeineko 有什么区别吗? - Stefan
1
[[1,2,3]] * 2 ⇒ 现在你有一个副本 :) - Aleksei Matiushkin
1
@mudasobwa tap 不会返回原始数组吗? - Stefan
对于 [1, 2, 3].tap { |a| a.map { |i| [i, a - [i]] }.to_h },将会... - Aleksei Matiushkin
显示剩余8条评论
5个回答

6
我想出了这样的内容:

我想到了像这样的东西:

[1,2,3].permutation.to_a.map{ |e| [e.shift, e] }.to_h

然而,这种方法存在一个缺陷:它会多次分配相同的键,但由于您不关心其中元素的顺序,这可能是一个“足够好”的解决方案。

1
这是一个有趣的解决方案。 - undur_gongor
确实,这是一个非常有创意的方法 :-) - Stefan
2
你可以省略 to_a 并使用 * 来拆分数组:[1,2,3].permutation.map { |k, *vs| [k, vs] }.to_h - Stefan

2
我有另一个想法。这里是它:

我有另一个想法。这里是它:

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

一个修改版的 Piotr Kruczek
[1,2,3].permutation.with_object({}) { |(k, *v), h| h[k] = v }
# => {1=>[3, 2], 2=>[3, 1], 3=>[2, 1]}

2
组合是我最初想到的,但问题在于作为参数,您必须传递 a.length-1,而整个重点不是将数组分配给变量 :/ - Piotr Kruczek
@PiotrKruczek 好的..我没有完整地阅读问题..所以,如果我将它分配给一个变量_life is easy_.. :) - Arup Rakshit

1

我会选择Piotr的解决方案,但为了好玩,我有一种不同的方法:

[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

但这更像是一种hack而不是优雅的解决方案。


尽管默认的proc返回了正确的值,但我无法枚举“keys”或使用“each”迭代哈希表。 - Stefan

1
这里有三种使用 Object#tap 的方法。是否有任何禁止使用它的参数?
如果数组包含重复项,则所有三种方法都有效;例如:
[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]}

附录

后两种方法中的代码可以通过使用非常必要的方法Array#difference来简化,这个方法在我的答案这里中定义。例如,第3个变成:

[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]}

tap 绝对比 instance_exec / instance_eval 好用 ;) - Stefan

1
[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]}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接