У меня есть следующий набор данных:
{
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
}
Подожди, Джаред! Любая программа на Ruby может быть написана в одну строку, если разрешены точки с запятой! :-)
input = {
'Nov 2020' => 1,
'Dec 2020' => 2,
'Jan 2021' => 3,
'Feb 2021' => 4,
'Mar 2021' => 5,
'Apr 2021' => 6
}
Если, как в примере, значения начинаются с 1
и каждое после первого на 1
больше, чем предыдущее значение (напомнить порядок вставки ключ/значение гарантируется в хэшах), значение 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}
Обратите внимание, что для этого не требуется, чтобы Хэш#transform_values обрабатывал пары ключ-значение в каком-либо конкретном порядке.
Это поможет:
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
), чтобы сохранить обработанные данные и позволить нам легко получить значение предыдущей пары, что позволяет нам накапливать значение. В конце мы преобразуем этот массив в хэш, чтобы получить нужную структуру данных.
Просто чтобы добавить отказ от ответственности, поскольку обрабатываемые данные являются хэшем (и, следовательно, не структурой данных, сохраняющей порядок), полный однострочный вариант, учитывающий также хэш, игнорирующий любой возможный порядок, будет следующим:
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}
В этом случае мы применяем ту же идею, но сначала преобразуем исходные данные в упорядоченный массив по дате.
Вот к чему я в конечном итоге пришел:
input.each_with_object({}) { |(k, v), h| h[k] = v + h.values.last.to_i }
Снимаю шляпу перед Маркос Паррейрас (принятый ответ) за то, что он указал мне направление 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 }
Я остановился на варианте с использованием хеша, потому что считаю его самым чистым. Однако стоит отметить, что он не самый производительный. Основываясь исключительно на производительности, исходное решение является бесспорным победителем. Естественно, все они очень быстрые, поэтому, чтобы действительно увидеть разницу в производительности, мне пришлось запускать параметры очень много раз (показано ниже). Но поскольку мое фактическое решение будет запускаться только один раз в продакшене, я решил добиться краткости, превышающей наносекунды производительности. :-)
Каждое решение запускалось внутри 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>
Требуется ли поместить это в одну строку или важнее удобство чтения? Можно и в одну строку, но я думаю, что двух- или трехстрочные решения будут читабельнее.