ruby 涉及哈希的语法代码

ruby syntax code involving hashes

我正在查看有关如何从数组中 return 模式的代码,我 运行 进入此代码:

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 还很陌生,并不完全理解哈希在这里是如何使用的。任何帮助将不胜感激。

inject 方法就像一个累加器。这是一个更简单的例子:

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

0是起点

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

模式要求最频繁的元素,这就是下一行所做的:只选择那些等于最大值的元素。

一行一行:

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

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

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

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

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

使用each_with_object代替inject

我怀疑代码可能相当旧,如 Enumerable#each_with_object, which was introduced in v. 1.9, is arguably a better choice here than 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 returns 对象,由块变量 v.

保存的哈希

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

  • 没有必要 return v 从块到 each_with_object,因为它是 inject(那个烦人的原因 ; vinject 块的末尾);
  • 对象 (k) 的块变量在 v 之后是 each_with_object,而它在 v 之后是 inject;和
  • 当没有给定块时,each_with_object return 是一个枚举器,这意味着它可以链接到其他其他方法(例如,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 编辑的对象 return(有时称为 "memo")由我正在使用的块变量 k 表示表示哈希键("k" 代表 "key")。 Simlarly,因为对象是一个散列,我选择使用 h 作为它的块变量。像许多其他人一样,我更喜欢保持块变量简短并使用指示对象类型的名称(例如,a 用于数组,h 用于散列,s 用于字符串,sym 表示符号,依此类推)。

现在假设:

array = [1,1]

然后 inject 会将第一个 1 传递到块中,然后计算 k[1] = array.count(1) #=> 2,因此哈希 k returned 到 inject 将是 {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 的文档读取,"If obj is specified [i.e., Hash.new(obj)], this single object will be used for all default values." 这意味着如果:

h = Hash.new('cat')

h没有keydog,则:

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

重要提示:最后一个表达式经常被误解。它只是 return 的默认值。 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} 

其余计算类似。