【问题标题】:perl: shuffle value-sorted hash?perl:洗牌值排序的哈希?
【发布时间】:2012-01-08 21:51:44
【问题描述】:

首先对不起我的英语 - 我希望你能理解我。

有一个哈希:

$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 } } keys %hash  ) { ... }

首先我得到所有值为 1 的键,然后是值为 2,等等......太好了。

但是如果哈希没有改变,键的顺序(在这个按值排序中)总是相同的。

问题:我怎样才能打乱排序结果,所以每次我运行“for”循环时,我都会得到值 1、值 2 等不同顺序的键?

【问题讨论】:

  • 你想对它进行排序,但是对于具有相同值的键有一个随机顺序,对吧?
  • 我在 for 循环中“排序”之前只尝试了洗牌(来自 List::Util),但这显然没有用,因为它洗牌了整个哈希。乔纳森:是的,那是正确的。
  • 等等,你是想随机化返回键的顺序(排序后,仍然保持按值排序),还是按值排序之前的键顺序?
  • 我想随机化返回键的顺序。
  • @gibson:见下文,这不是最优雅或最完美的,但这是逻辑

标签: perl sorting hash


【解决方案1】:

不太确定我是否完全理解您的需求,但这可以吗:

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}"
}

【讨论】:

  • 这和具有多级排序(通过 TLP)的示例有效:)。谢谢大家!
  • @M42,如果你想要一个公平的排序,你需要使用use sort 'stable';。没有它,sort 可能会破坏shuffle 的结果。
  • @ikegami 你在开玩笑吧。 shuffle 完成并将列表返回到 sort,然后再使用它的任何值。
  • @TLP,嗯???我不明白你想说什么,但我不开玩笑。 perl -E'use sort "_quicksort"; say for ( sort { 0 } "aa".."zz" )[0..5];' sm bg mw yy mg ri
【解决方案2】:

您可以简单地添加另一个级别的排序,当常规排序方法无法区分两个值时将使用该级别。例如:

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

例如:

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 "$_ ";
    };
}

【讨论】:

  • 您的代码有错误有两个原因。 1)当使用诸如此类的“行为不端”比较时,记录为undefined的结果,因此sort允许返回垃圾、重复元素、缺失元素等。2)即使@987654325 @ 不返回垃圾,它不会是一个公平的排序。结果将被权衡。我发布了修复作为答案。
  • @ikegami 我在documentation 中没有看到对数字比较或使用导致未定义结果的函数的引用。请就这些陈述提供一些文档或解释。
  • 它在底部。 “比较函数必须正常运行。如果它返回不一致的结果(例如,有时说$x[1] 小于$x[2],有时说相反),则结果定义不明确。”
  • 至于(2)的解释,对不起,我没有细节,但我看到理论和实际实验表明sort { rand &lt;=&gt; rand }不会导致随机排序的列表。
  • @ikegami 好的,我现在明白了.. 如果 a c 会引起混淆。取点。值必须是静态的。不过,结果是如何衡量的?
【解决方案3】:

要按值对键进行排序,对具有相同值的键进行随机排序,我看到了两种解决方案:

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

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

需要use sort 'stable'; 以防止sort 破坏shuffle 返回的列表的随机性。


上面对Schwartzian Transform 的使用不是优化的尝试。我见过有人在比较函数本身中使用rand 来尝试达到上述结果,但这样做有两个问题。

当使用诸如此类的“行为不端”比较时,结果被记录为未定义,因此允许sort 返回垃圾、重复元素、缺失元素等。

即使sort 不返回垃圾,它也不会是一个公平的排序。结果将被权衡。

【讨论】:

    【解决方案4】:

    你可以有两个函数用于升序和降序,并相应地使用它们

    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";
    }
    

    【讨论】:

      【解决方案5】:

      您似乎想通过键随机循环。

      Perl 不按顺序或排序顺序存储,但这对您来说似乎不够随机,因此您可能希望创建一个键数组并循环遍历它。

      首先,用键填充一个数组,然后使用随机数算法 (1..$#length_of_array) 将数组中该位置的键推送到 array_of_keys。


      如果您尝试随机化按值排序的哈希的键,那就有点不同了。

      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
         }
      }
      

      【讨论】:

      • 关于您的第一段:这不是设计使然。这是哈希的副作用。虽然顺序未知,但也不是随机的。甚至可以预见。例如,运行几次perl -E'%h = map { $_ =&gt; 1 } qw( a b c d ); say keys %h;'
      • @ikegami:你的话是金,所以我不怀疑你,但这不是为了减少搜索时间而设计的吗?
      • 如果这样做是为了减少哈希表的查找时间,这意味着可以以增加哈希表的查找时间(无论是什么)为代价来拥有一个有序的哈希表。但是不可能有一个有序的哈希表,所以没有减少哈希表的查找时间。同样,这只是使用hash table 的副作用。
      • 也许您说的是 5.8.1 中的更改。从那时起,如果太多元素最终位于同一个桶中,则可以将哈希的元素重新分配给新的桶(通过扰乱哈希算法)。这样做是为了防止从该特定哈希中获取和插入变得缓慢。 keys 返回的订单几乎总是作为副作用受到影响。
      • @ikegami:你不能有一个有序的哈希,使用 Tie::Hash 吗?无论如何,不​​,我认为哈希的公式类似于关系数据库查找表,其中顺序不是有限的。看来我错了,改了答案。
      猜你喜欢
      • 2010-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-21
      • 2018-12-17
      • 1970-01-01
      • 1970-01-01
      • 2012-07-28
      相关资源
      最近更新 更多