Может кто-нибудь объяснить, почему этот способ итерации вложенной структуры данных действительно работает?
1 Flip [2015-08-02 16:37:00]
Я хотел создать этот массив
["studies", "theory", "form", "animal", "basic", "processes"]
из следующей вложенной структуры данных (сохраненной как sorted_hash
):
[["studies", {:freq=>11, :cap_freq=>0, :value=>11}],
["theory", {:freq=>9, :cap_freq=>1, :value=>11}],
["form", {:freq=>9, :cap_freq=>1, :value=>11}],
["animal", {:freq=>12, :cap_freq=>0, :value=>12}],
["basic", {:freq=>10, :cap_freq=>1, :value=>12}],
["processes", {:freq=>13, :cap_freq=>0, :value=>13}]]
Я смутил это как хэш и написал следующий код для достижения моей задачи:
sorted_hash.each do |key,value|
array.push key
end
И я действительно получил то, что хотел. Но после некоторого размышления и игры в Pry я удивляюсь, почему. Метод each
Ruby Doc для массивов показывает только примеры с одной переменной item, как в
each { |item| block } → ary
но я использую две переменные, как и для хэши. Будет ли Ruby пытаться сопоставить данные элемента, которые в этом случае преуспевают, поскольку массив 2-го уровня имеет длину 2? Рекомендуется ли это делать так? Есть ли более идиоматические способы сделать это?
arrays ruby hash
2 ответа
3 Решение Cary Swoveland [2015-08-02 19:27:00]
Ответ следует из того, как в Ruby реализовано "параллельное назначение".
Как вы, наверное, знаете:
a,b,c = 1,2,3
a #=> 1
b #=> 2
c #=> 3
a,b,c = [1,2,3]
a #=> 1
b #=> 2
c #=> 3
a,b = [1,2,3]
a #=> 1
b #=> 2
a,*b = [1,2,3]
a #=> 1
b #=> [2, 3]
*a,b = [1,2,3]
a #=> [1, 2]
b #=> 3
a,(b,c) = [1,[2,3]]
a #=> 1
b #=> 2
c #=> 3
a,(b,(c,d)) = [1,[2,[3,4]]]
a #=> 1
b #=> 2
c #=> 3
d #=> 4
В двух последних примерах используется "неоднозначность", которую некоторые люди предпочитают называть "декомпозицией".
Теперь посмотрим, как это относится к назначению значений для блокировки переменных.
Предположим, что:
arr = [["studies", {:freq=>11, :cap_freq=>0, :value=>11}],
["theory", {:freq=>9, :cap_freq=>1, :value=>11}]]
и выполняем:
arr.each { |a| p a }
["studies", {:freq=>11, :cap_freq=>0, :value=>11}]
["theory", {:freq=>9, :cap_freq=>1, :value=>11}]
Посмотрите на это более внимательно. Определение:
enum = arr.each
#=> #<Enumerator: [["studies", {:freq=>11, :cap_freq=>0, :value=>11}],
# ["theory", {:freq=>9, :cap_freq=>1, :value=>11}]]:each>
Первый элемент передается блоку и присваивается блочной переменной v
:
v = enum.next
#=> ["studies", {:freq=>11, :cap_freq=>0, :value=>11}]
Мы можем предпочесть использовать параллельное назначение с двумя блочными переменными (после enum.rewind
до reset перечислителя):
a,h = enum.next
a #=> "studies"
h #=> {:freq=>11, :cap_freq=>0, :value=>11}
Это позволяет нам написать (например):
arr.each { |a,h| p h }
{:freq=>11, :cap_freq=>0, :value=>11}
{:freq=>9, :cap_freq=>1, :value=>11}
Здесь мы не используем блок-переменную a
. Если это так, мы можем заменить его локальной переменной _
или, возможно, _a
:
arr.each { |_,h| p h }
arr.each { |_a,h| p h }
Это обращает внимание на то, что a
не используется и может помочь избежать ошибок. Что касается ошибок, предположим, что мы хотим:
[[1,2],[3,4]].map { |a,b| puts 1+b }
#=> [3,5]
но непреднамеренно напишите:
[[1,2],[3,4]].map { |a,b| puts a+b }
#=> [3,7]
который выполняет только штраф (но создает неверный результат). Напротив,
[[1,2],[3,4]].map { |_,b| puts a+b }
#NameError: undefined local variable or method 'a'
говорит о наличии проблемы.
Вот более подробный пример того, что вы можете сделать в блоках с параллельным назначением и значениями. Дано:
h = { :a=>[1,2], :b=>[3,4] }
предположим, что мы хотим получить:
{ :a=>3, :b=>7 }
Один из способов:
h.each_with_object({}) { |(a,(b,c)),g| g[a] = b+c }
=> {:a=>3, :b=>7}
2 AmitA [2015-08-02 16:40:00]
Это потому, что Ruby позволяет вам сделать это:
[[1,2,3], [4,5,6]].each {|x,y,z| puts "#{x}#{y}#{z}"}
# 123
# 456
Таким образом, each
дает элемент массива блоку, и поскольку синтаксис блока Ruby позволяет "расширять" элементы массива их компонентам, предоставляя список аргументов, он работает.
Вы можете найти больше трюков с блочными аргументами здесь.
И, кстати, вместо создания массива самостоятельно и вызова push
вы можете просто сделать следующее, так как map
возвращает массив:
sorted_hash.map(&:first)