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

Here is the idea: I'd like to take an array of 1..16 numbers and I'd like to build an array of hashes that is composed of the following:

{"LIST"}a list (in ascending order) of between 2 and 15 indexes from the initial array
{"VALUE"}the result of an operation performed on each of the elements of the array that are indexed by {"LIST"}

The elements of the hasharray would be all the possible combinations (order ignored) of the given array elements.

So, for the array:

$array[0] = 1 $array[1] = 2 $array[2] = 3
The result (assuming an operation of "+") would be something like the following array:
$ArrayHash[0]->{"LIST"} = (0,1) $ArrayHash[0]->{"VALUE"} = 3 $ArrayHash[1]->{"LIST"} = (1,2) $ArrayHash[1]->{"VALUE"} = 5 $ArrayHash[2]->{"LIST"} = (0,2) $ArrayHash[2]->{"VALUE"} = 4 $ArrayHash[3]->{"LIST"} = (0,1,2) $ArrayHash[3]->{"VALUE"} = 6

I've coded this with a bunch of nested loops, but the code is really, really ugly and I'm thinking that PERL must offer something more elegant. Any ideas?

Thanks!

Replies are listed 'Best First'.
Re: array problems
by BrowserUk (Patriarch) on Aug 12, 2003 at 18:40 UTC

    How about this?

    #! perl -slw use strict; use vars qw[ $a $b ]; use List::Util qw[ reduce ]; my $op = shift @ARGV; my @hasharray; for ( 2 .. @ARGV ) { for my $indices ( Cnr( $_, 0..$#ARGV ) ) { push @hasharray, { LIST=> [ @$indices ], VALUE=> reduce{ my $val = eval "$a $op $b"; } @ARGV[ @$indices ] }; } } print "@{ $_->{LIST} } : $_->{VALUE}" for @hasharray; exit; sub Cnr{ my( $n, @r ) = shift; return [] unless $n--; for my $x ( 0 .. ($#_ - $n) ) { push @r, map{ [ $_[$x], @$_ ] } Cnr( $n, @_[ ($x+1) .. $#_ ] ) + ; } return @r; }

    Supply the (binary) operator as the first argument, the rest will be used as the values in the array.

    A few runs

    P:\test>283241 + 1 2 3 4 0 1 : 3 0 2 : 4 0 3 : 5 1 2 : 5 1 3 : 6 2 3 : 7 0 1 2 : 6 0 1 3 : 7 0 2 3 : 8 1 2 3 : 9 0 1 2 3 : 10 P:\test>283241 - 1 2 3 4 0 1 : -1 0 2 : -2 0 3 : -3 1 2 : -1 1 3 : -2 2 3 : -1 0 1 2 : -4 0 1 3 : -5 0 2 3 : -6 1 2 3 : -5 0 1 2 3 : -8 P:\test>283241 * 1 2 3 4 0 1 : 2 0 2 : 3 0 3 : 4 1 2 : 6 1 3 : 8 2 3 : 12 0 1 2 : 6 0 1 3 : 8 0 2 3 : 12 1 2 3 : 24 0 1 2 3 : 24 P:\test>283241 / 1 2 3 4 0 1 : 0.5 0 2 : 0.333333333333333 0 3 : 0.25 1 2 : 0.666666666666667 1 3 : 0.5 2 3 : 0.75 0 1 2 : 0.166666666666667 0 1 3 : 0.125 0 2 3 : 0.0833333333333332 1 2 3 : 0.166666666666667 0 1 2 3 : 0.0416666666666667

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
    If I understand your problem, I can solve it! Of course, the same can be said for you.

      Here is what I came up with (I'm just using the example of the operator "+" because that is the only one I _need_ right now):

      use List::Util; use Algorithm::ChooseSubsets; ...<lots of stuff snipped here>... # Generate sums of the values of all combinations of my picks my @to_trade1; for (my $i=0; $i < $roster_spots; $i++) { my $record={}; my $this_pick = $pick_by_team[$brett_team][$i]; if ($this_pick > $draft_pick) { push @to_trade1, $this_pick; } } my @to_trade = {}; for (my $i = 2; $i < $#to_trade1; $i++) { my $subset_list = new Algorithm::ChooseSubsets(\@to_trade1, $i); while (my $this_set = $subset_list->next()) { my $record={}; $record->{"LIST"} = [ @$this_set ]; $record->{"VALUE"} = List::Util::sum @$this_set; push @to_trade, $record; } } print Dumper(@to_trade);

      This works, but is pretty slow...I have to respond to trades within a minute so, I may try some ways to speed it up!

        If your array size is always the same, then you can avoid having to regenerate the combinations each time.

        In my version above, if you look closely you'll see that I don't permute the array elements. I permute the indices and the use those to slice the array. If your array is always the same size, you can do this once and reuse the indices each time which will save a fairly costly recursive generation each time.

        A modified version of the above code to show this

        If your array size varies, but over a short range, then you could use Memoize to cache the generation and only do it the first time a new array size is encountered.

        As Algorithm::ChooseSubsets permutes the array rather than the indices, Memoizing that wouldn't reap the same rewards.

        There is also a performance penalty from using OO-style interfaces, especially to inherently procedural algorithms. Most of the time this is pretty insignificant, but when it means issuing a call once each time around a loop instead of once at the top of the loop, then the overhead becomes a burden.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
        If I understand your problem, I can solve it! Of course, the same can be said for you.

      Does it really make sense to perform non-communtative operations on an unordered set?

        Almost certainly not, but then I didn't write the spec, only attempted to fulfill it. I blame management :)


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
        If I understand your problem, I can solve it! Of course, the same can be said for you.

      This looks very good! I had just stumbled on List::Util myself and put it into an implementation that I thought would work. I'm testing mine now, assuming it works, I'll post it...otherwise, I'll use yours!!!!

      Thanks!

Re: array problems
by ctilmes (Vicar) on Aug 12, 2003 at 16:13 UTC
    Might want to take a look at List::Permutor or Algorithm::Permute.

    Update: Want combinations, not permutations.

    Good luck on the homeworkFootball drafting tool!

      Yeah, I thought it would sound like a homework problem. I'm actually writing a drafting tool for my Fantasy Football league (16 rounds). I found a site that values each pick in the draft (www.footballguys.com) and I want to find all the combinations of my picks that are similar to the pick for which I want to trade.

      I'll look at the permutations stuff, there might be something there.

Re: array problems
by rupesh (Hermit) on Aug 12, 2003 at 16:43 UTC

    My idea would be to pass the array to a funstion, manipualte the contents of each element and put it into a hash on the fly. That way, you could have one generic function, which would make your code much simpler, and much smaller too.
    However, if you want more info on array of hashes, hope this site proves useful
    All the best!

    we're born with our eyes closed and our mouths wide open, and we spend our entire life trying to rectify that mistake of nature. - anonymous.
Re: array problems
by eric256 (Parson) on Aug 12, 2003 at 17:13 UTC

    You could push each index into a hash using the value as the key. Hope that makes some sense. I'll share my code if you share yours :-)

    Update: I misunderstood. Thought you were going for list of matching values (don't know where i got that idea.)
    ___________
    Eric Hodges

      Here is the beggining of a solution. It gives you all combinations of lenght x for a given set. So you could call it once for each length of your set and combine all those, to get all combinations of all lenghts.

      Fun bit of recursion here. No promises it will always work.

      use strict; my @array = ["a","b","c","d"]; my $newlist = combine(2,@array); foreach my $list (@$newlist) { print "[" . join(",", @$list) . "]\n"; } # takes a list of items # returns a list of lists of those items for each combination sub combine { my $length = shift; my $items = shift; my @list; if ($length == 1) { foreach my $item (@$items) { push @list,[$item]; } } else { my $l = length(@$items) + 1; foreach (0..$l) # once for each item { my $tempa = shift @$items; # get first item(a) # get permutations for this set, but one # shorter (without current item) my $templist = combine($length-1,$items); foreach my $i (@$templist) { unshift @$i,$tempa; push @list,$i; } push @$items,$tempa; } } return [@list]; } 1;
      ___________
      Eric Hodges

        Before getting your response, I found Algorithm::ChooseSubset which takes the same tact. I'm working on that approach now...if I get it to work correctly, I'll post the solution. In essence, it does the same thing yours does!

Re: array problems
by ido50 (Scribe) on Aug 12, 2003 at 16:37 UTC
    Will be nice if you put the code you already have here.

    -------------------------
    Live fat, die young

      Well, actually, I haven't coded the whole thing...it was just looking ugly as I started...I started out thinking I'd just check to see if I could find two of my picks that would be roughly the same value as the other guys pick, so here is the code I had:

      foreach my $rec1 (@to_trade1) { foreach my $rec2 (@to_trade1) { if ($rec1->{"PICK"} != $rec2->{"PICK"}) { my $trade_diff = $pot_trade_val - $rec1->{"VALUE"} - $rec2->{" +VALUE"}; if (($trade_diff > 49) && ($trade_diff < 101)) { $to_trade[$trade_cntr]->{"PICK_LIST"} = sort {$a <=> $b} ($r +ec1->{"PICK"}, $rec2->{"PICK"}); $to_trade[$trade_cntr]->{"VALUE"} = $trade_diff; }

      That should give you the idea of where I was going with the following understanding:

      $pot_trade_val The value of the pick I want plus the 16th round pick held by the same team
      $rec1 and $rec2 The same array copied twice and this is the list of picks I currently have.
      $to_trade This is the set of picks that is within my tolerance to trade 50-100 points difference, of course, I'd walk this list and pull the one closest to 50

      So, as I was going through this, I thought that it would end up pretty ugly and I'd only be able to account for say groups of three picks before the logic gets even worse. Thus, I thought it might be easier to value all my picks once, and then each time I was looking for a trade, I could just grep and sort the array of already calculated values! Sound like a good approach?