John Fredsted posted some interesting code dealing with permutations, and I suggested a small improvement. Here, I want to continue the story of that improvement. First, let us focus one particular line of code:
posMaps,negMaps := seq(map((perm::list) -> (x::list) -> [seq(op(perm[i],x),i=1..nops(perm))] ,perms),perms in [posPerms,negPerms]);
which uses a lexically scoped procedure to perform the permutations. The first thing to notice is that op(perm[i],x) is really equivalent to x[perm[i]]. Now that we have that perky op gone, we see that the resulting code expression will return unevaluated if x is unknown, unlike op which throws an error message (correctly so!). So now instead of using scoping, we can let Maple actually evaluate the inner perm[i] calls, and use unapply to recover a routine. Putting that together gives us my suggestion:
posMaps,negMaps := seq(map((perm::list) -> unapply([seq(x[perm[i]],i=1..nops(perm))],x),perms), perms in [posPerms,negPerms]);
But now we see that we are using a seq over an integer range where we could have simply done
seq(x[p],p in perm)
which is clearer indeed. Now we can really get tricky: we notice the seq builds a list, but perm is a list, so shouldn't we use map? We sure can! Our first try is
map(y -> x[y], perm)
which works. But it still needs a function. Can we get fancy and replace it too? Yes! We can do:
map2(`?[]`, x, map(`[]`,perm))
Ok, that sure was fun. But that last step was too much. The problem is that the inline version of table selection wants a list as a second argument, and that makes things less clear. So let's go back one step, and stare at what we have next:
map((perm::list) -> unapply(map(y->x[y],perm),x),perms)
Can we do anything we that? I don't see anything obvious. Looking at the whole code, there does not seem to be other available 'simplifications'. There are transformations one might do, but none of them seem to be an actual improvement. What is my favourite version? This one:
posMaps,negMaps := seq({seq}(unapply(map2(`?[]`,x,map(`[]`,perm)),x),perm in perms),
perms in [posPerms,negPerms]);There are lots and lots of loops and function applications going on, but no inline functions in sight. I would not use this exact version in production code, but it is still my favourite.
Comments
more refactoring
While using `?[]` is neat, that it requires a second map to convert the elements to lists is an annoyance. Consequently, I'd use seq instead. Also, rather than creating a set of function, I'd create a function that returns a set:
(posMap,negMaps) := seq(unapply({seq([seq(x[i],i=perm)], perm in perms)},x) , perms in [posPerms,negPerms]);Unapply works nicely here. An old hack is to use subs with globals (x and S):
(posMaps,negMaps) := seq(subs('S'={seq([seq('x'[i],i=perm)], perm in perms)}, x -> S) , perms in [posPerms,negPerms]);Preferred version
Thanks, Jacques and Joe, for your sharp observations. I prefer the double
mapversion, so that my code lines would look like:Success
My main point, and the reason why I put this in a separate blog post, was that there were a lot of possible variants. And that, past some point, the only thing to choose between the variants was a matter of personal taste. The version you have chosen is very clean indeed, and may well be the version I might have chosen for production code.
I agree
I also think that this version with two
map's is very clean. I am happy to find out that my aesthetical feeling here agrees with the one of a master like you.