Can someone explain why this way of iterating a nested data structure really works?

I wanted to create this array

["studies", "theory", "form", "animal", "basic", "processes"] 

from the following nested data structure (stored as 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}]] 

I confused this as a hash and wrote the following code to accomplish my task:

 sorted_hash.each do |key,value| array.push key end 

And I really got what I wanted. But after some thinking and playing Pry, I wonder why. each Ruby Doc method for arrays shows only examples with one item variable, as in

each { |item| block } β†’ ary

but I use two variables, as for hashes. Will Ruby try to match element data, which in this case succeeds because the 2nd level array is 2 in length? Is it recommended to do so? Are there any more idiomatic ways to do this?

+1
source share
2 answers

The answer follows from how Ruby implements "parallel assignment."

As you probably know:

 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 

The last two examples use "ambiguity," which some people prefer to call "decomposition."

Now let's see how this relates to assigning values ​​to lock variables.

Let's pretend that:

 arr = [["studies", {:freq=>11, :cap_freq=>0, :value=>11}], ["theory", {:freq=>9, :cap_freq=>1, :value=>11}]] 

and execute:

 arr.each { |a| pa } ["studies", {:freq=>11, :cap_freq=>0, :value=>11}] ["theory", {:freq=>9, :cap_freq=>1, :value=>11}] 

Look at it more closely. Definition:

 enum = arr.each #=> #<Enumerator: [["studies", {:freq=>11, :cap_freq=>0, :value=>11}], # ["theory", {:freq=>9, :cap_freq=>1, :value=>11}]]:each> 

The first element is passed to the block and assigned to the block variable v :

 v = enum.next #=> ["studies", {:freq=>11, :cap_freq=>0, :value=>11}] 

We may prefer to use a parallel assignment with two block variables (after enum.rewind to reset the enumerator):

 a,h = enum.next a #=> "studies" h #=> {:freq=>11, :cap_freq=>0, :value=>11} 

This allows us to write (for example):

 arr.each { |a,h| ph } {:freq=>11, :cap_freq=>0, :value=>11} {:freq=>9, :cap_freq=>1, :value=>11} 

Here we do not use the block variable a . If so, we can replace it with the local variable _ or perhaps _a :

 arr.each { |_,h| ph } arr.each { |_a,h| ph } 

This draws attention to the fact that a not used and can help avoid errors. As for errors, suppose we want:

 [[1,2],[3,4]].map { |a,b| puts 1+b } #=> [3,5] 

but inadvertently write:

 [[1,2],[3,4]].map { |a,b| puts a+b } #=> [3,7] 

which only fines (but produces an incorrect result). On the contrary

 [[1,2],[3,4]].map { |_,b| puts a+b } #NameError: undefined local variable or method 'a' 

indicates a problem.

Here is a more detailed example of what you can do in blocks with parallel assignment and values. Given:

 h = { :a=>[1,2], :b=>[3,4] } 

suppose we want to get:

  { :a=>3, :b=>7 } 

One of the methods:

 h.each_with_object({}) { |(a,(b,c)),g| g[a] = b+c } => {:a=>3, :b=>7} 
+3
source

This is because Ruby allows you to do this:

 [[1,2,3], [4,5,6]].each {|x,y,z| puts "#{x}#{y}#{z}"} # 123 # 456 

Thus, each gives an array element to a block, and since the Ruby block syntax allows you to "expand" the array elements to their components by providing a list of arguments, it works.

You can find more tricks with block arguments here .

And by the way, instead of creating an array yourself and calling push you can simply do the following, since map returns an array:

 sorted_hash.map(&:first) 
+2
source

Source: https://habr.com/ru/post/1236187/


All Articles