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

I have the following code to try and split a hash into say 2 other hashes, trying to maintain an equal distribution, but it skips one key. What is the best way to split a hash by key into N multiple hashes?
#!/usr/bin/perl use strict; use warnings; $|++; use Data::Dumper; my $numOfHashes = 2; my $numOfHashesO = 2; my $worker; my $hashref = { '6712' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6712' }, '6582' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6582' }, '6282' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6282' }, '6462' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6462' }, '16712' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6712' }, '26582' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6582' }, '36282' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6282' }, '46462' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6462' }, '96182' => { 'IP_ADDRESS' => '9.0.0.0', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6182' } }; my $keys = keys %{$hashref}; print "Original Hash Keys: $keys\n"; foreach my $key ( keys %{$hashref} ) { if ( $numOfHashes == 0 ) { $numOfHashes = $numOfHashesO; $worker->{$numOfHashes}->{WORK}->{$key} = $hashref->{$ +key}; } $worker->{$numOfHashes}->{WORK}->{$key} = $hashref->{$ +key}; $numOfHashes--; } foreach my $destKey ( keys %{$worker} ) { my $destKeyCount = keys %{$worker->{$numOfHashes}->{WORK}} +; print "$destKey: $destKeyCount\n" }

Replies are listed 'Best First'.
Re: How can I split a hash into other N hashes
by kennethk (Abbot) on May 13, 2014 at 15:45 UTC
    Your copying code is fine; you messed up your diagnostic code. Specifically
    my $destKeyCount = keys %{$worker->{$numOfHashes}->{WORK}};
    should be
    my $destKeyCount = keys %{$worker->{$destKey}->{WORK}};
    You're outputting case 1 twice. I see you included Data::Dumper; might have been helpful if you'd cross-checked with its output.

    On a side note, line 78 is redundant with line 81 (but not vice versa).


    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

      Humm, I didn't see that error.. I might need to use Data::Dumper more.. that code was written at 6PM after a full days of perl debugging and writing. Thanks!
Re: How can I split a hash into other N hashes
by GotToBTru (Prior) on May 13, 2014 at 16:12 UTC

    If you want N of something, using an array to store them suggests itself to me.... but not to you! Updated to be more in line with OP.

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; $|++; my $numOfHashes = 5; my $hashref = { '6712' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6712' }, '6582' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6582' }, '6282' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6282' }, '6462' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6462' }, '16712' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6712' }, '26582' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6582' }, '36282' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6282' }, '46462' => { 'IP_ADDRESS' => '1.2.3.44', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6462' }, '96182' => { 'IP_ADDRESS' => '9.0.0.0', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000000000000 +000000000000', 'CSX_ID' => '6182' } }; my $keys = keys %{$hashref}; my $worker; print "Original Hash Keys: $keys\n"; my $i=0; foreach my $key ( keys %{$hashref} ) { $worker->{$i++}{$key} = $hashref->{$key}; $i %= $numOfHashes; } print Dumper($worker);

    Output:

    Original Hash Keys: 9 $VAR1 = { '4' => { '36282' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6282', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '00000000000000000 +0000000000000000000000' } }, '1' => { '6582' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6582', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000 +000000000000000000000' }, '46462' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6462', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '00000000000000000 +0000000000000000000000' } }, '3' => { '6282' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6282', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000 +000000000000000000000' }, '96182' => { 'IP_ADDRESS' => '9.0.0.0', 'CSX_ID' => '6182', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '00000000000000000 +0000000000000000000000' } }, '0' => { '16712' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6712', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '00000000000000000 +0000000000000000000000' }, '6462' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6462', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000 +000000000000000000000' } }, '2' => { '6712' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6712', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '000000000000000000 +000000000000000000000' }, '26582' => { 'IP_ADDRESS' => '1.2.3.44', 'CSX_ID' => '6582', 'IPV6' => '0', 'IP_ADDRESS_BIN' => '00000000000000000 +0000000000000000000000' } } };
    1 Peter 4:10
      Thanks for the array suggestion, it seems cleaner than using if and else. Thanks for your help!

        Array version, reprise:

        my $keys = keys %{$hashref}; my @AoH; print "Original Hash Keys: $keys\n"; my $i=0; foreach my $key ( keys %{$hashref} ) { $AoH[$i++]{$key} = $hashref->{$key}; $i %= $numOfHashes; } print Dumper(@AoH);
        1 Peter 4:10
Re: How can I split a hash into other N hashes
by oiskuu (Hermit) on May 13, 2014 at 17:47 UTC

    Do you actually require splitting the hash? Partitioning the keys might suffice in many situations.

    In any case, using part is one option.

    use List::MoreUtils qw(part); use constant NWAYS => 3; my $i; #my @AoA = part { $i++ % NWAYS } keys %hash; my @AoH = map +{map {$_ => $hash{$_}} @$_}, part { $i++ % NWAYS } keys + %hash;
    Update. Much cleaner with key/value slices[*] (untested):
    use 5.020; my @AoH = map +{ %hash{@$_} }, part { $i++ % NWAYS } keys %hash;

Re: How can I split a hash into other N hashes
by Monk::Thomas (Friar) on May 13, 2014 at 17:06 UTC

    Since a hash can be treated as an even sized list/array, you can count the number of elements in the hash and splice the 'array' and generate new hashes.

    This requires you do not care for any sorting and simply want to split the array!

    (Sample implementation for hashes with an even number of keys, modifying to cope with uneven number of keys is left as an exercise to the reader.)

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %hash = ( a => 1, b => 2, c => 3, d => 4, ); my $keycount = (keys %hash); # hash has 4 keys (and 8 elements) # array index: 0 1 2 3 4 5 6 7 # hash value: a, 1, b, 2, c, 3, d, 4 my %hash1 = splice [ %hash ], 0, $keycount; my %hash2 = splice [ %hash ], $keycount, $keycount; print Dumper \%hash1, \%hash2; exit;
    Sample output:
    $VAR1 = {
              'b' => 2,
              'd' => 4
            };
    $VAR2 = {
              'c' => 3,
              'a' => 1
            };
    
Re: How can I split a hash into other N hashes
by kcott (Archbishop) on May 14, 2014 at 07:54 UTC

    G'day ataX,

    Here's another technique. Note how I've simplified data such that the solution predominates.

    Script (pm_example.pl):

    #!/usr/bin/env perl use strict; use warnings; my %data = qw{a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9}; my $N = $ARGV[0]; my $i = 0; my %work; $work{(($i += 1) % $N) + 1}{$_} = $data{$_} for keys %data; use Data::Dump; dd \%work;

    Some sample runs:

    $ pm_example.pl 2 { 1 => { a => 1, b => 2, f => 6, i => 9 }, 2 => { c => 3, d => 4, e => 5, g => 7, h => 8 }, }
    $ pm_example.pl 3 { 1 => { c => 3, d => 4, e => 5 }, 2 => { b => 2, h => 8, i => 9 }, 3 => { a => 1, f => 6, g => 7 }, }
    $ pm_example.pl 4 { 1 => { d => 4, g => 7 }, 2 => { a => 1, e => 5, i => 9 }, 3 => { c => 3, h => 8 }, 4 => { b => 2, f => 6 }, }

    -- Ken

Re: How can I split a hash into other N hashes
by locked_user sundialsvc4 (Abbot) on May 14, 2014 at 01:35 UTC

    Hey, “downvotes” mean nothing to me, so I guess it means I can get away with asking the perfectly-obvious dumb-question:   “Why in the heck would you want to?”

    And, actually, that is somewhat of a serious question.   I’ve never heard of trying to split a hash, let alone “to maintain an equal distribution” between several of them.   Under what circumstances would you seriously want to do this, and why?