Код
def group_em(h)
h.group_by { |_,v| v.drop(1) }.
transform_values do |a|
a.transpose.
last.
map(&:first).
sum(&:to_i).
to_s
end.
each_with_index.
with_object({}) { |((a,v),i),g| g[i] = [v,*a] }
end
Пример
h = {
"0" => ["1", "true", "21"],
"1" => ["2", "true", "21"],
"2" => ["3", "false", "21"],
"3" => ["4", "true", "22"],
"4" => ["5", "true", "22"],
"5" => ["6", "true", "22"],
"6" => ["7", "false", "21"]
}
group_em(h)
#=> {0=>["3", "true", "21"],
# 1=>["10", "false", "21"],
# 2=>["15", "true", "22"]}
Пояснение
Основные шаги
Для хэша h
выше основные шаги следующие:
p = h.group_by { |_,v| v.drop(1) }
#=> {["true", "21"]=>[["0", ["1", "true", "21"]],
# ["1", ["2", "true", "21"]]],
# ["false", "21"]=>[["2", ["3", "false", "21"]],
# ["6", ["7", "false", "21"]]],
# ["true", "22"]=>[["3", ["4", "true", "22"]],
# ["4", ["5", "true", "22"]],
# ["5", ["6", "true", "22"]]]}
q = p.transform_values do |a|
a.transpose.
last.
map(&:first).
sum(&:to_i).
to_s
end
#=> {["true", "21"]=>"3", ["false", "21"]=>"10", ["true", "22"]=>"15"}
enum0 = q.each_with_index
#=> #<Enumerator: {["true", "21"]=>"3", ["false", "21"]=>"10",
# ["true", "22"]=>"15"}:each_with_index>
enum1 = enum0.with_object({})
#=> #<Enumerator: #<Enumerator: {["true", "21"]=>"3", ["false", "21"]=>"10",
# ["true", "22"]=>"15"}:each_with_index>:with_object({})>
enum1.each { |((a,v),i),g| g[i] = [v,*a] }
#=> {0=>["3", "true", "21"],
# 1=>["10", "false", "21"],
# 2=>["15", "true", "22"]}
Мы можем видеть значения, которые будут сгенерированы и переданы в блокперечислителем enum1
путем преобразования его в массив:
enum1.to_a
#=> [[[[["true", "21"], "3"], 0], []],
# [[[["false", "21"], "10"], 1], []],
# [[[["true", "22"], "15"], 2], []]]
Если вы сравните возвращаемое значение для enum0
с значением enum1
, вы можете думать о последнем как о составной перечислитель , хотя в Ruby этот термин не используется.
Подробная информация о Hash#transform_values
Теперь давайте более подробно рассмотрим вычисление q
.Первое значение p
передается в блок Hash # transform_values (который дебютировал в MRI 2.4) и становится значением переменной блока a
:
a = p.first.last
#=> [["0", ["1", "true", "21"]], ["1", ["2", "true", "21"]]]
Расчеты блока выполняются следующим образом.
b = a.transpose
#=> [["0", "1"], [["1", "true", "21"], ["2", "true", "21"]]]
c = b.last
#=> [["1", "true", "21"], ["2", "true", "21"]]
d = c.map(&:first) # ~same as c.map { |a| a.first }
#=> ["1", "2"]
e = e.sum(&:to_i) # ~same as e.sum { |s| s.to_i }
#=> 3
e.to_s
#=> "3"
Мы видим, что значение a
было преобразовано в "3"
.Остальные вычисления для вычисления q
аналогичны.
Ссылки на документацию
Документацию по методам, которые я использовал, можно найти по следующим ссылкам для классов Массив (drop
, transpose
, last
, first
и sum
), Целое число (to_s
), Строка (to_i
) и Перечислитель (with_object
и next
) и модуль Перечислимый (group_by
, map
и each_with_index
).
Разложение вложенных объектов
Есть еще один хитрый момент, о котором я хотел бы упомянуть.Это строка
enum1.each { |((a,v),i),g| g[i] = [v,*a] }
. Я записал переменные блока таким образом, чтобы разложить значения, сгенерированные перечислителем enum1
и переданные в блок.Я уверен, что это должно выглядеть довольно внушительно для новичка, но это не так плохо, если вы будете делать шаг за шагом, как я объясню.
Во-первых, предположим, что у меня была единственная переменная блока r
(enum1.each { |r|...}
).Первое значение генерируется и передается в блок, присваивая значение r
:
r = enum1.next
#=> [[[["true", "21"], "3"], 0], []]
Затем мы можем выполнить следующую инструкцию в блоке для декомпозиции (из однозначность )r
следующим образом:
((a,v),i),g = r
#=> [[[["true", "21"], "3"], 0], []]
с выполнением следующих назначений:
a #=> ["true", "21"]
v #=> "3"
i #=> 0
g #=> []
Эквивалентно и проще заменить |r|
в блоке на |((a,v),i),g|
.
Если вы изучите расположение скобок во вложенном массиве, создаваемом enum1.next
, вы увидите, как я определил, где мне нужны круглые скобки при записи переменных блока.Эта декомпозиция вложенных массивов и других объектов является очень удобной и мощной функцией или Ruby, которая сильно не используется.