in reply to Re: Choosing a random product from an Array
in thread Choosing a random product from an Array

Combining the arrays is a neat idea but I think the way you do it is a bit unintuitive. Your %valid hash contains keys which are both valid and not valid members. Why not just create an array with the valid members and choose from it? Warning: This approach does come wiht a huge caveat. Explanation follows code.

my @one = qw( 1 3 5 7 9 11 13 15 17 19 21 23 ); my @two = qw( 1 5 9 13 17 21 ); my %valid; @valid{@one} = (1) x @one; @valid{@two} = (0) x @two; my @choices = keys %valid; sub randelt { $choices[rand @choices] }

Huge Caveat: As this uses all of the elements to choose as hash keys in an intermediate step, it might change the likelihood of a particular element being chosen if it were originally duplicated. For instance, if the original array was qw( a a b c ) there would be a 1 in 4 chance that an 'a' would be chosen. After being used as hash keys, however, the list becomes qw( a b c ) and there is a 1 in 3 chance that 'a' will be chosen. Do not use this method if your original list has duplicate members.

-sauoq
"My two cents aren't worth a dime.";

Replies are listed 'Best First'.
Re^3: Choosing a random product from an Array
by tadman (Prior) on Nov 13, 2002 at 00:14 UTC
    I think that's a very good point. Having duplicate members in the initial listing is a perfectly valid way to assign different weights to different products. It stands to reason that my, admittedly, heavy-handed approach could be made more elegant. In fact, you really only need to store the invalid ones, and this can be flipped around in a jiffy.
    use warnings; use strict; my @first = qw( 1 3 5 7 9 11 13 15 17 19 21 23 ); my @second = qw( 1 5 9 13 17 21 ); sub random_from { my ($first, $second) = @_; my %invalid = (map { $_ => 1 } @$second); my $return; do { # Choose a random key from the combined listing $return = $first->[rand(@$first)]; } while ($invalid{$return}); return $return; } print "Selection: ",random_from(\@first, \@second),$/;
    Update: I'd just thought to clarify why the hash was called %valid. While it's true that it contains valid and invalid members, the purpose of the hash is to store the valid status of the entry. Obscure, perhaps, oblique, maybe, but functional.

      After making those changes, you're left with basically the same solution I proposed in my first reply in this thread.

      I liked your idea of combining the arrays initially because, if you do it right, it allows the choosing of a random element to be an O(1) operation instead of an iterative one which is dependent on the distribution of valid and invalid elements. You can achieve this and still permit duplicate members with some additional setup.

      my @one = qw( 1 3 5 7 9 11 13 13 15 15 17 19 21 23 ); my @two = qw( 1 5 9 13 17 21 ); my %valid; $valid{$_}++ for @one; @valid{@two} = (0) x @two; @choices = map { ($_) x $valid{$_} } keys %valid; sub randelt { $choices[rand @choices] }
      -sauoq
      "My two cents aren't worth a dime.";