2

我有时间序列数据,我想在其中找到所有相互匹配的行,但值可以不同(匹配到第一个选项卡)!您可以在下面看到 vimdiff,我想在其中删除仅在其他时间序列上发生的日子。

我正在寻找最简单的 unix 工具来做到这一点!

在此处输入图像描述

在此处输入图像描述

Timeserie这里这里

简单的例子

输入

Left file                            Right File    
------------------------             ------------------------
10-Apr-00     00:00    0     ||      10-Apr-00     00:00     7
20-Apr 00     00:00    7     ||      21-Apr-00     00:00     3

输出

Left file                           Right File    
------------------------            ------------------------
10-Apr-00     00:00    0    ||      10-Apr-00     00:00     7
4

3 回答 3

4

让我们考虑这些示例输入文件:

$ cat file1
10-Apr-00       00:00   0
20-Apr-00       00:00   7
$ cat file2
10-Apr-00       00:00   7
21-Apr-00       00:00   3

要将具有相同日期的这些行合并在一起:

$ awk 'NR==FNR{a[$1]=$0;next;} {if ($1 in a) print a[$1]"\t||\t"$0;}' file1 file2
10-Apr-00       00:00   0       ||      10-Apr-00       00:00   7

解释

  • NR==FNR{a[$1]=$0;next;}

    NR是到目前为止读取的FNR行数,是从当前文件读取的行数。因此,当 时NR==FNR,我们仍在读取第一个文件。如果是这样,则将整行 保存$0在数组a中第一个字段 的键下$1,即日期。然后,跳过其余命令并跳转到该next行。

  • if ($1 in a) print a[$1]"\t||\t"$0

    如果我们到达这里,那么我们正在读取第二个文件,file2. 如果该行的第一个字段$1是我们已经在 中看到的日期,file1换句话说,如果$1 in a,则将该行与来自 的相应行一起打印出来file1。这两行由||制表符分隔。

替代输出

如果您只想选择file2日期也在 中的行file1,则可以简化代码:

$ awk 'NR==FNR{a[$1]++;next;} {if ($1 in a) print;}' file1 file2
10-Apr-00       00:00   7

或者,更简单:

$ awk 'NR==FNR{a[$1]++;next;} ($1 in a)' file1 file2
10-Apr-00       00:00   7
于 2014-11-17T05:14:03.533 回答
2

有一个相对不为人知的 unix 命令join。它可以在关键列上加入排序文件。

为了在您的上下文中使用它,我们遵循以下策略(left.txt 和 right.txt 是您的文件):

  1. 添加行号(将所有内容放在最后一步的原始序列中)

    nl left.txt > left_with_lns.txt
    nl right.txt > right_with_lns.txt
    
  2. 在日期列上对两个文件进行排序

    sort left_with_lns.txt -k 2 > sl.txt
    sort right_with_lns.txt -k 2 > sr.txt
    
  3. 使用日期列连接文件(所有时间都是 0:00)(这会将两个文件的所有列与相应的键合并,但我们提供了一个输出模板来将第一个文件中的列写入某处,并将第二个文件中的列写入其他地方(但只有那些具有匹配键的行才会以结果 fl.txt 和 fr.txt 结尾)

    join -j 2 -t $'\t' -o 1.1 1.2 1.3 1.4 sl.txt sr.txt > fl.txt
    join -j 2 -t $'\t' -o 2.1 2.2 2.3 2.4 sl.txt sr.txt > fr.txt
    
  4. 在 linenumber 列上对结果进行排序并输出其他列

    sort -n fl |cut -f 2- > left_filtered.txt
    sort -n fr.txt | cut -f 2- > right_filtered.txt
    

使用的工具:cut、join、nl、sort。

于 2015-11-07T20:11:39.797 回答
1

根据@Masi 的要求,我尝试使用 sed 制定解决方案。

我的第一次尝试使用了两次传球;第一个转换file1成一个 sed 脚本,在第二个 pass 中用于 filter file2

sed 's/\([^ \t]*\).*/\/^\1\t\/p;t/' file1 > sed1
sed -nf sed1 file2 > out2

对于大输入文件,这很慢;对于 中的每一行file2,sed 必须处理的模式数量等于 中的行数file1。我没有做过任何分析,但如果时间复杂度是二次的,我不会感到惊讶。

我的第二次尝试合并和排序这两个文件,然后扫描所有行以搜索对。这在线性时间内运行,因此要快得多。请注意,此解决方案会破坏文件的原始顺序;字母排序不适用于此日期表示法。提供具有不同日期格式 (ymd) 的文件将是解决此问题的最简单方法。

sed 's/^[^ \t]\+/&@1/' file1 > marked1
sed 's/^[^ \t]\+/&@2/' file2 > marked2

sort marked1 marked2 > sorted

sed '$d;N;/^\([^ \t]\+\)@1.*\n\1@2/{s/\(.*\)\n\(.*\)/\2\n\1/;P};D' sorted > filtered
sed 's/^\([^ \t]\+\)@2/\1/' filtered > out2

解释:

  • 在第一个命令中,s/^[^ \t]\+/&@1/附加@1到每个日期。这使得合并文件成为可能,在排序时将相同的日期保持在一起,并且仍然能够区分来自不同文件的行。
  • 第二个命令对file2;执行相同的操作 显然带有自己的标记@2
  • sort命令合并两个文件,将相等的日期组合在一起。
  • 第三个 sed 命令返回file2日期也出现在file1.
  • 第四个 sed 命令@2从输出中删除标记。

第三条 sed 命令详解:

  • $d禁止不恰当地打印最后一行
  • N读取另一行输入并将其附加到模式空间中已经存在的行
  • /^\([^ \t]\+\)@1.*\n\1@2/匹配来自不同文件但日期相同的两行
  • {启动命令组
  • s/\(.*\)\n\(.*\)/\2\n\1/交换模式空间中的两行
  • P打印模式空间中的第一行
  • }结束命令组
  • D从模式空间中删除第一行

坏消息是,即使是第二种方法也比@John1024 提出的 awk 方法慢。Sed 从来没有被设计成一个合并工具。awk 也不是,但 awk 的优势在于能够将整个文件存储在字典中,这使得 @John1024 的解决方案非常快。字典的缺点是内存消耗。在巨大的输入文件上,我的解决方案应该有优势。

于 2015-11-07T01:19:10.743 回答