Сумма повторяющихся значений с несколькими значениями в хэше - PullRequest
0 голосов
/ 23 декабря 2018

Учитывая хэш, который выглядит следующим образом:

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"]
}

Я хочу найти сумму элементов в позиции 0 в массивах, которые имеют одинаковые элементы в индексах 1 и 2и вернуть хеш, как показано ниже:

{
  0 => ["3", "true", "21"],
  1 => ["10", "false", "21"],
  2 => ["15", "true", "22"]
}

Поскольку существует два массива с индексами 1 и 2, имеющих значения "true" и "21", я хочу суммировать целочисленные значенияИндекс 0 для этих двух массивов, например.

Как я могу преобразовать пример хеша в верхней части этого вопроса в результирующий хеш под ним?

Ответы [ 7 ]

0 голосов
/ 24 декабря 2018

ссылки: Enumerable # group_by , Перечислитель # with_index , Массив # to_h

key_sum = ->(group) { group.sum { |key, _| key.to_i }.to_s }
given_hash.values.group_by { |_, *rest| rest }. 
  map.with_index { |(key, group), idx| [idx, [key_sum.call(group), *key]] }.to_h
#=> {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}
  • группа

    given_hash.values.group_by { |_, *rest| rest }
    #=> { ["true", "21"] => [["1", "true", "21"], ["2", "true", "21"]]...
    
  • функция key_sum

    key_sum = ->(group) { group.sum { |key, _| key.to_i }.to_s }
    key_sum.call([["1", "true", "21"], ["2", "true", "21"]]) #=> '3'
    
  • to_h

    [[0, ["3", "true", "21"]], [1, ["10", "false", "21"]], [2, ["15", "true", "22"]]].to_h
    #=> {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}
    
0 голосов
/ 25 декабря 2018
h
.values
.group_by{|_, *a| a}
.map
.with_index{|(k, a), i| [i, [a.inject(0){|acc, (n, *)| acc + n.to_i}.to_s, *k]]}
.to_h
# => {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}
0 голосов
/ 23 декабря 2018

Хэш и Массив имеют самые мощные встроенные функции в ruby.

z = h.group_by { |k,v| v[1..2] }.keep_if { |k,v| v.length > 1 }
val = z.map { |k,v| [v.map { |x| x[1] }.map(&:first).map(&:to_i).inject(:+).to_s, k[0], k[1]] }
val.each_with_index.inject({}) { |m,(x,i)| m[i] = x; m }

=> {0 =>["3", "true", "21"], 1 =>["10", "false", "21"], 2 =>["15", "true", "22"]}

Если вы знаете эти функции, вам никогда не понадобится сложная реализация, Счастливого обучения:)

0 голосов
/ 23 декабря 2018

Просто из любопытства.

input.
  values.
  map { |i, *rest| [rest, i.to_i] }.
  group_by(&:shift).
  map do |*key, values|
    [values.flatten.sum.to_s, *key.flatten]
  end
0 голосов
/ 23 декабря 2018

Код

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, которая сильно не используется.

0 голосов
/ 23 декабря 2018

Это должно ответить на ваш вопрос, несмотря на то, что это очень долгий способ его решения.Я уверен, что есть способы быстрого решения, но вы запросили четкое объяснение того, что происходит, и я надеюсь, что это поможет вам решить, как это решить.

# Start with a hash
hash = {
  "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"]
}

# Extract just the values from the hash into an array
values = hash.values

added_values = values.map do |array|
  # Find all arrays that match this array's values at indices [1] and [2]
  matching = values.select { |a| a[1] == array[1] && a[2] == array[2] }
  sum = 0
  # Add the values at index 0 from each matching array
  matching.each { |match| sum += match[0].to_i }
  # Return a new array with these values
  [sum.to_s, array[1], array[2]]
end

# Reject any duplicative arrays
added_values.uniq!

# Convert the array back to a hash
added_values.each_with_index.each_with_object({}) { |(array, index), hash| hash[index] = array }
0 голосов
/ 23 декабря 2018

Я не разработчик ruby, поэтому я не могу предложить какие-либо рекомендации, но простой алгоритм, который приходит мне в голову после прочтения этого, заключается в создании нового хэша и проверке, есть ли в нем значения массива, если нет, тодобавить новое значение, как это.

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"]
}

new_h = {}
h.each do |key, val|
  x1 = val.at(1)
  x2 = val.at(2)
  found = false
  new_h.each do |key1, val2|
    y1 = val2.at(1)
    y2 = val2.at(2)
    if x1 === y1 && x2 === y2
      found = true
      arr = [val2.at(0).to_i + val.at(0).to_i, x1, x2]
      new_h[key1] = arr
    end
  end
  if !found
    new_h[new_h.length] = val
  end
  if new_h.empty?
    new_h[key] = val
  end
end

puts "#{new_h}"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...