Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Randomize elements among several arrays, while maintaining original array sizes.

by BioNrd (Monk)
on Aug 01, 2008 at 15:40 UTC ( #701729=perlquestion: print w/replies, xml ) Need Help??

BioNrd has asked for the wisdom of the Perl Monks concerning the following question:

I know how to randomize an array 31461. I know how to splice elements among arrays.

What I don't know is how to combine the two, such that I shuffle items in array A among all possible other arrays. e.g. I randomly assign where items in array A go (from A into A, B, C ... N), while maintaining the size of A, B, C, ... N.

Here is how I have been thinking about it thus far:

use strict; use warnings; my @array_a = (10, 22, 34, 21, 20, 12, 32, 31, 91, 20); my @array_b = (10, 22, 34, 21, 20, 12, 32, 31, 91, 20, 11, 32, 44, 51, + 10, 22, 82, 71, 61, 54); my @array_c = (14, 25, 64, 71, 80); my @new_a = (); my @new_b = (); my @new_c = (); my @sizes = (10, 20, 5); #sizes of array obtained elsewhere my @where_to = ('a', 'b', 'c'); foreach my $move_to (@where_to) { my $size = shift @sizes; my @moving = (); for (1 .. $size) { my $index = int rand @where_to; my $element = $where_to[$index]; push @moving, $element; } print "@moving\n"; foreach my $moving (@moving) { # my $moved = shift @array_$move_to; #I am not sure how to do this # push @new_$moving, $moved; #Again now sure how to do this #But even lacking the correct bit of code here, you can see, # where I am going with this. } }
The problem with this is that it moves to many or not enough items into A B and C. So, is there a way to keep the sizes of the arrays same, but shuffle the items as I have tried to do above?

Thanks for any help the esteemed monks can give,
-Bio.

---- Even a blind squirrel finds a nut sometimes.
  • Comment on Randomize elements among several arrays, while maintaining original array sizes.
  • Download Code

Replies are listed 'Best First'.
Re: Randomize elements among several arrays, while maintaining original array sizes.
by BrowserUk (Pope) on Aug 01, 2008 at 16:54 UTC

    Here's a neat use of Perl's aliasing of @_.

    #! perl -slw use strict; ## Shuffle an array in place; sub shuffle { my $ref = @_ == 1 ? $_[ 0 ] : \@_; my $n = @$ref; for( 0 .. $#$ref ) { my $p = $_ + rand( $n-- ); my $t = $ref->[ $p ]; $ref->[ $p ] = $ref->[ $_ ]; $ref->[ $_ ] = $t; } return unless defined wantarray; return wantarray ? @{ $ref } : $ref; } my @a = 'a' .. 'c'; my @b = 1 .. 4; my @c = 'A' .. 'E'; shuffle( @a, @b, @c );; print "A:= [ @a ]\nB:= [ @b ]\nC:= [ @c ]"; __END__ c:\test>junk8 A:= [ c b D ] B:= [ 4 A 3 B ] C:= [ E 2 a 1 C ]

    Notice how after the shuffle, the elements of the arrays have been intermixed in the 3 arrays, but each knows how big it is. This is because @_ gets aliased to the elements of all 3 arrays.

    Thus, by shuffling @_ within the sub, we are shuffling the elements of all 3 arrays at the same time.


    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.
      Brilliant!

      Note that you can swap without temp variables:

      for( 0 .. $#$ref ) { my $p = $_ + rand( $n-- ); @{$ref}[$p,$_] = @{$ref}[$_,$p]; }
      You can even use List::Util 'shuffle':
      sub my_shuffle2 { my $ref = \@_; use List::Util 'shuffle'; @{$ref}[0..$#$ref] = shuffle(@$ref); return unless defined wantarray; return wantarray ? @{ $ref } : $ref; }

      Caution: Contents may have been coded under pressure.
      That will do it. Thanks for all the suggestions. This has helped a great deal.

      Thanks again!

      ---- Even a blind squirrel finds a nut sometimes.
        That will do it. Thanks for all the suggestions. This has helped a great deal.

        I personally believe that now that you acknowledge BrowserUk for gaving you an actual solution to your problem, it is eventually clear what the problem really was, which has not been the case for most of us up to this point. Let me try a possibly better phrasing:

        "Take N arrays and shuffle jointly all of their entries, redistribuiting them amongst the same N arrays while leaving their lenghts unchanged."

        In which case, I admit I would have worked with references, and I would have never ever thought of BUk's cool and neat trick: I generally don't write subs that modify stuff that's passed to them in place, but just pass by value and take return values. This, in turn, is most often sensible. But then one shouldn't blindly generalize: occasionally I deviate from that rule adopting the backslashed prototypes. In fact I am not ashamed to confess that I had to stare at BUk's code for some time, to understand how it could achieve a similar semantics without them. Of course it's very trivial instead, but it can easily seem not to be after you've been "reasoning" along different lines for years.

        Now, armed with:

        1. the precise definition of the problem,
        2. a convenient interface courtesy of @_'s aliasing properties,

        let me tell you that you can adoopt just about any shuffling technique you like, including the much recommended "do not reinvent the wheel and use List::Util's shuffle()" one:

        #!/usr/bin/perl use strict; use warnings; use List::Util 'shuffle'; sub shuffit { @_[0..$#_] = shuffle @_; } my @a = 'a' .. 'c'; my @b = 1 .. 4; my @c = 'A' .. 'E'; shuffit @a, @b, @c; print "A:= [ @a ]\nB:= [ @b ]\nC:= [ @c ]\n"; __END__ C:\temp>buk.pl A:= [ 2 D C ] B:= [ a 1 b 4 ] C:= [ 3 B c E A ]
        To complete the meditation above: while this remains a kind of interface that I'm not likely to resort to on a daily basis, I realize that it's both perfectly well suited for this particular case and for many other ones I may stumble upon. Which is the reason why I will always thank you for having brought up the question, even if the question itself is more of an aside in this respect.

        --
        If you can't understand the incipit, then please check the IPB Campaign.
Re: Randomize elements among several arrays, while maintaining original array sizes.
by pjotrik (Friar) on Aug 01, 2008 at 15:48 UTC
    Perhaps I'm failing to understand what you're trying to do, but why don't you shuffle the items inside a long array (with length being the sum of individual arrays lengths) and then split the array into slices of desired lengths?
Re: Randomize elements among several arrays, while maintaining original array sizes.
by InfiniteSilence (Curate) on Aug 01, 2008 at 16:19 UTC
    I'm not certain why the answers you got in the last node were unsatisfactory, but if you are just looking to move around elements, per array, around randomly you could do something like this:
    my @arr1 = qw|10 22 24 21 20 12 32 31 91 20|; my @arr2 = qw|10 22 24 21 20 12 32 31 91 20 11 32 44 51 10 22 82 71 61 + 54|; my @arr3 = qw|14 25 64 71 80|; my @arrays = (\@arr1, \@arr2, \@arr3); foreach my $arrref (@arrays) { for (0..@$arrref - 1) { my $indx = (int(rand()*100)%@$arrref); my $swap = $arrref->[$indx]; $arrref->[$indx] = $arrref->[$_]; $arrref->[$_] = $swap; } } print q|ARR->1 | . join qq| |,@arr1; print qq|\nARR->2 | . join qq| |,@arr2; print qq|\nARR->3 | . join qq| |,@arr3;
    Which produces different output every time, but here's one:
    ARR->1 31 10 20 20 12 24 32 22 91 21 ARR->2 32 71 10 24 22 10 61 22 44 12 21 20 31 + 91 11 54 51 82 32 20 ARR->3 80 25 64 71 14
    I think you need to use perldoc -f on rand, push. Also, look up the use of the modulus operator %.

    Celebrate Intellectual Diversity

Re: Randomize elements among several arrays, while maintaining original array sizes.
by GrandFather (Sage) on Aug 01, 2008 at 23:54 UTC

    You have an elegant solution to your basic problem, but there is a "concept" issue that it may be helpful to sort out.

    Wherever one has a bunch of things all the same one tends to use an array. That applies on many levels so, if you have a bunch of arrays all the same (as implied in this case) the first (and often correct) thought is 'use an array of arrays'. However that makes identification of the arrays difficult where there is some semantic meaning associated with each nested array. In that case, rather than using an array of arrays consider using a hash of arrays. The hash key becomes the "name" of each array so they are identified in a nice fashion where they need to be treated individually, but is is easy to deal with the whole collection in a uniform fashion.

    For further reading see perldsc, perldata, perlref and perllol.


    Perl reduces RSI - it saves typing
Re: Randomize elements among several arrays, while maintaining original array sizes.
by EvanCarroll (Chaplain) on Aug 01, 2008 at 16:24 UTC
    Learn some basic perl man!!
    @b = ( @a, 11, 32 ); my ( @newa, @newb, @newc ); my @sizes = ( scalar(@a), scalar(@b), scalar(@c) ); my @where_to = qw/a b c/;
    I'm still not sure what you're trying to do.. but you might want to checkout perldoc -q shuffle
    use List::Util 'shuffle'; @shuffled = shuffle(@list);
    UPDATE I believe the op just now clarified what about something like this, using parallel assignment:
    use XXX; use List::Util; @a=qw/foo bar baz/; @b=qw/mickey/; @c=qw/x y z/; (@a[0..$#a],@b[0..$#b],@c[0..$#c]) = List::Util::shuffle(@a,@b,@c); XXX [\@a, \@b, \@c]'


    Evan Carroll
    I hack for the ladies.
    www.EvanCarroll.com
      Presumably, his example a, b, and c arrays are datasets from some outside source, so your a+b suggestion likely doesn't add anything. Additionally, I don't even know what you meant to do with @where_to, as you're just assigning strings 'a', 'b' and 'c'.

      The simplest way to do what the OP wanted would be as pjotrik suggested. Your recommendation of List::Util's shuffle is definitely a good suggestion though.

      Addendum: A quick and dirty example...

      #!/usr/bin/perl -w use strict; use List::Util 'shuffle'; my @array_a = (10, 22, 34, 21, 20, 12, 32, 31, 91, 20); my @array_b = (10, 22, 34, 21, 20, 12, 32, 31, 91, 20, 11, 32, 44, 51, + 10, 22, 82, 71, 61, 54); my @array_c = (14, 25, 64, 71, 80); mass_shuffle(\@array_a, \@array_b, \@array_c); sub mass_shuffle { # all sub-arrays passed by ref my @arrays = @_; # combine into one large array and shuffle it my @shuffler; foreach my $sub (@arrays) { push @shuffler, @{$sub}; } @shuffler = shuffle(@shuffler); # splice properly sized randomized replacements foreach my $sub (@arrays) { @{$sub} = splice @shuffler, 0, scalar(@{$sub}); } }
        The @where_to, is a list of places where the items are destined to go. I want to not simply shuffle the arrays. That is easy as has been pointed out, and I am aware of how to do this (having learned "some basic perl").

        I want to move the items around between the arrays, while maintaining the array sizes. So items in A can move to A, B, or C.

        It is not so much shuffling the order of each array as it is shuffling where each elements goes and creating new arrays of size A B and C that now have elements drawn from A B or C. In essence, I am trying to set up a permutation test, but having great difficulty with this.

        ---- Even a blind squirrel finds a nut sometimes.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://701729]
Approved by toolic
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (8)
As of 2019-10-21 22:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?