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

Hi, I have a array more than 100 element and I want to pick up 10 element randomly from that array. My coded for this is:

for my $x (0..9) { my $element = $quest[ rand @quest ]; print $element; }

But, at times, the elements picked up are repeating. I want to pick up 10 unique elements randomly from the array. Can anyone help me how to do it?

Replies are listed 'Best First'.
Re: Picking unique element in random from a array
by davorg (Chancellor) on Aug 09, 2006 at 12:34 UTC

    Shuffle the array (using "shuffle" from List::Util) and take the first ten elements.

    Update: Here's a non-destructive approach that uses "shuffle":

    use List::Util 'shuffle'; my @arr = (1 .. 200); my @random = @arr[(shuffle 0 .. $#arr)[0 .. 9]]; print "@random\n";
    --
    <http://dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      davorg,
      Great advice and I upvoted you. It is nice to point out though that this, and randomly splicing elements of the original array do not leave it intact. Apparently Data::Random can do this as well as my suggestion of using a parallel array of indices.

      Update: After looking at the source of Data::Random's rand_set(), I noticed the routine can be terribly run-time inefficient depending on the parameters. It continously selects random indices until it finds enough unique ones to satisfy the 'size' option. This is doing a lot more work than necessary. I have emailed the author with suggestions.

      Cheers - L~R

      Shuffling all 100 elements (or indices) just to get 10 of them... A partial Fisher-Yates shuffle can be more efficient. For example, using pickFromRange which I wrote after Limbic~Region asked for such a thing in the CB (probably prompted by this thread), you could do:

      my @pick= @quest[ pickFromRange( 10, 0, $#quest ) ];

      Though I bet it will actually be slower than just shuffling the whole list of indices for this particular case given how slow dispatching Perl opnodes is and that List::Util uses XS. But for larger lists, it could be a win.

      - tye        

Re: Picking unique element in random from a array
by prasadbabu (Prior) on Aug 09, 2006 at 12:27 UTC

    rsriram,

    Here is a way to do it.

    use strict; use warnings; use Data::Random qw(:all); my @array = (1, 2, 1..120); #assign values to array my @ten = rand_set( set => \@array, size => 10 );#get random print "Ten unique random numbers: @ten";

    updated: Added random module. Removed identifying unique elements as per L~R's advice. As far as runtime is concerned, it is not most efficient way though. Take a look at Limbic~Region node or davorg's. Limbic~Region Thanks.

    Prasad

      prasadbabu,
      I think you have misunderstood though it is not your fault and I might be the one that is wrong. rsriram said unique elements but meant unique indices judging by the rest of the node. In other words, by repeating the random number selection, the same index was appearing multiple times. While your updated use of the Data::Random module gets the job done, the %unique is not needed. Update: Your second update corrects all the problems and this is a valid solution.

      Several ways to do this that involve changing the original array are to use List::Util's shuffle() as davorg has suggested or use splice to pull out elements randomly. Alternatively, a parrallel array of indices allows the original array to remain intact.

      my (@index, @rand); @index = 0 .. $#array; push @rand, splice(@index, rand @index, 1) for 1 .. 10; print "@array[@rand]\n"; # untested

      Update: I added a couple of alternatives that do not leave the original array intact. Additionally, I need to point out that your use of Data::Random can be terribly runtime inefficient. See my update in this node for details. The alternatives consume more memory (trading space for time). I also originally incorrectly thought that the module was returning the set in the original order by default but it isn't.

      Cheers - L~R

Re: Picking unique element in random from a array
by Leviathan (Scribe) on Aug 09, 2006 at 12:32 UTC

    A slight change to your code can help:

    my %seen; for my $x (0..9) { my $element = $quest[rand @quest]; redo if $seen{$element}++; print $element; }

    You set the element as a key to the hash, and you test to see if you have already used that element.

    --
    Leviathan
      Leviathan,
      While this code works, it can be terribly runtime inefficient. Imagine that you have an initial data set of 10_000 elements and the desired random set is 9_990 elements. This is how Data::Random does it under the covers and I have emailed the author with a couple of alternatives.

      Cheers - L~R

Re: Picking unique element in random from a array
by kwaping (Priest) on Aug 09, 2006 at 17:22 UTC
    Are all the elements of your array unqiue? Also, do you mind if the original array is altered? If your answers are "yes" and "no", respectively, then this alteration to your code will work.
    for my $x (0..9) { my $element = splice(@quest,rand @quest,1); print $element; }
    If you don't want to alter the original array, just make a copy of it first.

    ---
    It's all fine and dandy until someone has to look at the code.
Re: Picking unique element in random from a array
by Moron (Curate) on Aug 09, 2006 at 15:25 UTC
    Can also sort-of explicitly sort randomly:
    my $limit = 0; for $selection ( sort { rand() <=> 0.5 } @array ) { print "$selection\n"; ( ++$limit < 10 ) or last; }
    (updated to fix a minor problem). Or, if wanting to produce a new array of the results:
    @output = sort { rand() <=> 0.5 } @input; $#output = 9; #truncate the randomly sorted copy

    -M

    Free your mind

Re: Picking unique element in random from a array
by pajout (Curate) on Aug 10, 2006 at 11:54 UTC
    It should work, I hope that it does not need explanation:
    #!/usr/bin/perl my $n = 1000; my $cnt = 10; my %ret; for (my $i = 0; $i < $cnt; $i++) { my $r = int(rand()*$n); $r++ while exists $ret{$r}; $ret{$r} = undef; $n--; } print "$cnt random indexes:\n"; foreach (keys %ret) { print "$_\n"; }
      I am sorry, I'v made a mistake:

      my $n = 1000; my $cnt = 10; my @ret; for (my $i = 0; $i < $cnt; $i++) { my $r = int(rand()*$n); my $j = 0; while ($j <= $r) { $r++ if defined $ret[$j]; $j++; } $ret[$r] = 1; $n--; } print "$cnt random indexes:\n"; for (my $i = 0; $i < $n+$cnt; $i++) { print "$i\n" if $ret[$i]; }
      As you can see, the repaired algorithm does ~ $n*$cnt cycles. It is too naive. Therefore, it should be better to use some previously adviced libraries.