Over in CodingProblem RonJeffries asked for a routine to solve the following problem: : You have an ordered collection, size N, of objects. Given K, answer an ordered collection of K ordered collections of size approximately N/K, such that the first contains the first N/K objects, the second the second N/K, ''etc.'' He gave an example: input = #(1 2 3 4 5 6 7 8 9 10) k = 3 output = (for example) #( #(1 2 3) #(4 5 6) #(7 8 9 10) I don't understand where the subtlety is, and I'm starting to suspect that there must be some hidden assumptions somewhere. Just in case I was missing something about the problem as I understood it, I wrote the following Python solution: def R''''''eturnSplit(input,K): result = [] N = len(input) while input: s = N/K result.append(input[:s]) input = input[s:] N -= s K -= 1 return result N = 10 print R''''''eturnSplit( range(N), 3 ) This seems to work exactly as I expected, and produces the results requested. It works whether the division rounds up or down. It's easy enough to turn into an iterator-style solution, and it's easy enough to index into '''input''' and not reduce it in size. The reasoning as to why it works is easy. Select the best fit, adjust your concept of what remains, and repeat. It's an iterative version of a recursive solution that's "obviously" right. It seems to be correct, and the central loop seems obvious, as Ron requires. What am I missing? -- anon2 ---- Wow, I haven't thought about this problem since last century! And of course obvious to regular folks and obvious to the likes of me might be very different things. I think that if we were to discover that code, we would have to reason about it quite a bit to figure out what it did. The variable names, s, N, K aren't helping, but they're not the real issue for me. The real issue is that there's this s thing, meaning sequence or something, and I have to figure out, "Oh, I see, it's zero for a while, then one, then two ...", then I have to figure out how far it goes, what goes into each segment, and so on. I think that if faced with that code, I wouldn't know what it did. (Of course a test might show me, but the question was about the code itself.) Today, given what I'm thinking about, I might try to use slices to return the sequences. Perhaps a loop to compute the slice indexes, really obviously: [0...sliceLength], [sliceLength...2*sliceLength] ..., producing a list of the right number of slicers, and then loop over the slicers, with a collect: kind of operation, to produce the subsequences. That might make clear how many there were and what each one contained. Maybe I'll fiddle with it, but probably not, as I'm on another trajectory just now. The idea is not just to produce code that obviously works when we reason about it, knowing what is supposed to happen. The original problem we had was that we thought if we read the code a year later, we'd not instantly see what it did. I feel that someone reading the above code cold would not immediately say "Oh, that produces K subsequences of the original sequence." ''OK, so the question isn't to make the code clear, the question is to make the code clear '''even if you don't know what the problem is.''' That's a different question from the one I was answering.'' ''Here it is again but with some renaming and a reordering to show the invariant more clearly:'' def S''''''plitInputIntoLumpsOfEqualSize(input,number_of_lumps): lumps_of_equal_size = [] number_of_elements_remaining = len(input) while input: size_of_next_lump = number_of_elements_remaining/number_of_lumps number_of_elements_remaining -= size_of_next_lump number_of_lumps -= 1 lumps_of_equal_size.append(input[:size_of_next_lump]) input = input[size_of_next_lump:] return lumps_of_equal_size ''Is that now ScreaminglyObvious?'' ''I read with interest your comment about returning slicelengths. That's also easy to compute, and it just depends on how you want to carve up the operation, if at all. I think we all agree it's not hard - that's not the point. The point it (as I see it now) to create code that is obvious to a casual reader in isolation. There is certainly more than one way to do it.'' ---- One thing that is not screamingly obvious (to Y.T.) about the above is the division by the decreasing number of lumps. Yes, the second fourth is 1/3 of the remaining 3/4, the third fourth is 1/2 of the remaining 2/4, and so on. But IMO it's not obvious that that works. I'm supposing that the input[:size] and input[size:] thingies would be obvious to me if I used that language. Given the contents of my personal head, I might prefer the starting and ending indexes to be explicit. It would be nice if we didn't have to recompute the lump size every time through the loop, and I'm wondering whether with suitable rounding and the right stuff happening at the end of the array if it's short, if that could work nicely. I'm still leaning toward the slicing idea, but I haven't coded anything, so I could be more wrong than usual. -- RonJeffries I wonder if this is a difference in background. I read the above code and see a tail-recursive version that's obviously right. Basically it says: take off one lump of the best size possible, adjust the remainder and repeat. This will work because if this lump is a bit small you leave more behind than expected, so you'll take more next time. Here's a version using slices: def multiply_constant_by_list_giving_ints(k,l): return map( lambda x:int(k*x) , l ) # def F''''''airShareStartPoints(input,number_of_pieces): indexes_of_pieces = range(number_of_pieces) input_size = len(input) average_size = float(input_size) / number_of_pieces return multiply_constant_by_list_giving_ints(average_size,indexes_of_pieces) # def F''''''airShareBoundaries(input,number_of_pieces): return F''''''airShareStartPoints(input,number_of_pieces) + [input_size] # input = range(11) number_of_pieces = 3 boundaries = F''''''airShareBoundaries(input,number_of_pieces) print "Boundaries are:", boundaries print "Slices are:", zip(boundaries[:-1],boundaries[1:]) This prints the boundaries of the slices, so for input of 11 elements and three slices required, it prints [0,3,7,11]. Thus your slices are [0,3), [3,7), [7,11). It also prints the slice start and end, including the start, not including the end. I've been thinking about this for a while, and I think I'm back to not understanding what you're asking for. I think some code (although not necessarily this code) is subtle, and subtle code cannot be understood by reading without thinking. I wonder if an extreme version of what you're asking is simply that you want the code to be obvious from reading without thinking. Is that really a reasonable thing to ask for? Perhaps it can be our goal, perhaps it should be our goal, but is it truly reasonable to expect it of all code? This, of course, comes back to what code is for. The real code, the stuff that gets compiled/interpreted and executed must convey the "what" and the "how". Sometimes the "why" is there, and it should be whenever possible. Sometimes, however, the "why" isn't clear from the "what" and "how". There are not identical concepts, and trying to coerce them into a single vehicle might be misguided. Maybe, just maybe, the "why" should be in addition to the "what" and "how". -- anon2 ---- ''I guess I'm objecting to "subtle" code. I'd prefer code that requires less thinking than any of these examples do. In my opinion, we're not likely to randomly find people on the street who will look at the examples we've seen so far and say "oh, right. an obvious example of tail recursion. gotcha." '' ''I prefer code that is simple and clear, but not subtle. As I said when I posted this example, I have not as yet found such code for this problem. -- R'' Perhaps I mis-spoke myself. I believe that some code is absolutely clear as to what it is doing, but the reasons '''why''' it works are subtle. In some of these cases I believe you cannot express in code why it works. Examples always run the risk of turning out not to be good ones, and you, Ron, have the happy knack of being able to convert bad code into good code. However, here is an example of what I mean. Consider a simple algorithm for shuffling a vector. For simplicity, I will be specific, write in CeeLanguage, and assume the vector contains integers. void shuffle_method_one(int *vector, int number_of_elements) { for ( index_to_change=0; index_to_change