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