Re: Array Shuffle
by frenchtoast (Acolyte) on Feb 28, 2006 at 03:24 UTC
|
see Algorithm::Combinatorics::derangements(\@data)
"The derangements of @data are those reorderings that have no element in its original place. In jargon those are the permutations of @data with no fixed points." | [reply] |
Re: Array Shuffle
by dragonchild (Archbishop) on Feb 28, 2006 at 02:48 UTC
|
List::Util has shuffle().
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
|
dragonchild,
Sure List::Util has shuffle() but how does this meet the requirement. Perhaps I am misreading it or perhaps the root thread was updated after you replied but I believe the requirement also mandates that elements not remain in their original position. This is a derangement and I think that Derangement of a list and Derangements iterator are probably more applicable. I also see that frenchtoast had the same idea elsewhere in the thread.
| [reply] |
Re: Array Shuffle
by Thelonius (Priest) on Feb 28, 2006 at 03:19 UTC
|
I don't have a solution to this, but an interesting bit of mathematical trivia is that if you shuffle at random the odds that none of the elements ends up in the original space approaches 1/e as n -> infinity.
| [reply] |
|
|
So on average, for large n, 3 shuffles should suffice?
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] |
Re: Array Shuffle
by friedo (Prior) on Feb 28, 2006 at 02:55 UTC
|
List::Util's shuffle is good, but it's totally random, so it doesn't guarantee anything about the position of the shuffled elements. A brute-force solution would be to shuffle repeatedly until you end up with a satisfactory order, but that really sucks. | [reply] |
|
|
| [reply] |
|
|
| [reply] |
Re: Array Shuffle
by zer (Deacon) on Feb 28, 2006 at 04:45 UTC
|
sub shuffler{
foreach (@_){
do{$r = int(rand($#_+1)); }while ((($r == $c) ||($temp[$r])));
$temp[$r]= $_;$c++;
}
return @temp;
}
Thatll do it for you for a 1 dimensional array
hope it is what you were looking for
Edited by planetscape - added code tags
| [reply] [d/l] |
|
|
| [reply] |
Re: Array Shuffle
by phaylon (Curate) on Feb 28, 2006 at 02:50 UTC
|
| [reply] |
Re: Array Shuffle
by Roy Johnson (Monsignor) on Feb 28, 2006 at 15:34 UTC
|
A derangement is just a mapping from every element of a set to some other element of the set. The simplest derangement is to map each element to its neighbor. And if you randomize the list before doing that mapping, you have a random derangement (mapping). You can then use that mapping on the original list to get a derangement of the original list.
use strict;
use warnings;
my @array = (1..9);
sub derange {
use List::Util 'shuffle';
my @list = shuffle @_;
my %map = map {($list[$_-1] => $list[$_])} 0..$#list;
print "@map{@_}\n";
}
print "@array\n";
derange(@array) for 1..15;
You'll see that in the output, no element appears in its original position (after the first line, which is there for reference).
One slightly non-obvious thing I did was to use negative indexing so that my mapping would wrap around from the last element to the first.
Caution: Contents may have been coded under pressure.
| [reply] [d/l] |
|
|
That works well for 3 elements, but for 4, there are some derangements it never generates, for example (2, 1, 4, 3).
| [reply] |
|
|
An astute observation. For those who wonder why, it's because the mapping required to produce 2 1 4 3 is 1 => 2, 2 => 1, 3 => 4, 4 => 3, and my mapping is a big cycle. If I get a bit of time to work on it, I'll post some updated code that allows for subcycles.
Update (again: previous code that appeared here was broken): If you modify the Fisher-Yates algorithm to force every element to swap with a higher element (instead of possibly "swapping" with itself), then almost all derangements are possible; all derangements become possible (thought not equally likely) if you use a random position in the list as the first location to swap from, and allow some swaps to be skipped if the candidates are already deranged.
use strict;
use warnings;
sub derange {
my @list = @_;
# Swap every element with something higher
my $start = int rand @list;
for (0..($#list-1)) {
my $this = ($start + $_) % @_;
next if $list[$this] ne $_[$this] and $_ < $#list-1 and rand > .52
+;
my $other = $_ + 1 + int rand($#list - $_);
$other = ($start + $other) % @_;
@list[$this,$other] = @list[$other,$this];
}
"@list";
}
my @ar = ('a'..'d');
print "@ar\n";
my %countem;
$countem{derange @ar}++ for 1..5000;
print "$_: $countem{$_}\n" for (sort keys %countem);
Caution: Contents may have been coded under pressure.
| [reply] [d/l] |
Re: Array Shuffle
by spiritway (Vicar) on Feb 28, 2006 at 04:34 UTC
|
Are you saying that *none* of the elements can be in the same position as the original array? If so, then I suppose you'd have to compare the two arrays after the shuffle, and reject the shuffle if you found any two elements equal.
BTW, is this by any chance a homework problem?
| [reply] |
|
|
It is, and I am/was having a lot of trouble determining a solution to the given problem. Part of the problem is that I am relatively new to perl, and its been a while since I last programmed in Java, so I am quite rusty. However, I would not have just copied and pasted any code and put it off as my own either. More looking for some pseudo code/algorithm to accomplish this.
| [reply] |
|
|
| [reply] |
|
|
| [reply] |
Re: Array Shuffle
by ambrus (Abbot) on Feb 28, 2006 at 12:18 UTC
|
You could shuffle the array normally and check that no element remains in its place, and I think that wouldn't be so inefficent either, as IIRC the probability that no element remains in its place is 1/e, so this would require only 3 shufflings on average.
| [reply] |
Re: Array Shuffle
by QM (Parson) on Feb 28, 2006 at 18:12 UTC
|
What happens if the array has only 1 element?
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] |
Re: Array Shuffle
by ambrus (Abbot) on Feb 28, 2006 at 12:28 UTC
|
I've run your code a few times for the array (1, 2, 3), and it seems that it always returns (3, 2, 1), while it should return (2, 3, 1) or (3, 1, 2) so I think it doesn't do what you want.
It gives similarly wrong results for the array (1, 2, 3, 4) and gets in an infinite loop for (1, 2).
| [reply] |
Re: Array Shuffle
by Discipulus (Canon) on Feb 28, 2006 at 10:14 UTC
|
perl -e "my @toshuffle = qw (a b c d e f g h);$rand{$_}=1 for @toshuff
+le;print keys %rand;"
##OR in a sub-way
sub shuffle_array{
my @ar = @_;
my %rand;
$rand{$_} = undef for @ar;
my @ret = keys %rand;
return @ret;
}
cheers lorenzo* | [reply] [d/l] |
|
|
From perlsec:
Also note that while the order of the hash elements might be randomised, this "pseudoordering" should not be used for applications
like shuffling a list randomly (use List::Util::shuffle() for that,
see List::Util, a standard core module since Perl 5.8.0; or the
CPAN module Algorithm::Numerical::Shuffle), or for generating permutations (use e.g. the CPAN modules Algorithm::Permute or Algorithm::FastPermute), or for any cryptographic applications.
And indeed, your subroutine returns the same order if you call it twice for the same array in the same perl instance.
Your code doesn't guarantee that no elements get to their place.
If you don't have the criterion that no elements get into their place, I see no reason to use this stupid way instead of one of the simpler ways:
-
use List::Util "shuffle";
sub shuffleArray{
shuffle(@_);
}
-
sub shuffleArray{
my @p = @_;
my $k;
$k = $_ + int(rand(@p - $_)),
@p[$_, $k] = @p[$k, $_] for
0 .. @p - 1;
@p;
}
-
sub shuffleArray{
my @p = @_;
map { splice @p, $_ + rand(@p - $_), 1, $p[$_] } 0 .. @p - 1;
}
-
sub shuffleArray{
my @p = @_;
my $t;
map { $t = splice @p, rand(@p), 1, $p[0]; shift @p; $t } 0 ..
+@p - 1;
}
-
sub shuffleArray{
my @w = map { rand } @_;
@_[sort { $w[$a] <=> $w[$b] } 0 .. @_ - 1];
}
-
sub shuffleArray{
map { $$_[1] } sort { $$a[0] <=> $$b[0] } map { [rand, $_] } @
+_;
}
-
sub shuffleArray{
map { $$_[0] } sort { ref $a <=> ref $b } map { bless [$_], ra
+nd } @_;
}
| [reply] [d/l] [select] |