views:

359

answers:

4

Does anyone know how to shuffle two arrays randomly in exactly the same way in Perl? For example, say I have these two arrays:

Before shuffling: array 1: 1, 2, 3, 4, 5 array 2: a, b, c, d, e

After shuffling: array 1: 2, 4, 5, 3, 1 array 2: b, d, e, c, a

So every element in each array is bound to its equivalent element.

+5  A: 

Use List::Util shuffle to shuffle a list of indexes and map the results onto the arrays.

use strict;
use warnings;

use List::Util qw(shuffle);

my @array1 = qw( a b c d e );
my @array2 = 1..5;

my @indexes = shuffle 0..$#array1;
my @shuffle1 = map $array1[$_], @indexes;
my @shuffle2 = map $array2[$_], @indexes;


Update Use Chris Jester-Young's solution. Array slices are a better choice that I should have thought of.

daotoad
You don't need to use `map`; arrays can be indexed by another array, that contains the indices to get. :-)
Chris Jester-Young
+22  A: 

Try (something like) this:

use List::Util qw(shuffle);
my @list1 = qw(a b c d e);
my @list2 = qw(f g h i j);
my @order = shuffle 0..$#list1;
print @list1[@order];
print @list2[@order];
Chris Jester-Young
++ for the use of list slices. I don't remember to use them as often as I should.
daotoad
@daotoad: I doubly love hash slices: @foobar{qw(foo bar baz qux)} :-)
Chris Jester-Young
Thank you very much!
Abdel
@Brad Gilbert: I understand you think turning `qw(a b c d e)` into `qw'a b c d e'` improves highlighting and this is all very subjective, but bear in mind that `'` is much harder to see than `(` and most Perl code out there uses `qw()` or `qw//`. I am not sure the loss in legibility is worth the improvement in colors.
Sinan Ünür
I would add a: # optional... @list1 = @list1[@order]; @list2= @list2[@order];
ysth
@Sinan: Agree, and the "rollback" link is staring at me, tempting. :-) (BTW I like your approach too and I +1'd it; but I love list/hash slices too much to not use this approach. :-P)
Chris Jester-Young
@Chris Thank you for both rolling back and the vote.
Sinan Ünür
+8  A: 

First: parallel arrays are a potential sign of bad code; you should see if you can use an array of objects or hashes and save yourself that trouble.

Nonetheless:

use List::Util qw(shuffle);

sub shuffle_together {
  my (@arrays) = @_;

  my $length = @{ $arrays[0] };

  for my $array (@arrays) {
    die "Arrays weren't all the same length" if @$array != $length;
  }

  my @shuffle_order = shuffle (0 .. $length - 1);

  return map {
    [ @{$_}[@shuffle_order] ]
  } @arrays;
}

my ($numbers, $letters) = shuffle_together [1,2,3,4,5], ['a','b','c','d','e'];

Basically, use shuffle to produce a list of indices in random order, and then slice all of the arrays with the same list of indices.

hobbs
+1 for pointing out that you shouldn't have parallel arrays, but arrays of something else (arrays, hashes, objects, whatever) that keep the data together physically.
Tanktalus
+1 for making sure the array ARE the same size
lexu
@Tanktalus: "shouldn't" is overly strong. It is code smell, but sometimes that's just the way it is.
ysth
+5  A: 

Here is another way:

use strict;
use warnings;

use List::AllUtils qw(pairwise shuffle);

my @list1 = qw(a b c d e);
my @list2 = qw(f g h i j);

my @shuffled_pairs = shuffle pairwise{[$a, $b]} @list1, @list2;

for my $pair ( @shuffled_pairs ) {
    print "$pair->[0]\t$pair->[1]\n";
}

Output:

C:\Temp> sfl
e       j
b       g
d       i
a       f
c       h

This way, you can iterate directly over @shuffled_pairs without needing to keep an extra array for the indexes and avoid C-style loops.

Sinan Ünür
+1 for using the way I was thinking.
Axeman