blahblah has asked for the wisdom of the Perl Monks concerning the following question:

Please bear with me while I explain how I got in this mess.

I need to make selection list in a CGI program where users can choose the ordering of columns in a table, like 'first, last, suffix' etc. The hitch was users could also choose a shorter option like 'first, last' from the same list.

So, to make a permutation that presented all of the options for one data set was fairly simple with tye's code on Permuting with duplicates and no memory. Originally, I thought I could get the rest of the options by popping the array and passing it to the permute factory again. It didn't take long to realize that this method does not present all options.

For example:
If you pass the permute factory '0 1 2 3' it will give you all the options for '0 1 2 3'. You pop off 3 then pass it again. Now you have all the options for '0 1 2', but not a single one of those options has 3 in it. So, it turns out you need to pass unique permute seeds:

0 1 2
0 1 3
0 2 3
1 2 3

Only then will you truely have all the permutations.

So, I wrote this permute seed generator. It is supposed to take an array of arbitrary length and generate all the unique permute seeds. Right now it works on arrays that are less than 5 in length, but dies on arrays larger than 5.

I've Super Searched my brains out and pored over this for days. How can I fix this code so that it actually works on arrays of arbitrary length??

#!/usr/bin/perl -w use strict; #how many options should be presented at minimum? my $minpermutelength = $ARGV[0]; #this will be set to 0..$#array_passed_in when all is working my @slots = (0..4); my $biggestslotnum = $#slots; die("Minimum permute length $minpermutelength is larger than the data +set of 0..$#slots\n") if ($minpermutelength > $#slots); die("Minimum permute length cannot be negative or undefined.\n") if ($ +minpermutelength < 0 || !defined $minpermutelength); my $loop = 0; my $iteration = 0; my $thispermute = $#slots; while ($#slots >= $minpermutelength) { while ($slots[$#slots] <= $biggestslotnum) { for (@slots) { print "$_" }; print " loop: $loop iteration: $ite +ration thispermute: $thispermute\n"; # record $slots[$#slots]++; } $slots[$#slots]--; # fix overage from while above if ($slots[$#slots] == $biggestslotnum && $slots[0] == $biggestslot +num - $#slots && $loop == 0) { $thispermute--; # shrink permute string by one @slots = (0..$biggestslotnum); # reset splice(@slots,$thispermute-$biggestslotnum); # 0 1 2 3 $iteration = 0; next; } if ($slots[$#slots-$iteration-1] == $slots[$#slots-$iteration]-1) { + # 0 3 4 $loop = 0; $iteration++; $slots[$#slots-$iteration-1]++; # I think my problem is in these splices, but I can't figure it +out. :( if ($slots[$#slots-$iteration] - $slots[$#slots-$iteration-1] > +1) { splice(@slots,$slots[$#slots-$iteration-1],$thispermute); splice(@slots,@slots,0,$slots[$#slots-$iteration-1]+1..(($slo +ts[$#slots-$iteration-1]+1)+($thispermute-@slots))); $slots[$#slots]++ while ($slots[$#slots] <= $slots[$#slots-1] +); $iteration = 0; } } else { $loop++; $slots[$#slots-1]++; #increase the slot to the left by one $slots[$#slots] = $thispermute + $loop unless ($slots[$#slots-1] + == $slots[$#slots]-1); # start counting again $slots[$#slots]++ while ($slots[$#slots] <= $slots[$#slots-1]); } }
Thanks!

Replies are listed 'Best First'.
Re: Permutation seed generator
by kvale (Monsignor) on May 06, 2004 at 05:57 UTC
    If I understand correctly, you want all the possible subsets of a set:
    my @set = qw|0 1 2 3|; foreach my $index (0..2**@set-1) { my @subset; foreach my $pos (0..@set-1) { push @subset, $set[$pos] if ($index >> $pos) % 2; } print join " ", @subset, "\n"; }
    Here, $index iterates over all members of the power set. This should work for sets up to 32 members in size for 32-bit integers.

    -Mark

      This does not answer your question, and it's not even a Perl suggestion, but I have to ask ...

      why use permutations *at all*?

      If the only requirement is to allow a user-definable ordering of known table fields, why not just give the user four 'select' boxes, one for each column?

      In other words, give the user four of these ... one for each 'column'. (Line them up left to right so it is intuitive).

      {select} {fname} {lname} {title} {phone} {_leave_blank_} {/select}
      This is not an 'elegant' solution, but does not seem any worse than giving the user a list of *every possible permutation of the ordering*, (including orderings where zero or more columns are excluded)! The least kludgy solution is to use a GUI control that is intended for this specific purpose. Mozilla Firebird has one. Just something to think about.

      Geez, the pixels were still warm on my post before you blasted my hilarious code with that 8 liner! Oh woe is me. Days of labor only to see that. ha ha!

      My only question then is how can you set the limit on the length of subsets that get returned. I mean if I only want to see every subset with 2 or more digits? I'm not very familiar with bitshifting and that exponentiation ** thing is throwing me too.

      Oh well. More reading....

      Thanks!

        Probably the easiest way to get subsets of a particular size is simply to add a conditional to the print statement:
        print join " ", @subset, "\n" if @subset == 3;
        A little bit more explanation: The number of subsets of a set is 2**$n, with $n the number of elements in the set. So each integer from 0 to 2**n-1 is associated with a unique subset. To get the subset, I associate each bit position with an element of the set. That bit set to 1 means the element is in the subset, 0 means it isn't. The bitshifting and mod operation are used to extract the bit at position $pos to test for membership of that element in the subset.

        -Mark

      kvale, I like the elegant power set generator, but it doesn't fully solve the OP's question: the power set gives all combinations of a given set, not permutations

      That said, I'd also second scooterm's suggestion that giving all permutations in a list is probably the wrong approach from a UI perspective. Especially so if, as the question seems to imply, you're hoping to scale this solution. With 6 columns, even limiting the choices to orderings of 5 or 6 columns gives you an unwieldy 1,440 options ((6 choose 6 = 1) * 6! + (6 choose 5 = 6) * 5!).

Re: Permutation seed generator (again)
by tye (Sage) on May 06, 2004 at 06:14 UTC

    Starting with my code from a few days ago, Re^3: Searching for a Permutation Algorithm for nPr where n != r (A::Loops), I only had to make almost trivial changes (update; and then I mostly undid those two changes because I realized from your reply elsewhere that you only wanted certain lengths, not all lengths -- so I added two more changes to make the code more general and demonstrate that):

    #!/usr/bin/perl -w use strict; use Algorithm::Loops qw( NestedLoops NextPermute ); my @items= qw( prefix first middle last title ); my $choose= 3; my $iter= NestedLoops( [ [ 0..$#items ], ( sub { [ $_+1 .. $#items ] } ) x ( $choose - 1 ), ], # Uncomment next line if you want all sizes <= $choose # { OnlyWhen => 1 }, ); my @choice; while( @choice= sort @items[$iter->()] ) { do { print "@choice\n"; # replace above line with your code } while( NextPermute(@choice) ); }

    Sorry, I don't have access to Perl from here at the moment, so it is untested. Update: Thanks to tkil for testing and other feedback.

    - tye        

Re: Permutation seed generator
by oha (Friar) on May 06, 2004 at 11:44 UTC
    if i understand right, you have to remove some elements from the list to get only sorted permutations.

    for (0, 1, 2, 3), if you want 3 elements, sorted:
    remove 3: (0, 1, 2)
    remove 2: (0, 1, 3)
    remove 1: (0, 2, 3)
    remove [0]: (1, 2, 3)

    extending to a general form, you have to permute the removals:
    for (0, 1, 2, 3, 4) want 3 elements:
    remove 3, 4: (0, 1, 2)
    remove 2, 4: (0, 1, 3)
    remove 2, 3: (0, 1, 4)
    remove 1, 4: (0, 2, 3)
    remove 1, 3: (0, 2, 4)
    remove 1, 2: (0, 2, 3)
    remove [0], 4: (1, 2, 3)
    remove [0], 3: (1, 2, 4)
    remove [0], 2: (1, 3, 4)
    remove [0], 1: (2, 3, 4)

    the remove indexes are the permutations of indexes 0..4 in 2 elements

    Oha
Re: Permutation seed generator
by Your Mother (Archbishop) on May 06, 2004 at 20:37 UTC
    i think i got this from one of merlyn's pages, so if you like it, the thanks go to him. i'm at a loss to explain why this function or one like it isn't in one of the algorithm modules (but i don't think it is; yet).
    sub combinations { return [] unless @_; my $first = shift; my @rest = combinations(@_); return @rest, map { [$first, @$_] } @rest; }
Re: Permutation seed generator
by Anonymous Monk on May 06, 2004 at 17:46 UTC
    For further reference on permutations you might check out Donald Knuth's pre-fascicles on his ever fabled Volume 4.