No such thing as a small change PerlMonks

### getting random number 8 times never the same

 on Oct 31, 2010 at 16:23 UTC Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I want to get 8 random numbers from a list, from these: 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 so I guess I would put those into an array:
```@nums = (4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
Now, how do I get a random one that is unique from each other in 8 different variables? for example:
```\$_num1 = join("", @nums[ map { rand @nums } ( 1 .. 1 ) ]);
Now how would I remove that number from the array? Is that the best way to get a random number from the list?

thanks,
Rich

Replies are listed 'Best First'.
Re: getting random number 8 times never the same
by McDarren (Abbot) on Oct 31, 2010 at 16:37 UTC
You could try something like this:
```#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper::Simple;
use List::Util qw/shuffle/;

my @nums = (4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
my @shuffled_nums = shuffle(@nums);
my %extracted_nums;

for (0 .. 7) {
\$extracted_nums{\$_} = shift @shuffled_nums;
}

print Dumper(%extracted_nums);
print Dumper(@shuffled_nums);
%extracted_nums will contain the numbers removed from the original list, and @shuffled_nums will contain those left over.

Is that what you wanted?

Cheers,
Darren

Re: getting random number 8 times never the same
by ikegami (Pope) on Oct 31, 2010 at 17:04 UTC

The following would do the trick:

```my @selection = @nums;
for (0..7) {
my \$i = \$_ + rand(@selection - \$_);
(\$selection[\$_], \$selection[\$i]) = (\$selection[\$i], \$selection[\$_])
}

splice(@selection, 8);

But it would be better (simpler, cleaner, faster) to just use List::Util's shuffle.

```use List::Util qw( shuffle );

my @selection = (shuffle(@nums))[0..7];
Perfect, both worked, but yours used less code, very nice. thanks!
Re: getting random number 8 times never the same
by BrowserUk (Pope) on Oct 31, 2010 at 17:16 UTC

Depending on how big the list is, this might be more efficient than shuffling the whole list for a small number of picks:

```my @a = 4..20;;
my @s = map splice( @a, rand( @a ), 1 ), 1 .. 8;;

print "@a\n@s";;
4 5 7 8 9 12 13 14 18
11 10 6 20 15 17 19 16

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
For long lists, that isn't more efficient than shuffling. Shuffling means going through the list once, swapping each element. Splicing means going through (on average) half the list in each iteration moving the element one to the left.

Splicing a random element out of an array takes on average linear time.

For long lists, that isn't more efficient than shuffling.

Regardless of the length of the list, which is quicker depends upon the ratio of picks.

...for a small number of picks, Ie. If the ratio of picks to list size is less than ~15%, spliceing is quicker than shuffling the whole list:

```#! perl -slw
use strict;
use List::Util qw[ shuffle ];
use Benchmark qw[ cmpthese ];

our \$N //= 20;
our \$S //= 8;
our @nums = 0 .. \$N;

cmpthese -1, {
shuffle => q[ my @s = ( shuffle @nums )[ 0 .. \$S-1 ]; ],
splice  => q[ my @s = map splice( @nums, rand( @nums ), 1 ), 1 .. \$
+S; ],
};

__END__
c:\test>868601 -N=10 -S=2
Rate shuffle  splice
shuffle 894654/s      --    -10%
splice  993686/s     11%      --

c:\test>868601 -N=10 -S=3
Rate  splice shuffle
splice  769417/s      --     -9%
shuffle 844675/s     10%      --

c:\test>868601 -N=100 -S=15
Rate shuffle  splice
shuffle 196571/s      --     -5%
splice  207873/s      6%      --

c:\test>868601 -N=100 -S=17
Rate  splice shuffle
splice  186995/s      --     -3%
shuffle 192359/s      3%      --

c:\test>868601 -N=1000 -S=169
Rate shuffle  splice
shuffle 19552/s      --     -2%
splice  19968/s      2%      --

c:\test>868601 -N=1000 -S=170
Rate  splice shuffle
splice  20274/s      --     -1%
shuffle 20569/s      1%      --

c:\test>868601 -N=10000 -S=1998
Rate shuffle  splice
shuffle 1578/s      --     -6%
splice  1674/s      6%      --

c:\test>868601 -N=10000 -S=1999
Rate  splice shuffle
splice  1601/s      --     -1%
shuffle 1625/s      2%      --

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
Re: getting random number 8 times never the same
by davies (Parson) on Oct 31, 2010 at 18:12 UTC
This is the standard "pack of cards" algorithm that I mentioned in Re: Random data generation., misunderstanding BrowserUk's question. Changing the code for your case would give:
```use strict;
use warnings;

my \$nRepeats = 1;
my @sSet = (4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
my \$nLength = 8;

my @sAll;
for (1..\$nRepeats) {
push (@sAll, @sSet);
}

if (\$nLength - 1 > \$#sAll) {die "Don't be silly"}

my \$sString;
for (1..\$nLength) {
my \$i = int(rand(@sAll));
\$sString .= \$sAll[\$i];
\$sAll[\$i] = \$sAll[-1];
pop(@sAll);
}

print "\$sString \n";
I haven't tested this variation, but I tested the original.

Regards,

John Davies
Re: getting random number 8 times never the same
by aquarium (Curate) on Oct 31, 2010 at 22:25 UTC
if your list is always a continuous range, as per the example, then a non-list solution would suffice.
for example the famous one liner
```perl -le '\$n=10; \$min=5; \$max=15; \$, = " "; print map { int(rand(\$max-
+\$min))+\$min } 1..\$n'
comes to mind
the hardest line to type correctly is: stty erase ^H
These random numbers would not necessarily be unique
```> perl -le "\$n=10;\$min=5;\$max=15;\$,=qq( );print map {int(rand(\$max-\$mi
+n)+\$min)} 1..\$n"

9 9 12 10 11 12 9 5 10 9
Re: getting random number 8 times never the same
by Khen1950fx (Canon) on Nov 01, 2010 at 06:12 UTC
To get 8 random numbers from the list, I used random. It requires 5.10 though...
```#!/usr/bin/perl

use 5.010;
use strict;
use warnings;
use random qw(integer);
use Data::Dumper::Concise;

my @array = (4 .. 20);
for (0 .. 7) {
my \$array = 4 + rand 20;
print Dumper(\$array);
}
Re: getting random number 8 times never the same
by bduggan (Pilgrim) on Nov 01, 2010 at 17:27 UTC
You could use a reservoir sampling algorithm (see also perldoc -q random) :
```#!/usr/bin/env perl

my \$k = shift @ARGV;
srand;
while (<>) {
\$. <= \$k and do { push @lines, \$_; next; };
(\$a=rand(\$.)) < \$k and (\$lines[\$a] = \$_);
}
print @lines;
For instance,
```\$ seq 4 20 | ./rand.pl 8
19
5
6
14
16
9
15
11
Re: getting random number 8 times never the same
by sundialsvc4 (Abbot) on Nov 02, 2010 at 00:14 UTC

Over the years, I have found that there are three ways of looking at this problem, all three of which are appropriate in different situations:

1. “Fuhgeddaboudit!”   Don’t borrow trouble.   If the domain of random numbers is big enough (such as, “a 32-bit integer”), and the pseudo-random number generator (PRNG) is good enough (it is...), then the possibility of getting the same number twice in a reasonable length of time is near-zero.   (If it were not so, then neither Monaco nor Las Vegas would still be full of casinos.)
2. “Don’t worry about checking.”   If the domain is small, but the number-count that you actually need is also small, then the practical price to be paid by brute-force checking for dupes is also acceptably small.
3. If the problem that you are dealing with, is approximately equal to “dealing from a deck of cards,” then ... do that.   Initialize an array of “all possible values,” then shuffle it and “deal cards” from it.   (As noted, you don’t have to invent your own card-shuffling algorithm.)

Re: getting random number 8 times never the same
by Khen1950fx (Canon) on Nov 02, 2010 at 12:48 UTC
I've been working on this all day. I based it on this. It works:).
```#!/usr/bin/perl

use strict;
use warnings;

my \$length  = 1;
my \$max     = 1;
my @strings;
for ( 1 .. \$max ) {
push @strings, rand_nums(\$length);
}
print "@strings", "\n";

{
my %cache;

sub rand_nums {
my %local_cache;
my (\$length) = 8;
my \$lower    = 4;
my \$upper    = 21;
my \$serial = int( rand(\$upper - \$lower) ) + \$lower;
\$local_cache{\$serial} = 1;
for ( 2 .. \$length ) {
my \$num = int( rand(\$upper - \$lower) ) + \$lower;
redo if exists \$local_cache{\$num};
\$local_cache{\$num} = 1;
\$serial .= "-\$num";
}
rand_nums(\$length) if exists \$cache{\$serial};
\$cache{\$serial} = 1;
return \$serial;
}
}

Create A New User
Node Status?
node history
Node Type: perlquestion [id://868601]
Approved by McDarren
Front-paged by Arunbear
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (3)
As of 2017-08-23 22:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
Who is your favorite scientist and why?

Results (360 votes). Check out past polls.

Notices?