如何对Ruby中的hash值进行单行累计计数?
How to do a single-line cumulative count for hash values in Ruby?
我得到了以下数据集:
{
Nov 2020=>1,
Dec 2020=>2,
Jan 2021=>3,
Feb 2021=>4,
Mar 2021=>5,
Apr 2021=>6
}
使用以下代码:
cumulative_count = 0
count_data = {}
data_set.each { |k, v| count_data[k] = (cumulative_count += v) }
我正在生成以下数据集:
{
Nov 2020=>1,
Dec 2020=>3,
Jan 2021=>6,
Feb 2021=>10,
Mar 2021=>15,
Apr 2021=>21
}
尽管我已经将 each
作为一行,但我觉得必须有某种方法可以将整个事情作为一行来完成。我试过使用 inject
但没有成功。
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
如果必须在物理行,并且允许分号:
t = 0; input.each_with_object({}) { |(k, v), a| t += v; a[k] = t }
如果它必须在 物理 行上,并且 不允许 分号:
input.each_with_object({ t: 0, data: {}}) { |(k, v), a| (a[:t] += v) and (a[:data][k] = a[:t]) }[:data]
但在实际操作中,我认为在多个 物理 行上阅读更容易 :)
t = 0
input.each_with_object({}) { |(k, v), a|
t += v
a[k] = t
}
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
如果,如示例所示,值从 1
开始,并且第一个值之后的每个值都比前一个值大 1
(回想一下 key/value 插入顺序在哈希中得到保证),值 n
将转换为 1 + 2 +...+ n
,它是等差级数的总和,等于以下内容。
input.transform_values { |v| (1+v)*v/2 }
#=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10,
# "Mar 2021"=>15, "Apr 2021"=>21}
请注意,这不需要 Hash#transform_values 以任何特定顺序处理 key-value 对。
这样做就可以了:
input.each_with_object([]) { |(key, value), arr| arr << [key, arr.empty? ? value : value + arr.last[1]] }.to_h
=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10, "Mar 2021"=>15, "Apr 2021"=>21}
输入定义为:
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
想法是注入一个数组(通过each_with_object
)来保存处理后的数据,让我们很容易地得到前一对的值,从而让我们积累价值.最后,我们将这个数组转化为哈希,这样我们就有了我们想要的数据结构。
只是添加免责声明,因为正在处理的数据是哈希(因此不是保留顺序的数据结构),完整的 one-liner 也考虑哈希忽略任何可能的排序将是以下:
input.to_a.sort_by { |pair| Date.parse(pair[0]) }.each_with_object([]) { |pair, arr| arr << [pair[0], arr.empty? ? pair[1] : pair[1] + arr.last[1]] }.to_h
=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10, "Mar 2021"=>15, "Apr 2021"=>21}
在本例中,我们采用相同的思路,但首先将原始数据转换为按日期排序的数组。
TL;DR
这就是我最终的结果:
input.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
向 Marcos Parreiras(已接受的答案)致敬,因为它为我指明了 each_with_object
的方向,并提出了提取最后一个值进行累加而不是使用 +=
的想法累积变量初始化为 0.
详情
我最终得到了 3 个可能的解决方案(如下所列)。我的原始代码加上两个使用 each_with_object
的选项 – 其中一个取决于数组,另一个取决于散列。
原创
cumulative_count = 0
count_data = {}
input.each { |k, v| count_data[k] = (cumulative_count += v) }
使用数组
input.each_with_object([]) { |(k, v), a| a << [k, v + a.last&.last.to_i] }.to_h
使用散列
input.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
我选择了使用散列的选项,因为我认为它是最干净的。但是,值得注意的是,它并不是性能最高的。纯粹基于性能,原始解决方案是 hands-down 获胜者。当然,它们都非常快,所以为了真正看到性能差异,我不得不 运行 多次选择这些选项(如下所示)。但是由于我的实际解决方案在生产中一次只会 运行 一次,所以我决定在纳秒级的性能上追求简洁。 :-)
性能
每个解决方案 运行 在 2_000_000.times { }
内。
原创
#<Benchmark::Tms:0x00007fde00fb72d8 @real=2.5452079999959096, @stime=0.09558999999999962, @total=2.5108440000000005, @utime=2.415254000000001>
使用数组
#<Benchmark::Tms:0x00007fde0a1f58e8 @real=7.3623509999597445, @stime=0.08986500000000053, @total=7.250730000000002, @utime=7.160865000000001>
使用散列
#<Benchmark::Tms:0x00007f9e19ca7678 @real=5.903417999972589, @stime=0.057482000000000255, @total=5.830285999999999, @utime=5.772803999999999>
我得到了以下数据集:
{
Nov 2020=>1,
Dec 2020=>2,
Jan 2021=>3,
Feb 2021=>4,
Mar 2021=>5,
Apr 2021=>6
}
使用以下代码:
cumulative_count = 0
count_data = {}
data_set.each { |k, v| count_data[k] = (cumulative_count += v) }
我正在生成以下数据集:
{
Nov 2020=>1,
Dec 2020=>3,
Jan 2021=>6,
Feb 2021=>10,
Mar 2021=>15,
Apr 2021=>21
}
尽管我已经将 each
作为一行,但我觉得必须有某种方法可以将整个事情作为一行来完成。我试过使用 inject
但没有成功。
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
如果必须在物理行,并且允许分号:
t = 0; input.each_with_object({}) { |(k, v), a| t += v; a[k] = t }
如果它必须在 物理 行上,并且 不允许 分号:
input.each_with_object({ t: 0, data: {}}) { |(k, v), a| (a[:t] += v) and (a[:data][k] = a[:t]) }[:data]
但在实际操作中,我认为在多个 物理 行上阅读更容易 :)
t = 0
input.each_with_object({}) { |(k, v), a|
t += v
a[k] = t
}
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
如果,如示例所示,值从 1
开始,并且第一个值之后的每个值都比前一个值大 1
(回想一下 key/value 插入顺序在哈希中得到保证),值 n
将转换为 1 + 2 +...+ n
,它是等差级数的总和,等于以下内容。
input.transform_values { |v| (1+v)*v/2 }
#=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10,
# "Mar 2021"=>15, "Apr 2021"=>21}
请注意,这不需要 Hash#transform_values 以任何特定顺序处理 key-value 对。
这样做就可以了:
input.each_with_object([]) { |(key, value), arr| arr << [key, arr.empty? ? value : value + arr.last[1]] }.to_h
=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10, "Mar 2021"=>15, "Apr 2021"=>21}
输入定义为:
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
想法是注入一个数组(通过each_with_object
)来保存处理后的数据,让我们很容易地得到前一对的值,从而让我们积累价值.最后,我们将这个数组转化为哈希,这样我们就有了我们想要的数据结构。
只是添加免责声明,因为正在处理的数据是哈希(因此不是保留顺序的数据结构),完整的 one-liner 也考虑哈希忽略任何可能的排序将是以下:
input.to_a.sort_by { |pair| Date.parse(pair[0]) }.each_with_object([]) { |pair, arr| arr << [pair[0], arr.empty? ? pair[1] : pair[1] + arr.last[1]] }.to_h
=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10, "Mar 2021"=>15, "Apr 2021"=>21}
在本例中,我们采用相同的思路,但首先将原始数据转换为按日期排序的数组。
TL;DR
这就是我最终的结果:
input.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
向 Marcos Parreiras(已接受的答案)致敬,因为它为我指明了 each_with_object
的方向,并提出了提取最后一个值进行累加而不是使用 +=
的想法累积变量初始化为 0.
详情
我最终得到了 3 个可能的解决方案(如下所列)。我的原始代码加上两个使用 each_with_object
的选项 – 其中一个取决于数组,另一个取决于散列。
原创
cumulative_count = 0
count_data = {}
input.each { |k, v| count_data[k] = (cumulative_count += v) }
使用数组
input.each_with_object([]) { |(k, v), a| a << [k, v + a.last&.last.to_i] }.to_h
使用散列
input.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
我选择了使用散列的选项,因为我认为它是最干净的。但是,值得注意的是,它并不是性能最高的。纯粹基于性能,原始解决方案是 hands-down 获胜者。当然,它们都非常快,所以为了真正看到性能差异,我不得不 运行 多次选择这些选项(如下所示)。但是由于我的实际解决方案在生产中一次只会 运行 一次,所以我决定在纳秒级的性能上追求简洁。 :-)
性能
每个解决方案 运行 在 2_000_000.times { }
内。
原创
#<Benchmark::Tms:0x00007fde00fb72d8 @real=2.5452079999959096, @stime=0.09558999999999962, @total=2.5108440000000005, @utime=2.415254000000001>
使用数组
#<Benchmark::Tms:0x00007fde0a1f58e8 @real=7.3623509999597445, @stime=0.08986500000000053, @total=7.250730000000002, @utime=7.160865000000001>
使用散列
#<Benchmark::Tms:0x00007f9e19ca7678 @real=5.903417999972589, @stime=0.057482000000000255, @total=5.830285999999999, @utime=5.772803999999999>