tomazos has asked for the wisdom of the Perl Monks concerning the following question:
I'm trying to write a simple shuffling function...
sub shuffle($$$) {
my ($seed, $max, $input) = @_;
# $max > $input
# $max > 0
# $input >= 0
my $result;
# calculate $result
# $result >= 0
# $result < $max
return $result
}
...such that shuffle($K1, $K2, $x) == shuffle($K1, $K2, $y) if and only if $x == $y.
shuffle returns a semirandom number.
This could be used to iterate randomly over a readonly list inplace.
ie
get_rand(\@$$) {
my ($list, $seed, $input) = @_;
return $list>[shuffle($seed, scalar(@$list), $input)];
}
my $list = [10, 20, 30, 40, 50, 60];
for (my $i = 0; $i < scalar(@$list); $i++) {
print get_rand($list, 42, $i) . ' ';
}
# might print 40 10 20 60 30 50
Any math geniuses out there care to fill in the calculate $result part? I'm sure it has something to do with moding and hashing and stuff.
(No, this isn't a homework question. :) I actually need it to iterate randomly over a list in a resourceconstrained embedded environment where I can't move the memory around  and haven't got spare memory. I knew I shouldn't have slept through my CS lectures.)
Thanks in advance.
Andrew.
Re: Random 11 mapping
by Zaxo (Archbishop) on May 28, 2006 at 18:10 UTC

Since an array has the factorial of its element count possible orders, your $input must have that range to obtain a 11 relation. Are you sure that so stringent a requirement is needed?
Check out List::Util::shuffle(). It may be good enough for what you want.
You won't want to keep C++ comments in your perl, and prototypes are usually not desirable. See perltrap.
 [reply] 

shuffle(42, 10, 0) == 6
shuffle(42, 10, 1) == 1
shuffle(42, 10, 2) == 5
shuffle(42, 10, 3) == 8
shuffle(42, 10, 4) == 3
shuffle(42, 10, 5) == 4
shuffle(42, 10, 6) == 9
shuffle(42, 10, 7) == 2
shuffle(42, 10, 8) == 7
shuffle(42, 10, 9) == 0
?
Andrew.
 [reply] [d/l] 
Re: Random 11 mapping
by QM (Parson) on May 28, 2006 at 18:56 UTC

If you want to walk the list in a random order ("without replacement" in the vernacular), then you need something to maintain state, and be aware of the size of your list (both initially and at each call).
Your claims of no spare memory make this difficult. Either you need a scheme that implicitly keeps track of which elements have already been returned, or you need to explicitly keep track of them.
On the explicit front, you can keep a record in a bit vector, with one bit for each element already used. However, to get the last element of a large list, using a PRNG will take a long time.
On the implicit front, you need an iterator thats reasonably random, yet is exactly uniformly distributed regardless of the size of your list. This makes me think of a prime modulo operation:
#!/your/perl/here
use strict;
use warnings;
{ # closure for "static" variables
my $mult = 2**151;
my $offset = 76543;
my $state = 0;
sub get_rand
{
my $list = shift; # ref
$state = ($state*$mult+$offset)%scalar(@{$list});
return $list>[$state];
}
sub init_get_rand
{
my $list = shift;
$state = rand(scalar(@{$list}));
}
}
my $list = [0..17];
for (1..2)
{
init_get_rand($list);
for ( 1..@{$list} )
{
print get_rand($list), " ";
}
print "\n";
}
__OUTPUT__
8 9 16 11 12 1 14 15 4 17 0 7 2 3 10 5 6 13
2 3 10 5 6 13 8 9 16 11 12 1 14 15 4 17 0 7
Now, I'll be the first to admit I haven't checked on good values for $mult, $offset, or whether lists of certain sizes will cause you to miss values, etc. But you should be able to look up a good PRNG on your own ;)
QM

Quantum Mechanics: The dreams stuff is made of
 [reply] [d/l] [select] 

I've possibly been confusing with the use of the word iterate. The list is actually directly accessible. I can access element x cheaply and statelessly  the records are fixed sized and contiguous.
I just want to walk this list in a welldefined, reproducible, randomorder.
I think multiplying by a prime and then dividing by the maximum has something to do with producing the 11 mapping (was it RSA or abstract algebra / group/ring theory  I'm too old for this sh*t).
Andrew.
 [reply] 

1) Did this code/idea help you?
2) You need an offset if zero is involved.
3) Yes, multiplying by a prime mod some other prime will do what you want. There are probably several other configurations that will do the same, and some will be better than what I've given. There are numerous approaches, rules, tests, and other info that you can dig up. I'd start with Mathworld or Wikipedia.
QM

Quantum Mechanics: The dreams stuff is made of
 [reply] [d/l] 
Re: Random 11 mapping
by BerntB (Deacon) on May 28, 2006 at 18:44 UTC

What you want is a perfect hash function, that is inexpensive in computing time?
Why not ask for something simple, like a perpetum mobile? :)
The $seed $input is the place in the original list? Maybe permutation encryption algorithms? But to keep them below $max...?
Update: If you could keep $max to a power of 2, you could maybe XOR with a constant? (Update2: The $seed (constant) would also have to be as many bits as $max.)
Code example for Update (n.b. $max must be 2^X):
my $max = 8;
my $list = [10, 20, 30, 40, 50, 60, 70, 80];
for(my $seed = 0; $seed < $max; $seed++) {
print "With seed $seed: ";
for(my $i = 0; $i < @$list; $i++) {
my $place = get_rand($i, $seed);
print "$place:", $list>[$place], " ";
}
print "\n";
}
sub get_rand {
my($item, $seed) = @_;
return $item ^ $seed;
}
__END__
# 
# output:
With seed 0: 0:10 1:20 2:30 3:40 4:50 5:60 6:70 7:80
With seed 1: 1:20 0:10 3:40 2:30 5:60 4:50 7:80 6:70
With seed 2: 2:30 3:40 0:10 1:20 6:70 7:80 4:50 5:60
With seed 3: 3:40 2:30 1:20 0:10 7:80 6:70 5:60 4:50
With seed 4: 4:50 5:60 6:70 7:80 0:10 1:20 2:30 3:40
With seed 5: 5:60 4:50 7:80 6:70 1:20 0:10 3:40 2:30
With seed 6: 6:70 7:80 4:50 5:60 2:30 3:40 0:10 1:20
With seed 7: 7:80 6:70 5:60 4:50 3:40 2:30 1:20 0:10
 [reply] [d/l] 

$result = ($prime_number * $input + $seed) % $max;
Example for $max = 10, $prime_number = 7 and $seed = 5..
shuffle(5,10,0) == (7 * 0 + 5) % 10 == 5 % 10 == 5
shuffle(5,10,1) == (7 * 1 + 5) % 10 == 12 % 10 == 2
shuffle(5,10,2) == (7 * 2 + 5) % 10 == 19 % 10 == 9
shuffle(5,10,3) == (7 * 3 + 5) % 10 == 26 % 10 == 6
shuffle(5,10,4) == (7 * 4 + 5) % 10 == 33 % 10 == 3
shuffle(5,10,5) == (7 * 5 + 5) % 10 == 40 % 10 == 0
shuffle(5,10,6) == (7 * 6 + 5) % 10 == 47 % 10 == 7
shuffle(5,10,7) == (7 * 7 + 5) % 10 == 54 % 10 == 4
shuffle(5,10,8) == (7 * 8 + 5) % 10 == 61 % 10 == 1
shuffle(5,10,9) == (7 * 9 + 5) % 10 == 68 % 10 == 8
So that works for 7 and 10. Does it work for any $max and any $prime_number? If not what conditions will it work for? I am no good with proofs.
Andrew.
 [reply] [d/l] [select] 

f(x) = (a*x + b) % m
is 1to1 (restricted to 0 <= x < m) as long as gcd(a,m)==1, since that's the only time you can find an inverse to a mod m, and invert the function.
I would suggest splitting your "seed" value into the a & b coefficients in the following way:
 a = largest number less than seed satisfying gcd(a,m)=1
 b = seed  a
sub gcd { $_[1] ? gcd($_[1], $_[0] % $_[1]) : $_[0] }
sub shuffle {
my ($seed, $max, $i) = @_;
my $ca = $seed;
$ca until gcd($ca, $max) == 1;
my $cb = $seed  $ca;
($ca * $i + $cb) % $max;
}
for my $seed (1 .. 10) {
my @result = map shuffle($seed, 15, $_), 0 .. 14;
print "seed=$seed ==> @result\n";
}
But take this approach for what it's worth  The only kinds of mapping you'll get by this process are simple linear (affine) mappings, which may not "look random enough":
seed=1 ==> 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
seed=2 ==> 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13
seed=3 ==> 1 3 5 7 9 11 13 0 2 4 6 8 10 12 14
seed=4 ==> 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11
seed=5 ==> 1 5 9 13 2 6 10 14 3 7 11 0 4 8 12
seed=6 ==> 2 6 10 14 3 7 11 0 4 8 12 1 5 9 13
seed=7 ==> 0 7 14 6 13 5 12 4 11 3 10 2 9 1 8
seed=8 ==> 0 8 1 9 2 10 3 11 4 12 5 13 6 14 7
seed=9 ==> 1 9 2 10 3 11 4 12 5 13 6 14 7 0 8
seed=10 ==> 2 10 3 11 4 12 5 13 6 14 7 0 8 1 9
For more "unpredictable" orders, you could have more tools at your disposal if $max is always a prime. Then you take one of the linear sequences above and use it as a sequence of powers of a generator element for the field mod $max.
 [reply] [d/l] [select] 
Re: Random 11 mapping
by TedPride (Priest) on May 28, 2006 at 23:33 UTC

As stated above, you're good to go as long as max % prime != 0:
use strict;
use warnings;
my ($seed, $prime, $max, $input, $result);
$seed = 8;
for $prime (2,3,5,7,11,13,17,19,23) {
for $max (20..30) {
next if $max % $prime == 0;
my %hash;
for $input (0..($max1)) {
$result = ($prime * $input + $seed) % $max;
$hash{$result}++;
}
print "$prime $max : ";
print join '', values %hash, "\n";
}
}
All you need to guarantee this is a sufficiently large prime so that it's always going to be larger than sqrt(max) (but not equal to max itself).
use strict;
use warnings;
my (@arr, $seed, $max, $prime, $input, $result);
@arr = 1..10;
$seed = 5;
$max = $#arr+1;
$prime = 65521;
for $input (0..($max1)) {
$result = ($prime * $input + $seed) % $max;
print $arr[$result], " ";
}
The hard part though, since I assume you're doing this in assembly or a low level language, will be making sure that none of the basic mathematical operations go out of bounds.  [reply] [d/l] [select] 

