Как сделать однострочный кумулятивный подсчет хеш-значений в 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 безуспешно.

Требуется ли поместить это в одну строку или важнее удобство чтения? Можно и в одну строку, но я думаю, что двух- или трехстрочные решения будут читабельнее.

Jared Beck 05.04.2022 04:35

Более того, просто любопытно, можно ли это сделать красноречиво/читабельно в одну строку. Это не требование.

jeffdill2 05.04.2022 04:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
75
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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 может быть написана в одну строку, если разрешены точки с запятой! :-)

Cary Swoveland 05.04.2022 10:18
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}

В этом случае мы применяем ту же идею, но сначала преобразуем исходные данные в упорядоченный массив по дате.

TL;DR

Вот к чему я в конечном итоге пришел:

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>

Другие вопросы по теме