Welcome to the Monastery PerlMonks

### Re: Select value from array w/o replacement (or destroying the array)

by fenLisesi (Priest)
 on Dec 03, 2007 at 09:33 UTC ( #654527=note: print w/replies, xml ) Need Help??

I think tye's solution is the best. If for some reason you would prefer one of the others, the answer would depend on how big n is compared to the size of the array. If it is small, say, smaller than 10%, then just pick a random number in the range 0..\$#a and pick another one if that had been picked before. Such collisions should be rare for small n / @a; If it is larger, then shuffle first, but do not shuffle the array itself, especially if the array elements themselves tend to be big (which doesn't seem to be the case here). Instead, create a separate index array and shuffle that -- something like @indices = shuffle 0..\$#a. Below, I tried to add comments to tye's code. I may have messed up, so wait until wiser monks have had a chance to peruse it.

```use strict;
use warnings;

## How many elements to pick
use constant N => 10;

## The original array that must not be altered
my @letters = ('a'..'z');

##-----------------------------------------------------+
## Generate code that will pick an element from
##     @letters at each iteration without repeating
##-----------------------------------------------------+
my \$picker_of_next_rand = gen_picker( \@letters );

## Now execute (iterate) that code N times
for (1..N) {
print \$picker_of_next_rand->();
}
print "\n";            ## look nice
print @letters, "\n";  ## original untouched
exit( 0 );

##-----------------------------------------------------+
## This sub generates and returns code that will pick
##     a random element from @\$aref at each iteration
##     without repeating
##-----------------------------------------------------+
sub gen_picker {
my (\$aref) = @_;
my @indices = 0 .. \$#\$aref;

## closes on @indices
return sub {
if (! @indices) {
die "No more items left to pick from";
}

##---------------------------------------------+
## pick an int in the range (0 .. \$#\$aref),
## the range of the array's indices
##---------------------------------------------+
my \$random_index = int rand @indices;

## \$pick is the element at the random_index
my \$pick = \$aref->[ \$indices[ \$random_index ] ];

##---------------------------------------------+
## We are about to pop the index array.  Save
## the element at the pop-end of the index array
## by copying it into the position of the \$pick
## (clobber index of \$pick, won't be used again)
##---------------------------------------------+
\$indices[ \$random_index ] = \$indices[ -1 ];

## Now it is safe to pop the index array
pop @indices;

return \$pick;
};
}

which prints something like the following:

```vkdbfymxie
abcdefghijklmnopqrstuvwxyz

Replies are listed 'Best First'.
Re^2: Select value from array w/o replacement (or destroying the array)
by BioNrd (Monk) on Dec 04, 2007 at 16:30 UTC
I agree that this solution is going to suit my needs. The code works really well, but I don't understand a part of it.
```my \$picker_of_next_rand = gen_picker( \@letters );

print \$picker_of_next_rand->();
I under stand that is is a reference/dereference type thing.

What I don't understand is how to use the variable \$pick in a meaningful (non-global) way. i.e. \$var_to_use_later = \$list[\$pick]; I have had trouble with reference/dereference structure before, and I have tried to use the camal book. It is not sinking in, and combined with a bit of code I am not familar with...it is a headache for me.

I appreciate all that has been done to this point for me, it is just that last little push in the right direction I need.

Thanks...Bio.

Even a blind squirrel finds a nut sometimes.

Create A New User
Node Status?
node history
Node Type: note [id://654527]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (9)
As of 2019-05-22 15:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
Do you enjoy 3D movies?

Results (140 votes). Check out past polls.

Notices?