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_object
与 inject
非常相似,唯一的区别是:
- 没有必要 return
v
从块到 each_with_object
,因为它是 inject
(那个烦人的原因 ; v
在 inject
块的末尾);
- 对象 (
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}
接下来,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
:
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}
其余计算类似。
我正在查看有关如何从数组中 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_object
与 inject
非常相似,唯一的区别是:
- 没有必要 return
v
从块到each_with_object
,因为它是inject
(那个烦人的原因; v
在inject
块的末尾); - 对象 (
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}
接下来,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
:
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}
其余计算类似。