【问题标题】:Sorting Data with awk or Perl [closed]使用 awk 或 Perl 对数据进行排序 [关闭]
【发布时间】:2014-02-02 23:49:10
【问题描述】:

我有一个包含如下数据的文本文件:

# Pri: Rest, DTG: 20140109183524.013578591, CID: 1004592976
dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
action: add
Class: c-subSpecData
Option3: applView
Option2: 3

# Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
action: add
Class: c-subSpecData
Option3: applView
Option2: 3
Option1:34
Option5:98

一个数据单元从“#”开始直到下一个“#”。就像上面的示例一样,一个数据单元是:

# Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
action: add
Class: c-subSpecData
Option3: applView
Option2: 3

现在我必须根据 DTG 对这些数据进行排序。 (例如,第二个数据单元中给出的 20140109183524.013580787)。

输出文件将具有相同的数据,但根据 DTG 值(实际上是日期和时间)进行排序。

就数据排序而言,其他数据单元并不重要。

实际的输入文件将包含数千个这样的条目。 实现排序数据的最快方法是什么?实际上,速度对于实现所需结果很重要。

【问题讨论】:

  • 请贴出你试过的代码;有错误还是需要简单优化?
  • 您希望如何进行基于 DTG 的排序?例如20140109183524.013580787:是否应该使用不带点的整数,例如20140109183524013580787,然后对该值进行升序数字排序?

标签: perl unit-testing sorting unix awk


【解决方案1】:

在 Gnu Awk 4.1 版中可以使用PROCINFO["sorted_in"] 进行排序,点赞

gawk -f s.awk file

s.awk 在哪里:

BEGIN {RS="^$"}
{
    n=split($0,a,/# Pri: [^0-9]* DTG: [0-9]*\.[0-9]*/,s)
    top=s[0] a[1]
    for (i=1;i<n; i++) {
        match(s[i],/DTG: ([0-9]*)\.([0-9]*)/,c)
        b[(c[1] c[2])]=s[i] a[i+1]
    }
}


END {
    PROCINFO["sorted_in"]="@ind_num_asc"
    printf "%s%s", s[0], a[1]
    for (i in b)
        printf "%s", b[i]
    printf "%s", s[n]
}

更新

似乎这个方法被match() 函数拖慢了。 这是一个大约快 2.5 倍的版本:

BEGIN {RS="^$"}
{
    n=split($0,a,/# Pri: [^0-9]* DTG: [0-9]*\.[0-9]*/,s)
    top=s[0] a[1]
    for (i=1;i<n; i++) {
        ind=index(s[i],"DTG:")
        c=substr(s[i],ind+5)
        ind=index(c,".")
        c=substr(c,1,ind-1) substr(c,ind+1)
        b[c]=s[i] a[i+1]
    }
}


END {
    PROCINFO["sorted_in"]="@ind_num_asc"
    printf "%s%s", s[0], a[1]
    for (i in b)
        printf "%s", b[i]
    printf "%s", s[n]
}

【讨论】:

    【解决方案2】:

    假设您的记录之间有一个空白行,如示例输入所示:

    awk -v RS= '{gsub(/a/,"aA"); gsub(/\n/,"aB"); print $5, $0}' file |
    sort |
    awk -v ORS='\n\n' '{sub(/[^ ]+ /,""); gsub(/aB/,"\n"); gsub(/aA/,"a")}1'
    

    上面通过将所有“a”转换为“aA”,然后将“\n”转换为“aB”,将每条记录压缩到一行(所以稍后在输入中转换回“aB”时您会知道只能来自第二个 gsub(),因为所有原始“aB”都已由第一个 gsub()) 转换为“aAB”,在 DTG 值之前添加,对其进行排序,然后反转该过程。

    我对一个有 100,000 条记录的文件进行了计时(您发布的示例 2 重复了 50,000 次),运行时间不到 7 秒:

    real    0m6.930s
    user    0m6.722s
    sys     0m0.388s
    

    为了帮助@Jotne 了解在 cygwin 上使用 bash 4.1.11(2)、perl 5.14.4 运行脚本时会发生什么,这对我来说是脚本的 2 个版本,第二个版本是修改后的 print 语句:

    $ cat file
    # Pri: Rest, DTG: 20140109183524.013578591, CID: 1004592976
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    
    # Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:98
    
    #
    $ printf "20140109183524.013580787\n20140109183524.013578591\n" | sort
    20140109183524.013578591
    20140109183524.013580787
    
    $ printf "20140109183524.013580787\n20140109183524.013578591\n" | sort -n
    20140109183524.013578591
    20140109183524.013580787
    
    #
    $ cat script1.pl
    #!/usr/bin/perl -w
    
    use strict;
    
    my $dtg;
    my %data;
    
    while (<>) {
        if (/^#.*?DTG: ([\d.]+)/) {
            $dtg = $+;
        }
        $data{$dtg} .= $_;
    }
    
    print @data{sort keys %data};
    
    #
    $ cat script2.pl
    #!/usr/bin/perl -w
    
    use strict;
    
    my $dtg;
    my %data;
    
    while (<>) {
        if (/^#.*?DTG: ([\d.]+)/) {
            $dtg = $+;
        }
        $data{$dtg} .= $_;
    }
    
    print @data{sort { $a <=> $b } keys %data};
    
    #
    $ perl script1.pl < file
    # Pri: Rest, DTG: 20140109183524.013578591, CID: 1004592976
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    
    # Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:98
    
    #
    $ perl script2.pl < file
    # Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:98
    # Pri: Rest, DTG: 20140109183524.013578591, CID: 1004592976
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    

    如您所见,perl 脚本的第二个版本以与期望相反的顺序打印记录,无论是字母排序还是数字排序。

    【讨论】:

    • 我用 80000 条记录测试了 16 Mb 随机文件的速度:它似乎是 janos 解决方案的 4 倍。@janos 运行速度为 0.063 秒,而这使用了 2.43秒。
    • 我在我的 100K 样本输入文件上运行 janos 的解决方案时发现了类似的时差(他的运行时间为 1.3 秒,而我的运行时间为 6.9 秒)但使用他的“更正”时,janos 的输出排序不正确排序打印语句。不过他原来的那个似乎工作得很好,所以我不知道问题出在哪里。
    • 是的,@janos 的新版本对我也不起作用..
    • 就个人而言,即使 100K 文件需要多 4 秒的时间,我还是喜欢我的awk | sort | awk 解决方案,因为每个单独的步骤都很简单,它遵循 UNIX 习惯,即在最佳工具之间进行管道连接作业,其核心是使用为主要作业创建的主要 UNIX 工具,即sort 进行排序,因此您知道它将能够简洁而稳健地执行所需的任何类型的排序。
    • +1 我喜欢你的解决方案,因为它聪明。但是与简单的 perl 脚本相比,聪明的做法让它有点 hacky。我同意管道的美感,但由于 OP 专门要求速度,awk | sort | awk 解决方案不可避免地是有损的。我不知道差异是否真的很大。
    【解决方案3】:

    给你:

    #!/usr/bin/perl -w
    
    use strict;
    
    my $dtg;
    my %data;
    
    while (<>) {
        if (/^#.*?DTG: ([\d.]+)/) {
            $dtg = $+;
        }
        $data{$dtg} .= $_;
    }
    
    print @data{sort keys %data};
    

    如果你把这个脚本保存在script.pl,那么你可以这样使用它:

    perl script.pl < data.txt
    

    但是,在对最后一行的 DTG 值进行排序时,sort 函数将值视为字符串,按字母顺序对它们进行排序,正如@hakon-haegland 在他的评论中指出的那样。因此,例如,1001 之类的值会错误地出现在 110 之前。

    如果你想强制按数字排序,你可以试试这个

    print @data{sort { $a <=> $b } keys %data};
    

    但是,这不适用于具有太多数字的数字,例如 OP 示例中的数字,并且行为也可能取决于 Perl 的版本。从 perl 5.12.4 开始,这种技术似乎可以可靠地处理少于 16 位的数字。

    【讨论】:

    • +1 不错!目前这比我的解决方案快大约 3 倍,但它进行了不同的排序。它根据字符串值排序,例如值1,110,1001 它排序为1,1001,110..
    • @HåkonHægland 很好发现!我为此添加了一个修复程序。
    • 我无法让您的改进版本正常工作,它似乎与@Kenosis 解决方案在修复之前存在类似的问题(排序不正确)..
    • @HåkonHægland 感谢您的警惕,但它仍然适用于我:s 在 osx 和 debian 中测试,有或没有数字排序
    • 谢谢@EdMorton,你说得对,我之前忽略了一些事情。它不像我在 Git Bash 中宣传的那样工作,在我这里的旧版 Linux 中也没有。我会在家里再次检查,但我怀疑我可能也忽略了那里,这从来没有真正奏效。 :(
    【解决方案4】:

    尝试类似(我添加了一些测试记录):

    use strict;
    use warnings;
    use Data::Dumper;
    use v5.16;
    my (%data,$DTG);
    while(<DATA>) {
      if(/^#.*?DTG:\s(.*?),/) {
         $DTG = $1;
           }
      if($DTG) {
          $data{$DTG} .= $_;
         }
       }
     for my $d (sort keys %data) {
        print $data{$d};
      }
    
    
    __DATA__
    # Pri: Rest, DTG: 20140109183524.013578591, CID: 1004592976
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    
    # Pri: Rest, DTG: 20140109183524.013580787, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:98
    
    # Pri: Rest, DTG: 20140109183524.013580900, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:198
    # Pri: Rest, DTG: 20140109183524.000580787, CID: 1004592977
    dn: op=applV,uid=2256c1e8-1166-46e4-a3a9-9fbe27694e12,Name=sub,loc=Ph,o=s,o=s.com
    action: add
    Class: c-subSpecData
    Option3: applView
    Option2: 3
    Option1:34
    Option5:298
    

    或者,如果您更喜欢 oneliner:

    perl -wlne 'if(/^#.*?DTG:\s(.*?),/) {$DTG = $1;} if($DTG) {$data{$DTG} .= $_."\n"} END {for(sort keys %data) {print $data{$_},"\n"}}' datafile.txt
    

    【讨论】:

    • 对不起,我对Perl不是很熟悉,使用5.16版本的原因是什么?
    • 这个解决方案并不真正需要,但它是我创建新脚本时模板的一部分。如果使用现代 perl 版本中添加的功能,通常需要它。
    • 好的 :) 我明白了,我的机器上只有 5.14.2 版本(Ubuntu 12.04)
    • 我尝试测试您的代码并更改为use v5.14 并以perl s.perl file 运行它并得到错误:Name "main::DATA" used only once: possible typo at s.perl line 6
    • @HåkonHægland 啊是的,这是一个最近的功能,如果你真的很好奇,你可以删除脚本的那部分 __ DATA __ 和下面的所有行,将这一行 更改为 并调用带有数据文件的脚本,例如 perl scriptname.pl datafile
    【解决方案5】:

    此选项设置以段落模式 (local $/ = '';) 读取文件,以分块读取,因为这避免了连接“记录”行所需的时间。它也不假定日期/时间戳都是唯一的,因此它为记录创建了一个数组哈希 (HoA):

    use strict;
    use warnings;
    
    my %hash;
    local $/ = '';
    
    while (<>) {
        s/\s+$//;
        push @{ $hash{$1} }, $_ if /DTG:\s+([^,]+?),/;
    }
    
    for my $key ( sort timeStamp keys %hash ) {
        print $_, "\n\n" for @{ $hash{$key} };
    }
    
    sub timeStamp{
        my @a = split /\./, $a;
        my @b = split /\./, $b;
        return $a[0]  <=> $b[0] || $a[1]  <=> $b[1]
    }
    

    用法:perl script.pl inFile [&gt;outFile]

    最后一个可选参数将输出定向到文件。

    希望这会有所帮助!

    【讨论】:

    • 我试过你的脚本,它非常快,但不知何故它没有正确排序。也许我在运行它时犯了一些错误?我对 perl 还不是很熟悉 :)
    • @HåkonHægland - sort 结果是什么?使用扩展数据集,sorted 的记录在数字上是可以的。另外,做了一个小修改:从每条记录中删除了尾随空格,所以printing 是一致的。
    • 对我来说仍然无法正确排序:试试这个文件:pastebin.com/5kurgq3L
    • 这里是带有随机 DTG 的 80000 记录文件的排序结果的前 100 行:pastebin.com/H4wNmDe3。如您所见,结果看起来很奇怪..
    • @HåkonHægland - 欣赏数据。固定。
    【解决方案6】:
    #!/usr/bin/perl
    
    $/="#"; # to read the record from # to # as a single line
    open "FH" , "< input.txt " ;
    my @data = <FH> ;
    chomp(@data);
    my %data ;
    
    foreach my $line ( @data )
    {
      $line =~ /DTG:\s*(.*?),/;
    chomp($1);
      $data{$1}=$line ;
    }
    
    
    foreach ( sort { $a <=> $b }(keys %data ) )
    {
        print "$data{$_} \n";
    }
    

    【讨论】:

    • 这似乎是一个非常快的脚本。。添加文本"hi "的目的是什么?
    • 大声笑..!好吧,这只是一个将人们聚集在一起的神奇词:P .. 抱歉,这只是我用来测试 o/p 的词。
    • 我明白了 :) 您的代码仍然很快,但是它修改了文件的原始记录,使得排序后的记录错过了原始记录中的# 符号,此外还完成了排序词汇上而不是数字上..
    • 嗯,是的,这是排序命令使用的默认值,而且“#”正在被删除,因为在这里我们在读取输入 $/='#' 时将“#”视为分隔符跨度>
    猜你喜欢
    • 2011-07-17
    • 1970-01-01
    • 1970-01-01
    • 2016-08-13
    • 1970-01-01
    • 2014-05-05
    • 2011-01-28
    • 1970-01-01
    • 2020-04-22
    相关资源
    最近更新 更多