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

Folks I have two arrays(single dimensional).I need to select a random element from one array and need to make sure that the selected number is not there in the second array.I need a function which needs to do this really efficient. FYI, the first array will have 1 to 1000 elements while the second will have 1 to 50 Thanks
  • Comment on Choosing a random product from an Array

Replies are listed 'Best First'.
Re: Choosing a random product from an Array
by DamnDirtyApe (Curate) on Nov 12, 2002 at 21:27 UTC

    Transforming your "lookup" array into a set of hash keys is one widely used way of doing this.

    #! /usr/bin/perl use strict ; use warnings ; my @first = qw( 1 3 5 7 9 11 13 15 17 19 21 23 ) ; my @second = qw( 1 5 9 13 17 21 ) ; ## Turn the second array into hash keys. my %hash = map { $_ => 1 } @second ; my $rand_el = $first[ int rand @first ] ; print $rand_el . ( $hash{$rand_el} ? ' is ' : ' is not ' ) . 'in the second array.' . "\n" ; exit ;

    _______________
    DamnDirtyApe
    Those who know that they are profound strive for clarity. Those who
    would like to seem profound to the crowd strive for obscurity.
                --Friedrich Nietzsche
Re: Choosing a random product from an Array
by tadman (Prior) on Nov 12, 2002 at 21:57 UTC
    A sort of combined variation of DamnDirtyApe and sauog which is really just what came to mind first:
    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 %valid = ((map { $_ => 1 } @$first), (map { $_ => 0 } @$second)); my $return; do { # Choose a random key from the combined listing... $return = (keys %valid)[rand(keys %valid)]; # ...and make sure it's valid. } while (!$valid{$return}); return $return; } print "Selection: ",random_from(\@first, \@second),$/;

      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.";
      
        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.
Re: Choosing a random product from an Array
by sauoq (Abbot) on Nov 12, 2002 at 21:28 UTC
    my $elt; $elt = $one[rand @one] until $elt and not grep $elt eq $_, @two;

    Update: Use a hash instead of grep for speed.

    my $elt; my %hash; @hash{@two} = (); $elt = $one[rand @one] until $elt and not $hash{$elt};

    Examples assume your two arrays are named @one and @two.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Choosing a random product from an Array
by BrowserUk (Patriarch) on Nov 13, 2002 at 05:17 UTC

    Another way of doing it that maybe slightly faster and use less memory than building a hash or grep. It would also work if the arrays contained duplicates or even compound objects instead of simple string or numbers.

    #! perl -slw use strict; use constant ALLPRODUCTS =>0; use constant PROHIBITED =>1; my @allProducts = map { int rand 10_000 } 1..1000; my @selection = map { $allProducts[rand 1000] } 1..50; sub randProduct (\@\@) { local $"="\cA"; #!" my ($choice, $disallowed) = ('', "@{$_[PROHIBITED]}"); $choice = $_[ALLPRODUCTS]->[rand @{$_[ALLPRODUCTS]}] while 1+index +( $disallowed, $choice ); return $choice; } my $chosen = randProduct @allProducts, @selection;

    Okay you lot, get your wings on the left, halos on the right. It's one size fits all, and "No!", you can't have a different color.
    Pick up your cloud down the end and "Yes" if you get allocated a grey one they are a bit damp under foot, but someone has to get them.
    Get used to the wings fast cos its an 8 hour day...unless the Govenor calls for a cyclone or hurricane, in which case 16 hour shifts are mandatory.
    Just be grateful that you arrived just as the tornado season finished. Them buggers are real work.

Re: Choosing a random product from an Array
by pg (Canon) on Nov 13, 2002 at 01:52 UTC
    I would suggest to use a bit map to figure out the diff between two arrays:
    @a = (10, 2, 3, 5, 89); @b = (2, 5); my $c = []; foreach (@a) { $c[$_] = $_; }; foreach (@b) { $c[$_] = undef; } foreach (0 .. $#{@c}) { print "$c[$_]\n" if (defined($c[$_])); }