涉及哈希的ruby语法代码

时间:2015-04-08 01:04:58

标签: ruby arrays syntax hash

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

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

3 个答案:

答案 0 :(得分:2)

逐行:

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

答案 1 :(得分:0)

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

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

0是起点。

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

模式调用最常用的元素,这就是下一行的作用:只选择那些等于最大值的元素。

答案 2 :(得分: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

我怀疑代码可能相当陈旧,因为在{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非常相似,唯一的区别是:

  • 没有必要将v从块中返回到each_with_object,就像inject一样(; v结尾inject的原因k 1}}'阻止);
  • 对象(v)的块变量跟随each_with_objectv,而injecteach_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返回的对象(有时称为“备忘录”)由块变量h表示,我用它来表示哈希键(“k”)为“钥匙”)。同样地,由于对象是哈希,我选择使用a作为其块变量。像许多其他人一样,我更喜欢保持块变量简短并使用指示对象类型的名称(例如,h表示数组,s表示哈希,sym表示字符串,{{1} } for symbol,等等。)

现在假设:

array = [1,1]

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

更好:使用计数哈希

这仍然非常低效 - 所有这些array.uniq...。这是一种阅读方式更好,效率更高的方法:

counts

让我们看一下血淋淋的细节。首先,Hash#new的文档读取“如果指定了array.each_with_object(Hash.new(0)) { |k,h| h[k] += 1 } #=> {1=>3, 2=>2, 4=>1, 3=>1} [即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

非常酷,嗯?

我们可以使用Enumerator#next来模拟这个。 enum.each { |k,h| h[k] += 1 } => {1=>3, 2=>2, 4=>1, 3=>1} 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} each的第二个值传递到块中,并执行类似的计算:

enum

传递k,h = enum.next #=> [2, {1=>1}] k #=> 2 h #=> {1=>1} h[k] += 1 #=> 1 h #=> {1=>1, 2=>1} 的第三个元素时情况会有所不同,因为enum现在有一个键h

1

其余的计算也是类似的。