In the first few parts of this series (first, second, and third), I introduced the “reduce” function, and showed how it can be used in a number of ways. However, in all of the examples we have seen so far, the output from our invocations of “reduce” were integers or strings. If we reduce with a “sum” function, then we get an integer representing the sum of all integers. If we reduce over a measurement, such as the size of an integer or the length of a string, we can implement “min” or “max”.
Another interesting and clever thing that we can do with “reduce” is to create an array (in Ruby) or list (in Python). Think of it this way: The output of each iteration of “reduce”, which is then passed to the next iteration, can be any data structure we wish. If the output from the iteration is an array, then that’s what will be passed to the next iteration.
In other words, we can use “reduce” to build up an array, with each iteration emitting that same array, perhaps with additional elements added to it. For example, I can create a new array whose contents are identical to the original one on which “reduce” was invoked:
['a', 'b', 'c'].reduce([]) {|output, current| output << current }
By adding each element to the beginning, I can reverse the array:
['a', 'b', 'c'].reduce([]) {|output, current| output.unshift(current) }
I can create a nested array, too:
['a', 'b', 'c'].reduce([]) {|output, current| output << [current, current] }
“reduce” is thus a great way to create an array, building it up piece by piece. True, we need to pass “reduce” an empty array as a parameter, and then each iteration needs to return the current value of the array. But other than that, we can add whatever we want to the array, and modify it in whatever ways we want. If I want to do something more complex than the above, or if I want to be able to debug individual lines of my block, I can even use do-end, thus spreading the block across multiple lines. (And yes, I know about the late Jim Weirich’s great suggestion, to use curly braces for returning values. But if you’re going to have multiple lines in your block, then do-end still rings truer for me.)
Let’s see how we can do the same thing in Python. First, we can return a list:
reduce(lambda output, current: output + [current], 'abc', [])
We start (using the optional final argument) with an empty list. Then, in each iteration, we take the current element, turn it into a one-element list, and then append it to the existing list. Note that we cannot use list.append to add the current element, because list.append returns None, like many other methods that change mutable objects in Python.
Using “reduce” to reverse the list is just as easy, by reversing the order in which we add things to the list:
reduce(lambda output, current: [current] + output, 'abc', [])
Finally, if we want to return a list of lists, we can do that fairly easily, just add a list of lists:
reduce(lambda output, current: output + [[current, current]], 'abc', [])
As you can see, “reduce” makes it possible for us to, in a single line, create a collection based on another set of inputs. The output can contain any types that we want; in these simple examples, we created lists of strings. But we could create lists of dictionaries, or of more complex objects, just as easily.
In the next installment, we’ll see how to create hashes/dictionaries using reduce.
[…] seen how we can build a number of different types of data structures — integers, strings, arrays, and hashes — using “reduce”. But the really interesting use of […]