【问题标题】:Sort comma-delimited file by three columns with custom criteria in Perl在 Perl 中使用自定义条件按三列对逗号分隔的文件进行排序
【发布时间】:2020-05-26 12:44:55
【问题描述】:

我有一个逗号分隔的文本文件。我想先按第 3 列、第 2 列、第 1 列对文件进行排序。

但是,我希望第 3 列按字母顺序排序,最长的值在前。

例如,AAA,然后是 AA,然后是 A,然后是 BBB,然后是 BB,然后是 B,然后是 CCC,然后是 CC,等等。

输入(alpha-sort-test2.txt):

JOHN,1,A
MARY,3,AA
FRED,5,BBB
SAM,7,A
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
JOANNE,2,BB
AMANDA,2,DD
AMY,5,B
PETE,7,CC
MATT,4,B
SARAH,3,CCC
GEORGE,3,CC
AMANDA,3,AAA

我目前的 Perl 代码如下:

$infile = "alpha-sort-test2.txt";
$outfile = "alpha-sort-test-sorted2.txt";

open (INFILE, "<$infile") or die "Could not open file $infile $!";
open (OUTFILE, ">$outfile");

my @array = sort howtosort <INFILE>;

foreach (@array)
{
   chomp;
   print "$_\n";
   print OUTFILE "$_\n"; 
}

sub howtosort 
{
   my @flds_a = split(/,/, $a);
   my @flds_b = split(/,/, $b);

   $flds_a[2] cmp $flds_b[2]; 
}

close INFILE;
close OUTFILE; 

当前输出(alpha-sort-test-sorted2.txt):

JOHN,1,A
SAM,7,A
MARY,3,AA
AMANDA,3,AAA
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
AMY,5,B
MATT,4,B
JOANNE,2,BB
FRED,5,BBB
PETE,7,CC
GEORGE,3,CC
SARAH,3,CCC
AMANDA,2,DD

期望的输出:

BETTY,2,AAA
JOHN,2,AAA
AMANDA,3,AAA
JOHN,3,AAA
JARROD,7,AAA
MARY,3,AA
JOHN,1,A
SAM,7,A
FRED,5,BBB
JOANNE,2,BB
MATT,4,B
AMY,5,B
SARAH,3,CCC
GEORGE,3,CC
PETE,7,CC
AMANDA,2,DD

提前致谢。

【问题讨论】:

    标签: perl sorting


    【解决方案1】:

    第三个字段的标准有点复杂。

    字典比较逐个字符进行,因此abc 小于ax,但更长的字符串更大,其他条件相同。所以ab 小于bab 大于a

    因此,对第三个字段的要求混合了这两个东西,并在中间打破了cmp。如果我们使用cmp,那么abb 之前(正确)但aaa 之后(不需要)。我根本不知道如何使用cmp 来满足该要求。

    所以这是一个非常基本的实现,对于这些标准

    use warnings;
    use strict;
    use feature 'say';
    use Path::Tiny qw(path);  # convenience
    
    my $file = shift // die "Usage: $0 file\n";
    my @lines = path($file)->lines({ chomp => 1 });
    
    my @sorted =
        map { $_->[0] }
        sort { custom_sort($a, $b) }
        map { [$_, split /,/]  }
        @lines;
    
    say for @sorted;
    
    
    sub custom_sort {
        my ($aa, $bb) = @_;
    
        # Last field for both terms, their lengths
        my ($af, $bf) = map { $_->[-1] } $aa, $bb;
        my ($len_a, $len_b) = map { length } $af, $bf;
    
        # Strip and return first characters and compare them lexicographically
        # Then compare lengths of original strings if needed
        # Keep going until difference is found or one string is depleted
        while (
            (my $ca = substr $af, 0, 1, "")  and
            (my $cb = substr $bf, 0, 1, "")    )
        {
            if ($ca gt $cb) {
                return 1
            }
            elsif ($ca lt $cb) {
                return -1;
            }
            elsif ($len_a < $len_b) {
                return 1
            }
            elsif ($len_a > $len_b) {
                return -1
            }
        }
    
        # Still here, so third field was the same; use other two criteria
        return
            $aa->[2] <=> $bb->[2]
                ||
            $aa->[1] cmp $bb->[1];
    }
    

    这将打印出所需的列表。

    一些cmets

      1234563这是Schwartzian transform
    • 第三个字段的标准:按字母顺序逐个字符比较,直到发现差异;如果一个字符串包含在另一个字符串中,则较长的字符串获胜。所以abcab的逐字符比较在babc'wins'处停止

    • substr 中的(可选)第四个参数是返回的子字符串的替换,根据第二个和第三个参数找到。所以这里一个空字符串替换了从0 开始的一个长子字符串——它删除并返回第一个字符。这很像在数组上使用shift

    • 如果第三个字段完全相同,则按数字比较第二个字段,如果相同,则按字母顺序比较第一个字段

    • 比较后,我们从排序后的arrayrefs中检索原始字符串

    【讨论】:

    • 我敢说,这很漂亮。 ;-) 非常感谢@zdim 非常感谢您的深入解释。非常感谢。
    • @BillyJ 非常感谢您的客气话,如果有帮助我会很高兴 :) 我添加了另一条评论。如果出现问题,请告诉我
    猜你喜欢
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多