gib
gib

Reputation: 788

perl: shuffle value-sorted hash?

At first sorry for my english - i hope you will understand me.

There is a hash:

$hash{a} = 1;
$hash{b} = 3;
$hash{c} = 3;
$hash{d} = 2;
$hash{e} = 1;
$hash{f} = 1;

I want to sort it by values (not keys) so I have:

for my $key ( sort { $hash{ $a } <=> $hash{ $b } } keys %hash  ) { ... }

And at first I get all the keys with value 1, then with value 2, etc... Great.

But if hash is not changing, the order of keys (in this sort-by-value) is always the same.

Question: How can I shuffle sort-results, so every time I run 'for' loop, I get different order of keys with value 1, value 2, etc. ?

Upvotes: 2

Views: 1017

Answers (5)

vol7ron
vol7ron

Reputation: 42109

It seems like you want to randomize looping through the keys.

Perl, does not store in sequential or sorted order, but this doesn't seem to be random enough for you, so you may want to create an array of keys and loop through that instead.

First, populate an array with keys, then use a random number algorithm (1..$#length_of_array) to push the key at that position in the array, to the array_of_keys.


If you're trying to randomize the keys of the sorted-by-value hash, that's a little different.

See Codepad

my %hash = (a=>1, b=>3, c=>3, d=>2, e=>1, f=>1);
my %hash_by_val;

for my $key ( sort { $hash{$a} <=> $hash{$b} } keys %hash ) { 
   push @{ $hash_by_val{$hash{$key}} }, $key;
}


for my $key (sort keys %hash_by_val){
   my @arr        = @{$hash_by_val{$key}};
   my $arr_ubound = $#arr;

   for (0..$arr_ubound){
      my $randnum = int(rand($arr_ubound));
      my $val     = splice(@arr,$randnum,1);
      $arr_ubound--;
      print "$key : $val\n";                    # notice: output varies b/t runs
   }
}

Upvotes: 1

TLP
TLP

Reputation: 67908

You can simply add another level of sorting, which will be used when the regular sorting method cannot distinguish between two values. E.g.:

sort { METHOD_1 || METHOD_2 || ... METHOD_N } LIST

For example:

sub regular_sort {
    my $hash = shift;
    for (sort { $hash->{$a} <=> $hash->{$b} } keys %$hash) {
        print "$_ ";
    };
}
sub random_sort {
    my $hash = shift;
    my %rand = map { $_ => rand } keys %hash;
    for (sort { $hash->{$a} <=> $hash->{$b} ||
        $rand{$a} <=> $rand{$b} } keys %$hash ) {
        print "$_ ";
    };
}

Upvotes: 3

ikegami
ikegami

Reputation: 385857

To sort the keys by value, with random ordering of keys with identical values, I see two solutions:

use List::Util qw( shuffle );
use sort 'stable';
my @keys =
   sort { $hash{$a} <=> $hash{$b} }
   shuffle keys %hash;

or

my @keys =
   map $_->[0],
   sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] }
   map [ $_, $hash{$_}, rand ],
   keys %hash;

The use sort 'stable'; is required to prevent sort from corrupting the randomness of the list returned by shuffle.


The above's use of the Schwartzian Transform is not an attempt at optimisation. I've seen people use rand in the compare function itself to try to achieve the above result, but doing so is buggy for two reasons.

When using "misbehaving" comparisons such as that, the results are documented as being undefined, so sort is allowed to return garbage, repeated elements, missing elements, etc.

Even if sort doesn't return garbage, it won't be a fair sort. The result will be weighed.

Upvotes: 2

Toto
Toto

Reputation: 91430

Not quite sure I well understand your needs, but is this ok:

use List::Util qw(shuffle);

my %hash;
$hash{a} = 1;
$hash{b} = 3;
$hash{c} = 3;
$hash{d} = 2;
$hash{e} = 1;
$hash{f} = 1;

for my $key (sort { $hash{ $a } <=> $hash{ $b } } shuffle( keys %hash  )) {
    say "hash{$key} = $hash{$key}"
}

Upvotes: 4

Rahul
Rahul

Reputation: 77876

You can have two functions for ascending and decending order and use them accordingly like

sub hasAscending {
   $hash{$a} <=> $hash{$b};
}

sub hashDescending {
   $hash{$b} <=> $hash{$a};
}

foreach $key (sort hashAscending (keys(%hash))) {
   print "\t$hash{$key} \t\t $key\n";
}

foreach $key (sort hashDescending (keys(%hash))) {
   print "\t$hash{$key} \t\t $key\n";
}

Upvotes: 1

Related Questions