Вы можете исправить свой код, заменив
b[m[0]].push [m[1]=>v]
с
b[m[0]] += [m[1]=> v]
или
b[m[0]].concat [m[1]=> v]
Как вы знаете, это значение b
после выполнения кода, которое вы хотите, поэтому вы должны добавить b
в качестве финальной строки.
Несколько других наблюдений:
- если
r = line.split(",")
изменяется на r = line.chomp.split(",")
, следующая строка упрощается.
a.map { |k,v|...
можно заменить на a.each { |k,v|...
, что более уместно и лучше читается.
a[r[0] + "°" + r[1]]...
сделал мои глаза болят. Вам никогда не нужно прибегать к таким взломам. Вместо этого вы могли бы написать a[r[0], r[1]]...
, удалить m=k.split("°")
и заменить следующую строку на b[k[0]] += [k[1]=> v]
.
Вот два других способа сделать это. Оба подхода используют метод Hash # transform_values , который дебютировал в Ruby v2.4.
str =<<_
Us,1,1,F
Us,1,2,O
Us,2,1,N
Pa,1,1,S
Pa,1,3,D
Pa,1,5,H
Pa,4,7,K
_
Использование Enumerable # group_by
str.lines.
map { |line| line.chomp.split(',') }.
group_by(&:shift).
transform_values { |arr| arr.group_by(&:shift).map { |k,v| { k=>v } } }
#=> {"Us"=>[{"1"=>[["1", "F"], ["2", "O"]]}, {"2"=>[["1", "N "]]}],
# "Pa"=>[{"1"=>[["1", "S"], ["3", " D"], ["5", "H"]]}, {"4"=>[["7", "K"]]}]}
Шаги следующие.
a = str.lines
#=> ["Us,1,1,F\n", "Us,1,2,O\n", "Us,2,1,N \n",
# "Pa,1,1,S\n", "Pa,1,3, D\n", "Pa,1,5,H\n", "Pa,4,7,K\n"]
b = a.map { |line| line.chomp.split(',') }
#=> [["Us", "1", "1", "F"], ["Us", "1", "2", "O"], ["Us", "2", "1", "N "],
# ["Pa", "1", "1", "S"], ["Pa", "1", "3", " D"], ["Pa", "1", "5", "H"],
# ["Pa", "4", "7", "K"]]
c = b.group_by(&:shift)
#=> {"Us"=>[["1", "1", "F"], ["1", "2", "O"], ["2", "1", "N "]],
# "Pa"=>[["1", "1", "S"], ["1", "3", " D"], ["1", "5", "H"],
# ["4", "7", "K"]]}
c.transform_values { |arr| arr.group_by(&:shift).map { |k,v| { k=>v } } }
#=> <the return value shown above>
При выполнении последнего выражения первое значение, переданное блоку и присвоенное переменной блока, равно:
arr = [["1", "1", "F"], ["1", "2", "O"], ["2", "1", "N "]]
Затем вычисление блока возвращает:
d = arr.group_by(&:shift)
#=> {"1"=>[["1", "F"], ["2", "O"]], "2"=>[["1", "N "]]}
d.map { |k,v| { k=>v } }
#=> [{"1"=>[["1", "F"], ["2", "O"]]}, {"2"=>[["1", "N "]]}]
Использование Hash # update
Используется форма Hash#update
(он же Hash#merge!
), в которой используется блок для определения значений ключей, которые присутствуют в обоих объединяемых хэшах. Эта форма update
используется в двух уровнях вложенности.
str.lines.each_with_object({}) do |line, h|
s0, s1, s2, s3 = line.chomp.split(',')
h.update(s0=>{ s1=>[[s2, s3]] }) do |_0,oh0,nh0|
oh0.merge(nh0) { |_1,oh1,nh1| oh1+nh1 }
end
end.transform_values { |h| h.map { |k,v| { k=>v } } }
#=> <the return value shown above>
Обратите внимание, что код, предшествующий transform_values
, возвращает следующее.
{"Us"=>{"1"=>[["1", "F"], ["2", "O"]], "2"=>[["1", "N"]]},
"Pa"=>{"1"=>[["1", "S"], ["3", " D"], ["5", "H"]], "4"=>[["7", "K"]]}}
Вариант этого метода следующий.
str.lines.each_with_object({}) do |line, h|
s1, s2, s3, s4 = line.chomp.split(',')
h.update(s1=>{ s2=>{ s2=>[[s3, s4]] } }) do |_0,oh0,nh0|
oh0.merge(nh0) do |_1,oh1,nh1|
oh1.merge(nh1) { |_2,oh2,nh2| oh2+nh2 }
end
end
end.transform_values(&:values)
#=> <the return value shown above>
Обратите внимание, что код, предшествующий transform_values
, возвращает следующее.
h = {"Us"=>{"1"=>{"1"=>[["1", "F"], ["2", "O"]]}, "2"=>{"2"=>[["1", "N "]]}},
"Pa"=>{"1"=>{"1"=>[["1", "S"], ["3", " D"], ["5", "H"]]}, "4"=>{"4"=>[["7", "K"]]}}}
transform_values(&:values)
преобразует значения "Us"
и "Pa"
(которые являются хэшами) в массивы значений этих хешей (которые также являются хэшами), а именно,
[{"1"=>[["1", "F"], ["2", "O"]]}, {"2"=>[["1", "N "]]}]
для ключа "Us"
и
[{"1"=>[["1", "S"], ["3", " D"], ["5", "H"]]}, {"4"=>[["7", "K"]]}]
для "Pa"
. Поскольку мы хотим, чтобы значения "Us"
и "Pa"
были массивами хэшей, нам нужно несколько странное выражение
s1=>{ s2=>{ s2=>[[s3, s4]] } }
Если бы мы хотели, чтобы значения "Us"
и "Pa"
были единым хешем, мы могли бы написать
s1=>{ s2=>[[s3, s4]] }