【问题标题】:Perl: Sort part of arrayPerl:对数组的一部分进行排序
【发布时间】:2015-06-04 14:52:19
【问题描述】:

我有一个数组,每行中有许多字段,间隔不同,例如:

INDDUMMY   drawing2   139       30        1        0        0        0        0        0
RMDUMMY    drawing2   69        2         1        0        0        0        0        0  
PIMP       drawing    7         0         1444     718      437      0        0        0

我正在尝试按 3rd 字段中的数字对该数组进行排序,因此所需的输出应该是:

PIMP       drawing    7         0         1444     718      437      0        0        0
RMDUMMY    drawing2   69        2         1        0        0        0        0        0
INDDUMMY   drawing2   139       30        1        0        0        0        0        0

我尝试在排序函数中使用正则表达式进行拆分,例如:

@sortedListOfLayers = sort {
    split(m/\w+\s+(\d+)\s/gm,$a)
    cmp
    split(m/\w+\s+(\d+)\s/gm,$b)
}@listOfLayers;

但它不能正常工作。我怎样才能进行这种类型的排序?

【问题讨论】:

    标签: arrays regex perl sorting


    【解决方案1】:

    您需要进一步扩展排序功能。我也不确定split 是否按照您认为的方式工作。拆分将文本转换为基于分隔符的数组。

    我认为您的问题是您的正则表达式 - 感谢gm 标志 - 与您认为它匹配的内容不匹配。不过,我可能会略有不同:

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my @array = <DATA>;
    
    sub sort_third_num {
       my $a1 = (split ( ' ', $a ) )[2];
       my $b1 = (split ( ' ', $b )) [2];
       return $a1 <=> $b1;
    }
    
    print sort sort_third_num @array;
    
    __DATA__
    NDDUMMY   drawing2   139       30        1        0        0        0        0        0
    RMDUMMY    drawing2   69        2         1        0        0        0        0        0  
    PIMP       drawing    7         0         1444     718      437      0        0        0
    

    例如,这可以解决问题。

    如果您打算使用正则表达式方法:

    sub sort_third_num {
        my ($a1) = $a =~ m/\s(\d+)/;
        my ($b1) = $b =~ m/\s(\d+)/;
        return $a1 <=> $b1;
    }
    

    非全局匹配意味着只返回第一个元素。并且只返回第一个匹配的 'whitespace-digits'。我们还进行数字比较,而不是按字符串进行比较。

    【讨论】:

    • 为了获得额外的功劳,请参考 Sobrique 教给您的关于 split 和正则表达式的内容,在网上搜索并自学施瓦茨变换,然后将行拆分 O(n) 次而不是 O(n *ln(n)) 次。
    • 或者你可以省去所有的麻烦,只将代码的分割部分传递给List::UtilsBynsort_by函数
    • 非常感谢您的回答和建议。
    • @eslamsaad:或者,更好的是,保持原样,除非它对你来说运行得太慢
    • @LeoNerd:但是作为模块的作者,您会这么说,不是吗。我对建议没有任何问题,但我更愿意全面披露
    【解决方案2】:

    如果你想对一个列表进行排序,而 sort 块中使用的操作成本很高,那么一个常用的 Perl 习惯用法是 Schwartzian Transform:你对每个列表元素应用一次操作,并将结果与​​原始元素一起存储元素,排序,然后映射回原始格式。

    经典的教科书示例是使用昂贵的-s 文件测试按大小对目录中的文件进行排序。一种天真的方法是

    my @sorted = sort { -s $a <=> -s $b } @unsorted;
    

    每个比较操作必须执行两次-s

    使用 Schwartzian 变换,我们将文件名映射到数组引用列表中,每个引用包含列表元素及其大小(每个文件只需确定一次)的数组,然后按文件大小排序,然后最后将数组引用映射回文件名。这一切都在一个步骤中完成:

    my @sorted =
        map $_->[0],                 # 3. map to file name
        sort { a$->[1] <=> b$->[1] } # 2. sort by size
        map [ $_, -s $_ ],           # 1. evaluate size once for each file
        @unsorted;
    

    在您的情况下,问题是提取每个数组元素的第三个字段的成本是多少。如有疑问,请measure 比较不同的方法。对于几十个文件,文件大小示例中的加速速度非常快,大约是 10 倍!

    应用于您的问题的 Schwartzian 变换如下所示:

    my @sorted =
        map $_->[0],                         # 3. Map to original array
        sort { $a->[1] <=> $b->[1] }         # 2. Sort by third column
        map [ $_, ( split( ' ', $_ ) )[2] ], # 1. Use Sobrique's idea
        @array;
    

    如果使用的操作非常昂贵,您希望避免对每个值执行多次,以防您有相同的数组元素,您可以缓存结果,如this question 中所述;这被称为Orcish Maneuver

    【讨论】:

      猜你喜欢
      • 2011-04-28
      • 1970-01-01
      • 2012-07-04
      • 2020-04-08
      • 1970-01-01
      • 2017-02-24
      • 2022-01-07
      • 1970-01-01
      • 2015-06-25
      相关资源
      最近更新 更多