How can I do idiomatic non-recursive smoothing in ruby?

I have a method that returns an array of arrays. For convenience, I use the collection in the collection to put them together.

arr = collection.collect {|item| item.get_array_of_arrays} 

Now I would like to have one array containing all arrays. Of course, I can iterate over the array and use the + operator for this.

 newarr = [] arr.each {|item| newarr += item} 

But this is disgusting, is there a better way?

+4
source share
3 answers

There is a method to smooth an array in Ruby: Array#flatten :

 newarr = arr.flatten(1) 

From your description, it actually looks like you no longer need arr , so there is no need to store the old arr value around, we can just change it:

 arr.flatten!(1) 

(There is a rule in Ruby that says that if you have two methods that do basically the same thing, but one does it in a somewhat surprising way, you call this method the same as the other method, but with a dot exclamations at the end. In this case, both methods smooth the array, but the version with the exclamation mark does this by destroying the original array.)

However, although in this particular case there is actually a method that does exactly what you want, there is a more general principle in your code: you have a sequence of things, and you iterate over it and try to "reduce" it to nothing. In this case, it is difficult to understand, because you start with an array and you get an array. But, changing just a few small details in your code, all this suddenly becomes dazzlingly obvious:

 sum = 0 arr.each {|item| sum += item } # assume arr is an array of numbers 

This is exactly the same pattern.

What you are trying to do is known as catamorphism in category theory, code in mathematics, reduction of functional programming, inject:into: in Smalltalk, and Enumerable#inject and its alias Enumerable#reduce (or in this case actually Array#inject and Array#reduce ) in Ruby.

This is very easy to notice: whenever you initialize the battery variable outside the loop, and then assign it or modify the object that it refers to each iteration of the loop, then you have a case for reduce .

In this particular case, your battery is newarr , and the operation adds an array to it.

So your loop can be more idiomatically rewritten as follows:

 newarr = arr.reduce(:+) 
An experienced rubist, of course, will see it right away. However, even a novice will end up there by following some simple refactoring steps, probably similar to this:

First, you understand that this is actually a summary:

 newarr = arr.reduce([]) {|acc, el| acc += el } 

Then you realize that assigning acc completely unnecessary because reduce overwrites the contents of acc anyway with the result of the result of each iteration:

 newarr = arr.reduce([]) {|acc, el| acc + el } 

Thirdly, there is no need to enter an empty array as the initial value for the first iteration, since all arr elements are already arrays:

 newarr = arr.reduce {|acc, el| acc + el } 

This can, of course, be further simplified by using Symbol#to_proc :

 newarr = arr.reduce(&:+) 

And in fact, we do not need Symbol#to_proc , because reduce and inject already accept the symbol parameter for the operation:

 newarr = arr.reduce(:+) 

This is a really generic template. If you recall the sum example above, it will look like this:

 sum = arr.reduce(:+) 

There are no changes in the code except the variable name.

+28
source
 arr.inject([]) { |main, item| main += item } 
+1
source

I don't seem to fully understand the question ... Is Array # flatten what you are looking for?

 [[:a,:b], [1,2,3], 'foobar'].flatten # => [:a, :b, 1, 2, 3, 'foobar'] 
0
source

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


All Articles