涉及哈希的 Ruby 语法代码



我正在查看有关如何从数组返回模式的代码,我遇到了以下代码:

def mode(array)
    answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}
    answer.select { |k,v| v == answer.values.max}.keys

end

我试图概念化语法背后的含义,因为我对 Ruby 相当陌生,并不完全了解哈希在这里的使用方式。任何帮助将不胜感激。

逐行:

answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}

这将组装计数哈希。我不会称变量为answer因为它不是答案,它是一个中间步骤。inject()方法(也称为reduce()(允许您迭代集合,保留累加器(例如,运行总计或在这种情况下哈希收集计数(。它需要起始值 {},以便在尝试存储值时存在哈希。给定数组[1,2,2,2,3,4,5,6,6]计数将如下所示:{1=>1, 2=>3, 3=>1, 4=>1, 5=>1, 6=>2} .

answer.select { |k,v| v == answer.values.max}.keys

这将选择上述哈希值等于最大值(换句话说,最大值(的所有元素。然后,它标识与最大值关联的keys。请注意,如果它们共享最大值,它将列出多个值。

另一种选择:

如果您不关心返回多个,则可以按如下方式使用group_by:

array.group_by{|x|x}.values.max_by(&:size).first

或者,在 Ruby 2.2+ 中:

array.group_by{&:itself}.values.max_by(&:size).first

inject 方法的作用类似于累加器。下面是一个更简单的示例:

sum = [1,2,3].inject(0) { |current_tally, new_value| current_tally + new_value }

0 是起点。

因此,在第一行之后,我们有一个哈希值,将每个数字映射到它出现的次数。

该模式调用最频繁的元素,这就是下一行的作用:仅选择等于最大值的元素。

我相信

你的问题已经得到了回答,@Mark提到了不同的计算方法。我只想关注改进第一行代码的其他方法:

answer = array.inject ({}) { |k, v| k[v] = array.count(v); k }

首先,让我们创建一些数据:

array = [1,2,1,4,3,2,1]

使用each_with_object而不是inject

我怀疑代码可能相当旧,因为在 v. 1.9 中引入的 Enumerable#each_with_object 可以说是比 Enumerable#inject(又名 reduce(更好的选择。如果我们要使用each_with_object,第一行将是:

answer = array.each_with_object ({}) { |v,k| k[v] = array.count(v) }
  #=> {1=>3, 2=>2, 4=>1, 3=>1}

each_with_object返回对象,块变量 v 持有的哈希。

如您所见,each_with_objectinject非常相似,唯一的区别是:

    没有必要
  • inject那样将v从块返回到each_with_object(inject块末尾那个烦人; v的原因(;
  • 对象的块变量(k(跟在v后面each_with_object,而它跟v inject;和
  • 当未给定块时,each_with_object返回一个枚举器,这意味着它可以链接到其他方法(例如,arr.each_with_object.with_index ... .

不要误会我的意思,inject仍然是一种非常强大的方法,在许多情况下它是无与伦比的。

另外两项改进

除了将inject替换为 each_with_object 之外,让我做另外两个更改:

answer = array.uniq.each_with_object ({}) { |k,h| h[k] = array.count(k) }
  #=> {1=>3, 2=>2, 4=>1, 3=>1} 

在原始表达式中,inject返回的对象(有时称为"备忘录"(由块变量 k 表示,我用它来表示哈希键("k"表示"键"(。同样,由于对象是一个哈希,我选择使用 h 作为其块变量。像许多其他变量一样,我更喜欢保持块变量简短,并使用指示对象类型的名称(例如,a 表示数组,h 表示哈希,s 表示字符串,sym 表示符号等(。

现在假设:

array = [1,1]

然后inject会将第一个1传递到块中,然后计算k[1] = array.count(1) #=> 2,因此返回给inject的哈希k将被{1=>2}。然后它会将第二个1传递到块中,再次计算k[1] = array.count(1) #=> 2,用1=>1覆盖k中的1=>1;也就是说,根本不改变它。对于array的唯一值这样做不是更有意义吗?这就是为什么我有:array.uniq....

更好的是:使用计数哈希

这仍然效率很低 - 所有这些counts。这是一种阅读效果更好且可能更有效的方法:

array.each_with_object(Hash.new(0)) { |k,h| h[k] += 1 }
  #=> {1=>3, 2=>2, 4=>1, 3=>1} 

让我们来看看这个血腥的细节。首先,Hash#new 的文档显示:"如果指定了obj[即Hash.new(obj)],则此单个对象将用于所有默认值。这意味着如果:

h = Hash.new('cat')

h没有密钥dog,那么:

h['dog'] #=> 'cat'

重要提示:最后一个表达式经常被误解。它仅返回默认值。 str = "It does *not* add the key-value pair 'dog'=>'cat' to the hash." 让我重复一遍:puts str.

现在让我们看看这里发生了什么:

enum = array.each_with_object(Hash.new(0))
  #=> #<Enumerator: [1, 2, 1, 4, 3, 2, 1]:each_with_object({})> 

我们可以通过将其转换为数组来查看枚举器的内容:

enum.to_a
  #=> [[1, {}], [2, {}], [1, {}], [4, {}], [3, {}], [2, {}], [1, {}]] 

这七个元素通过方法传递到块中 each

enum.each { |k,h| h[k] += 1 }
  => {1=>3, 2=>2, 4=>1, 3=>1}

很酷,是吗?

我们可以使用 Enumerator#next 来模拟这一点。enum([1, {}](的第一个值被传递给块并分配给块变量:

k,h = enum.next
  #=> [1, {}] 
k #=> 1 
h #=> {} 

我们计算:

h[k] += 1
  #=> h[k] = h[k] + 1  (what '+=' means)
  #        = 0 + 1 = 1 (h[k] on the right equals the default value
  #                     of 1 since `h` has no key `k`) 

所以现在:

h #=> {1=>1}

接下来,eachenum的第二个值传递到块中,并执行类似的计算:

k,h = enum.next
  #=> [2, {1=>1}] 
k #=> 2 
h #=> {1=>1} 
h[k] += 1
  #=> 1 
h #=> {1=>1, 2=>1} 

当传入enum的第三个元素时,情况会有所不同,因为h现在有一个关键1

k,h = enum.next
  #=> [1, {1=>1, 2=>1}] 
k #=> 1 
h #=> {1=>1, 2=>1} 
h[k] += 1
  #=> h[k] = h[k] + 1
  #=> h[1] = h[1] + 1  
  #=> h[1] = 1 + 1 => 2
h #=> {1=>1, 2=>1} 

其余计算的执行方式类似。

最新更新