【问题标题】:How can I sort an array or table by column in Perl?如何在 Perl 中按列对数组或表进行排序?
【发布时间】:2015-01-22 14:46:19
【问题描述】:

我一直在到处寻找这个问题的答案,但我无法让它发挥作用。

我有一个使用 Perl 读入数组的输入文件。该文件是一个包含表格的文本文件。 Perl 将它作为一个数组读入,每个元素都是一整行(包括所有五列)。这是数组的样子:

0__len__340      16    324       0    0.0470588235294118
1__len__251      2     249       0    0.00796812749003984
2__len__497      0     497       0    0
3__len__55       7     48        0    0.127272727272727
4__len__171      0     171       0    0
5__len__75       0     75        0    0
6__len__160      75    85        0    0.46875
7__len__285      1     284       0    0.00350877192982456
8__len__94       44    50        0    0.468085106382979

我需要按最后一列的降序对该表进行排序。所以我的输出应该是:

6__len__160     75    85       0    0.46875
8__len__94      44    50       0    0.468085106382979
3__len__55      7     48       0    0.127272727272727
0__len__340     16    324      0    0.0470588235294118
1__len__251     2     249      0    0.00796812749003984
7__len__285     1     284      0    0.00350877192982456
2__len__497     0     497      0    0
4__len__171     0     171      0    0
5__len__75      0     75       0    0

我尝试了几种方法,但都没有奏效。这是我尝试过的代码:

@input = <FILENAME>;

#Close the file
close FILENAME;

my @fractions;
my $y = 0;
for (my $x = 1; $x <= $#input; ++$x) {
    $fractions[$y] = (split (/\s/, $input[$x]))[4];
    ++$y;
}
my @sorted = sort {$b <=> $a} @fractions;
my $e = 1;
my $z = 0;
my $f = 0;
my @final;

do {
    do {
        if ((split (/\s/, $input[$e]))[4] == $sorted[$z]){
            $final[$f] = $input[$e];
            ++$e;
            ++$f;
        } else {
            ++$e;
        }
    } until ($e > $#input);

    do {
        ++$z;
    } until ($sorted[$z] != $sorted[$z - 1]);

    $e = 0;
} until ($z > $#sorted);

for (my $h = 0; $h <= $#final; ++$h) {
    print $final[$h] . "\n\n";
}

有了这个,我基本上是尝试将第5列的数字放入自己的数组中,排序,然后回过原数组,拉出与排序后的数组匹配的元素,放入最终的数组中.

如果我继续努力,这可能会奏效,但运行时间太长以至于不切实际。我用来测试我的代码的这个小表需要很长时间才能运行,一旦代码运行,它将处理一个包含数百万行的表。

我也尝试将排序命令应用于表本身,但我的输出与我的输入完全相同...它没有得到排序。

@input = <FILENAME>;
close FILENAME;
my @sorted = sort { $b->[4] <=> $a->[4] } @input;
for (my $h = 0; $h <= $#sorted; ++$h) {
    print $sorted[$h] . "\n\n";
}
exit;

最后,我尝试将数组放入哈希表中,其中键是前四列,因为第一列名称是唯一的,而值是第五列。

然后我希望我可以按值对哈希进行排序,并且键将保留其分配的值。我也无法让它工作,但不幸的是几天前我删除了代码。

一个问题是我无法弄清楚如何只在第五列之前拆分字符串,所以我最终得到了两个字符串,一个包含前四列,一个包含第五列。

我对排序命令做错了什么?有没有更好的方法来做到这一点?

【问题讨论】:

  • “便携式”紧急情况时不在 Unix 上排序 :-) perl -E 'say sort { (split(/\s+/,$b))[4] &lt;=&gt; (split(/\s+/,$a))[4] } &lt;&gt;'

标签: arrays string perl sorting data-structures


【解决方案1】:

在您的上一个代码示例中,您可以替换

my @sorted = sort { $b->[4] <=> $a->[4] } @input;

my @sorted = sort { (split(' ', $b))[4] <=> (split(' ', $a))[4] } @input;

甚至

my @sorted = sort { (split(/\s+/, $b))[4] <=> (split(/\s+/, $a))[4] } @input;

如果输入数据没有带前导空格的行。

【讨论】:

  • 请注意,这里最重要的是在' ' 上拆分(这是/\s+/ 上拆分的特殊简写,但也忽略了前导空格)而不是/\s/(在单个空格上拆分,导致“第五列”实际上不包含所需的数据,因为大多数列由多个空格分隔)
  • @ysth 是的,我知道这一点,但在上面的示例中看不到在这个意义上会有问题的行吗?
  • 这行得通,非常感谢!我之前曾尝试将 split 函数插入到我的排序语句中,但可能使用 /\s/ 而不是 ' ' 会搞砸,或者我犯了其他错误。再次感谢!
  • @Lisa 正如一些人已经提到的那样,\s 仅在一个空白字符上拆分数据,而在您的情况下,列之间有很多,所以\s+ 将它们全部考虑在内更合适。
  • @mpapec @ysth 见上面的 oneliner 变体 :-) ...似乎总是可以使用较短的版本 map 和/或自动拆分(使用 @F),但是 ...
【解决方案2】:

您可能还喜欢List::UtilsBy 中的nsort_by 函数:

use List::UtilsBy 'rev_nsort_by';

my @sorted = rev_nsort_by { (split(' ', $_))[4] } @input;

【讨论】:

    【解决方案3】:

    以防万一这有助于未来的人们 - 这里有一些不雅的尝试 sort() lines.txt 的内容(来自问题的数据),在其第五列中,使用 Perl 单行。这应该有效:

    perl -E 'say "@$_" for sort {$a->[4] <=> $b->[4]} map {[(split)]} <>' file
    

    这或多或少是一样的,但 split “自动化” 与自动拆分 (-a) 开关创建 @F 数组:

    perl -anE 'push @t,[@F]}{say "@$_" for sort {$a->[4] <=> $b->[4]} @t' file
    

    如果拆分模式不是空白,您可以将其替换为此处显示的默认 (\s+):

    perl -E 'say sort {(split(/\s+/,$a))[4] <=> (split(/\s+/,$b))[4]} <>' file
    

    这是sort 并打印第五列的最短方法:

    perl -E 'say for sort map{ (split)[4] } <>' file
    

    转换排序

    我们可以一次性完成mapsplitsort 吗?这是对第五列进行排序的简单方法:

    perl -E 'say for sort map{ [(split)[4], $_]->[0] } <>' file
    

    剖析最后一个例子:perl 第一个 maps STDINsplit() - 列一个列表;获取此split() 列表的第五个元素( [4])并将该列表项和刚刚读取的整行($_)包装在数组构造函数[]中;然后获取该匿名数组的第一个元素(即每行的第五列)并将其传递给sort()。呸!

    这只是打印第五列,因为我们只将匿名数组的第一个元素 -&gt;[0] 传递给 sort。要以这种方式打印按列排序的整行,我们需要将整个匿名数组传递给 sort 并告诉 sort 使用包含列内容的元素来完成它的工作,然后传递另一个元素匿名数组(包含整行的数组)到print(或say) - 这样我们可以按第五列排序,但打印出整行:

    perl -E 'say $_->[1] for sort{$a->[0] <=> $b->[0]} map{[(split)[4], $_]} <>' file
    

    这就像我们上面的第一个示例一样。如果不是遍历使用for 创建的列表,而是map 第二个元素并将其传递给print,我们得到:

    perl -E 'say map $_->[1], sort{$a->[0] <=> $b->[0]} map{[(split)[4],$_]} <>' file
    

    我们重新发明了Schwartzian transform,这是一个非常棒的 Perl 习语,它是 "built in" to Perl 6 ;-)


    【讨论】:

      猜你喜欢
      • 2010-10-19
      • 1970-01-01
      • 2018-12-05
      • 1970-01-01
      • 1970-01-01
      • 2021-05-27
      • 2015-01-04
      • 2023-04-01
      • 1970-01-01
      相关资源
      最近更新 更多