【问题标题】:Sorting hashes on value length whilst preserving order在保持顺序的同时按值长度对哈希进行排序
【发布时间】:2019-02-21 07:31:32
【问题描述】:

我目前正在编写一个 Perl 脚本来对标准输入中的行进行排序,并按行长的顺序打印这些行,同时保留相等的行的顺序。 我的排序代码包含以下内容:

while (my $curr_line = <STDIN>) {
    chomp($curr_line);
    $lines{$curr_line} = length $curr_line;
}

for my $line (sort{ $lines{$a} <=> $lines{$b} } keys %lines){
    print $line, "\n";
}

例如,我的标准输入由以下内容组成:

tiny line
medium line
big line
huge line
rand line
megahugegigantic line

我会得到以下输出:

big line
rand line
tiny line
huge line
medium line
megahugegigantic line

有什么方法可以保留等长行的顺序,以使小行先于大行,而大行先于 rand?此外,每次我运行脚本时,顺序似乎都会发生变化。

提前致谢

【问题讨论】:

    标签: perl sorting hash key


    【解决方案1】:

    一种可能的解决方案

    您可以在输入文件句柄以及length 中保存该行的位置。 $. magic variable (input line number) 提供了这一点。然后,您可以对这两个值进行排序。

    use strict;
    use warnings;
    
    my %lines;
    while ( my $curr_line = <DATA> ) {
      chomp($curr_line);
      $lines{$curr_line} = [ length $curr_line, $. ];
    }
    
    for my $line (
      sort {
           $lines{$a}->[0] <=> $lines{$b}->[0]
        || $lines{$a}->[1] <=> $lines{$b}->[1]
      } keys %lines
    ) {
      print $line, "\n";
    }
    
    __DATA__
    tiny lin1
    medium line
    big line
    huge lin2
    rand lin3
    megahugegigantic line
    

    这将始终输出

    big line
    tiny lin1
    huge lin2
    rand lin3
    medium line
    megahugegigantic line
    

    您当然也可以使用哈希来使代码更具可读性。

    $lines{$curr_line} = { 
      length   => length $curr_line, 
      position => $., 
    };
    

    您的实施说明

    由于随机哈希排序,您的结果每次都会更改顺序。 keys 返回键列表的方式是随机的,因为 Perl 实现散列的方式。这是设计使然,并且是一项安全功能。由于存在多个具有相同值的键,因此排序有时会返回不同的结果,具体取决于哪个相等值的键先出来。

    您可以通过在您的 keys 呼叫前面添加另一个 sort 来缓解这种情况。这将按名称对键进行排序,至少使不需要的结果的顺序保持一致。

    #                                               vvvv
    for my $line (sort{ $lines{$a} <=> $lines{$b} } sort keys %lines) { ... }
    

    请注意,如果您在 print 时将 \n 放回原处,则不必输入 chomp。无论如何,它总是具有相同的长度。如果你这样做了,你应该print 一个$/,这是input record separatorchomp 删除,或者你伪造你的数据。

    【讨论】:

      【解决方案2】:

      您的问题不在于排序 Perl 使用快速排序算法,它是一种稳定的排序,匹配相同排序键的输入在排序的输出上具有与输入相同的顺序。

      您的问题是您将行存储在哈希中。散列是键值对的无序集合,因此将行添加到散列中,然后在不进行排序的情况下再次打印出来,将为您提供随机顺序的行。

      您需要将所有行读入一个数组,然后按长度对其进行排序,最快的方法是使用Schwartzian Transformation,见下文。

      my  @lines = <STDIN>;
      chomp(@lines);
      
      my @sorted = # This is the clever bit and needs to be red from the last map up
          map { $_->[0] } # Get the lines
          sort { $a->[1] <=> $b->[1] }  # Sort on length
          map { [$_, length $_] } # Create a list of array refs containing
              # the line and the length of the line
          @lines;
      
      print join "\n", @sorted; # print out the sorted lines
      

      【讨论】:

      • print join "\n", LIST 是个坏主意,因为它会使输出的最后一行未终止。而且它不比正确的print "$_\n" for LIST短。
      • my @lines = &lt;STDIN&gt; 将要求操作员使用 Ctrl-D (Linux) 或 Ctrl-Z (Windows) 来终止输入。这是一个丑陋的用户界面,你应该允许一个空白输入行结束列表。 OP 有 while ( my $curr_line = &lt;STDIN&gt; ) 但这也不会终止,因为输入将始终包含换行符。最好的方法是在循环中last unless $curr_line =~ /\S/
      • 请不要误导人们进行不必要的优化,徒劳地炫耀。在您编写简单的工作代码并发现它太慢之前,您永远不应该添加优化。无论如何,Schwartzian Transform 可能比使用 sort { length $a &lt;=&gt; length $b } @lines 慢,因为 length 非常快。
      【解决方案3】:

      您没有将原始订单存储在任何地方,因此您不可能对其进行排序。最简单的解决方法是将行存储在一个数组中,并确保 Perl 使用的是稳定的排序。

      use sort 'stable';
      
      my @lines = <>;
      chomp(@lines);
      
      for my $line ( sort { length($a) <=> length($b) } @lines) {
         say $line;
      }
      

      [ ST 对此太过分了。这太过分了,甚至可能会减慢速度! ]

      【讨论】:

        【解决方案4】:

        如前所述,随机性来自于您使用散列键来存储字符串。没有必要这样做,或者像 Schwartzian Transform 这样更复杂的东西来完成这项工作

        自 v5.8 以来的所有 Perl 版本都使用 stable 排序,这将保持排序相同的值以相同的顺序。但是你可以坚持认为你得到的sort 操作符是一个稳定的使用sort pragma 的操作符

        use sort 'stable'
        

        这就是我将如何编写您的程序。它会在文件末尾停止读取输入,或者当您想从键盘输入数据时看到空白行

        use strict;
        use warnings 'all';
        use feature 'say';
        use sort 'stable';
        
        my @list;
        
        while ( <> ) {
            last unless /\S/;
            chomp;
            push @list, $_;
        }
        
        say for sort { length $a <=> length $b } @list;
        

        使用与您在问题中使用的相同的输入,这会产生

        输出

        big line
        tiny line
        huge line
        rand line
        medium line
        megahugegigantic line
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-09
          • 2015-06-06
          • 2011-04-26
          • 2011-05-04
          • 1970-01-01
          • 1970-01-01
          • 2022-11-18
          • 2013-06-06
          相关资源
          最近更新 更多