我正在查看有关如何从数组返回模式的代码,我遇到了这段代码:
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很新,并且不完全理解这里是如何使用哈希的。任何帮助将不胜感激。
答案 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_object
与inject
非常相似,唯一的区别是:
v
从块中返回到each_with_object
,就像inject
一样(; v
结尾inject
的原因k
1}}'阻止); 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
返回的对象(有时称为“备忘录”)由块变量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
其余的计算也是类似的。