将Ruby数组元素转换为索引计数的哈希表

3

给定一个 Ruby 的二维数组:

[ [1, 1, 1],
  [1, 1],
  [1, 1, 1, 1],
  [1, 1]
]

我想创建一个哈希表,其中键是每个内部数组的计数,而值则是原始数组中其内部数组大小具有特定计数的索引数组。生成的哈希表如下所示:
{ 2 => [1, 3], 3 => [0], 4 => [2] }

我该如何用 Ruby 简洁地表达这个功能?我正在尝试类似于 Hash.new([]).tap { |h| array.each_with_index { |a, i| h[a.length] << i } },但生成的 Hash 是空的。
2个回答

5
你的代码有两个问题。第一个问题是当h为空时,你写下了h[2] << 1,因为h没有键2h[2]返回默认值,所以这个表达式变成了[] << 1 #=> [1],但[1]没有连接到哈希表,因此没有添加键和值。
你需要写h[2] = h[2] << 11。如果你这样做,你的代码将返回h #=> {3=>[0, 1, 2, 3], 2=>[0, 1, 2, 3], 4=>[0, 1, 2, 3]}。不幸的是,这仍然是不正确的,这带我们来到你的代码的第二个问题:你没有正确定义新创建的哈希表的默认值。
首先请注意,
h[3].object_id
  #=> 70113420279440 
h[2].object_id
  #=> 70113420279440 
h[4].object_id
  #=> 70113420279440 

啊哈,所有三个值都是同一个对象! 当h没有键k时,new的参数[]h[k]返回。问题在于对于添加到哈希表的所有键k都返回相同的数组,因此您将为第一个新键添加一个键值对到一个空数组中,然后为下一个新键将第二个键值对添加到相同的数组中,依此类推。请参见下面如何定义哈希表。
通过这两个更改,您的代码可以正常工作,但我建议按以下方式编写它。
arr = [ [1, 1, 1], [1, 1], [1, 1, 1, 1], [1, 1] ]

arr.each_with_index.with_object(Hash.new {|h,k| h[k]=[]}) { |(a,i),h|
  h[a.size] << i }
  #=> {3=>[0], 2=>[1, 3], 4=>[2]} 

使用形式为Hash::new的哈希表,它使用一个块来计算哈希表的默认值(即当哈希表h没有键k时 h[k] 返回的值)。
或者
arr.each_with_index.with_object({}) { |(a,i),h| (h[a.size] ||= []) << i }
  #=> {3=>[0], 2=>[1, 3], 4=>[2]} 

这两者实际上是以下内容:

h = {}
arr.each_with_index do |a,i|
  sz = a.size
  h[sz] = [] unless h.key?(sz)
  h[a.size] << i
end
h #=> {3=>[0], 2=>[1, 3], 4=>[2]} 

另一种方法是使用Enumerable#group_by,按数组大小进行分组,然后获取每个内部数组的索引。
h = arr.each_with_index.group_by { |a,i| a.size }
  #=> {3=>[[[1, 1, 1], 0]],
  #    2=>[[[1, 1], 1], [[1, 1], 3]],
  #    4=>[[[1, 1, 1, 1], 2]]} 
h.each_key { |k| h[k] = h[k].map(&:last) }
  #=> {3=>[0], 2=>[1, 3], 4=>[2]}

1表达式h[2] = h[2] << 1使用方法Hash#[]=Hash#[],这就是为什么在等号左侧的h[2]不返回默认值。该表达式可以改写为h[2] ||= [] << 1


啊,原来是这样链接的。我先尝试了 arr.with_object,但失败了。 - ybakos
2
非常有用的#with_object,避免了我在答案中使用的代码味道,需要在块外声明哈希。再次感谢您,Cary。能够从像您这样的大师那里学习是一种特权。 - Ed de Almeida
@ybakos with_object 只能用于 Enumerator 对象。或者你可以说 Array 类没有定义 with_object 方法。 - Sagar Pandya
@sagarpandya82 with_object 是用于 Enumerator 的,但是有 Array#each_with_object(和 Enumerable#each_with_object)。同样的,with_indexeach_with_index 也是如此。 - mu is too short

3
arry = [ [1, 1, 1],
         [1, 1],
         [1, 1, 1, 1],
         [1, 1]
       ]

h = {}
arry.each_with_index do |el,i|
  c = el.count
  h.has_key?(c) ? h[c] << i : h[c] = [i]
end

p h

这将为您提供:
{3=>[0], 2=>[1, 3], 4=>[2]}

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