From: James Harrison Date: 2010-05-17T09:49:56+09:00 Subject: Re: iterating over sub arrays >> > > Minor nit: by convention, Ruby people tend to use CamelCase for constants > only, and underscores for variables. Call it array_collection. When I keep to that convention, I find my code's more readable. I can tell what something is by looking at it. Thanks for the reminder :) > > Anyway, seems like one obvious way would be recursion: > > def each_join array, context=[], &block > if array.length == 0 > yield context > else > first = array.first > rest = array[1...array.length] > first.each do |elem| > each_join rest, context+[elem], &block > end > end > end > > Not pretty, and I'm sure someone could improve it, but it works. That it does, that it does. Unfortunately, I don't understand it. If you've got a minute, would you give a hand? It looks to me like: each_join takes three arguments. The first is an array, the second is predefined to be an empty array, the third is a reference to a block. First, I don't understand why you need to pass in a reference to a block, or why one would want to do that in general. Programming Ruby covers it by saying that this allows a block to be treated as a Proc object. I guess my misunderstanding, then, is what the hell a Proc object is really used for. I've been putting this off, and it meant that a simple solution escaped me, apparently. Onwards with understanding, then! A Proc object lets you assign a block to a variable. My naive reading of http://ruby-doc.org/core/classes/Proc.html leads me to imagine that the first benefit of this is that you can assign what's essentially a method call to a variable, almost like you're making a new object out of the variable. What does the object do? Take some number of arguments to its call method, and do something with those arguments. Why would you want to use this? I don't know. But it looks like that's all it does. And what does this have to do with each_join? Okay. The second argument, context. I'm not sure what it's doing. It's an empty array which is handed back to the block when the length of the original argument array is zero. I don't see how the argument array ever gets set to zero, though. Okay, here goes my explanation: Why is &block passed in? So that on successive calls to each_join, the block is correctly associated with the method. At the if statement, array.length is non-zero. As such, we go to the else clause. The first sub-array is assigned to the variable "first". The other subarrays are assigned to the variable "rest". So in the case of an array of arrays: [["bacon", "time", "ostrich"], ["jam", "bees", "please"], ["1", "2", "3"]] After the first pass: first = ["bacon", "time", "ostrich"] rest = [["jam", "bees", "please"], ["1", "2", "3"]] And then you start iterating over first. first.each do |elem| #first time around, elem == "bacon" each_join rest, context+[elem], &block So what happens now? first = ["jam", "bees", "please"] rest = ["1", "2", "3"] elem is set to "jam" on the iteration over first, context becomes ["bacon", "jam"] and we enter the whole thing again. Okay, the above is a nice start for any other new folks looking to trace this, but I just finished up and my mind == blown. It took me a while to realise that the control structure here is actually: first.each do |elem| and that's what's driving everything. For anyone else wanting to follow along, try this out: arrayCollection = [["bacon", "1", "2", "4"], ["jam", "bees", "please"], ["6", "7", "8"]] def each_join array, context=[], &block if array.length == 0 puts "now context is #{context}" yield context else first = array.first puts "context is #{context}" puts "first is #{first}" rest = array[1...array.length] puts "rest is #{rest}" first.each do |elem| puts "elem is #{elem}" puts "" each_join rest, context+[elem], &block end end end each_join(arrayCollection) do |b| puts "-------" puts "#{b}" puts "-------" end > > It does seem pretty weird, though. Out of curiosity, what do you need this > for? > Okay, you gave me quite an education today, so turn about is fair play. I have a program which accepts input and outputs output. It has both a gui and a scripting environment. For the scripting environment, I documented every option that you can pass in. The options look like: foo=bar or foo=1 and several can be handed in at a time. foo=bar;jet=bam; and so on. Each option can only be specified once, but each option can have multiple valid values. In order to verify that every option that can be handed in actually works as expected, I want to generate a set of scripts from the documentation I wrote. The parser I have for the documentation finds a series of lines of type: foo=bar -- does something fooish foo=bam -- does something else fooish jet=bar -- does something jetish jet=bam -- does something else jetish and so on. I'm popping each option string into an array: [[foo=bar, foo=bam], [jet=bar, jet=bam]] And from there, generate every possible combination of all options. I'd done everything else, but that last item on the list was killin' me.