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

Randomly reassign hash keys

by cormanaz (Chaplain)
on Apr 26, 2017 at 16:47 UTC ( #1188980=perlquestion: print w/replies, xml ) Need Help??
cormanaz has asked for the wisdom of the Perl Monks concerning the following question:

Good day bros. I would like to create a hash, then randomly reassign the keys (to support a randomization test). I know I can get a shuffled list of keys with
use List::Util qw(shuffle); ... foreach my $k (shuffle keys %foo) { ... }
but accessing that would leave the keys associated with the same values. I want to randomly reassign the keys to different values already in the hash. So if the original was
%hash = ( "a" => 1, "b" => 2, "c" => 3, "d" => 4 };
I could end up with
%hash = ( "c" => 1, "d" => 2, "a" => 3, "b" => 4 };

I can imagine how to do that in an inelegant way, but was wondering if there is an elegant way.

Replies are listed 'Best First'.
Re: Randomly reassign hash keys
by poj (Prior) on Apr 26, 2017 at 17:07 UTC
    @hash{ keys %hash } = shuffle values %hash;

    update : you said 'random' so that means it could result in no change.

    poj

      And, if you do that a lot, there's this alternative for your consideration:

      sub inplace_shuffle { @_[keys @_] = shuffle @_ } inplace_shuffle (values %foo);

        The functionality of keys operating on an array to return the indices of the array (see similarily values, each) was added with Perl version 5.12. The following will work with any Perl 5 version:
            sub inplace_shuffle { @_[ 0 .. $#_ ] = shuffle @_ }


        Give a man a fish:  <%-{-{-{-<

        Just a random funny observation having done it myself a few times, taking a fully comprehensive "slice" of a hash like this sounds linguistically silly to me even though it's perfectly valid in the Perl language. Hmm... which is the better analogy? Is it like cutting a personal pizza in to slices and then proceeding to eat them all in one sitting, or more like just folding your one slice (the entire pizza) up like a calzone to eat it? After all, in the latter case you don't even need to get your pizza cutter dirty, that's just efficiency in practice. :-)

        I wonder if there is a word in the English language for when a word that's technically correct is also simultaneously nonsensical, like here with the "slice" in fact being "the entire thing". Oxymoron or misnomer don't seem quite right, because strictly speaking there aren't any contradictory terms involved and "slice" is still the correct name. Are there any logophile Monks with suggestions? (I cross posted on Stack Exchange since it's admittedly a bit off topic here)

        Just another Perl hooker - Working on the corner... corner conditions that is.
      use List::Util qw(shuffle); use List::MoreUtils qw(zip); %hash = zip @{[shuffle keys %hash]}, @{[values %hash]};
Re: Randomly reassign hash keys
by thomas895 (Chaplain) on Apr 26, 2017 at 16:53 UTC

    Perl hashes have no guarantee of key ordering. See keys and Algorithmic Complexity Attacks. You might be able to randomize the order of the keys by changing the internal parameters used by Perl.

    A better solution is to do something like @randomized_keys = List::Util::shuffle(keys %hash) and then accessing the corresponding values with something like @corresponding_values = @hash{@randomized_keys} (untested).

    -Thomas
    "Excuse me for butting in, but I'm interrupt-driven..."
Re: Randomly reassign hash keys
by thanos1983 (Chaplain) on Apr 26, 2017 at 18:02 UTC

    Hello cormanaz,

    Another approach could be:

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %hash = ( "a" => 1, "b" => 2, "c" => 3, "d" => 4 ); # Export keys from the hash my @keys = keys %hash; # Export values from the hash my @values = values %hash; my %new_hash; for ( 0 .. $#keys ) { $new_hash{splice @keys, rand @keys, 1} = splice @values, rand @val +ues, 1; } print Dumper \%new_hash; __DATA__ $ perl test.pl $VAR1 = { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 };

    Update:in case that someone is interested in speed simple foreach element loop is always faster. Sample of code:

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Benchmark::Forking qw( cmpthese ); my %hash = ( "a" => 1, "b" => 2, "c" => 3, "d" => 4 ); # Export keys from the hash my @keys = keys %hash; # Export values from the hash my @values = values %hash; my %new_hash_1; my %new_hash_2; cmpthese(100000000, { 'loop indices' => sub { for ( 0 .. $#keys ) { $new_hash_1{splice @keys, rand @keys, 1} = splice @values, ran +d @values, 1; } }, 'loop elements' => sub { for ( @keys ) { $new_hash_2{splice @keys, rand @keys, 1} = splice @values, ran +d @values, 1; } }, }); __DATA__ $ perl test.pl Rate loop indices loop elements loop indices 5211047/s -- -58% loop elements 12531328/s 140%

    Hope this helps.

    Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: Randomly reassign hash keys
by stevieb (Monsignor) on Apr 26, 2017 at 16:57 UTC

    Is this the sort of thing you're after (uses rand and map). Note that it'll reuse values from the hash. Not every one is guaranteed to be re-input into the new hash:

    use warnings; use strict; use Data::Dumper; my %h = ( a => 1, b => 2, c => 3, d => 4, ); my @vals = values %h; my %new = map {$_ => $vals[int(rand(@vals))]} keys %h; print Dumper \%new;

    Output:

    $VAR1 = { 'c' => 2, 'a' => 4, 'd' => 3, 'b' => 1 };
Re: Randomly reassign hash keys
by kcott (Chancellor) on Apr 27, 2017 at 05:50 UTC

    G'day cormanaz,

    "I can imagine how to do that in an inelegant way, but was wondering if there is an elegant way."

    How about:

    @hash{sort keys %hash} = values %hash;

    That uses the inherent randomness of both keys and values. The documentation for both of those functions includes the same sentence:

    "Hash entries are returned in an apparently random order."

    I ran a quick test from the command line:

    $ perl -MData::Dump -e 'my %x = qw{a 1 b 2 c 3}; dd \%x; @x{sort keys +%x} = values %x; dd \%x'

    As you would expect, all tests output this as the first line:

    { a => 1, b => 2, c => 3 }

    Here's the second line from three runs:

    { a => 1, b => 2, c => 3 } { a => 1, b => 3, c => 2 } { a => 2, b => 3, c => 1 }

    By the way, I also tried just:

    @hash{keys %hash} = values %hash;

    However, that doesn't change anything. The documentation for both keys and values also contains this same sentence:

    "So long as a given hash is unmodified you may rely on keys, values and each to repeatedly return the same order as each other."

    — Ken

Re: Randomly reassign hash keys
by BillKSmith (Priest) on Apr 26, 2017 at 19:56 UTC
    You cannot shuffle keys because keys do not have an order. You can accomplish what you ask by shuffling the values.
    use strict; use warnings; use List::Util qw(shuffle); use Data::Dumper; my %hash = ( "a" => 1, "b" => 2, "c" => 3, "d" => 4 ); @hash{ keys %hash } = shuffle values %hash; print Dumper(\%hash); OUTPUT: $VAR1 = { 'a' => 3, 'c' => 2, 'b' => 4, 'd' => 1 };

    I am curious why you want to do this.

    Bill

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1188980]
Approved by thomas895
Front-paged by kcott
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (10)
As of 2017-05-22 17:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?