【问题标题】:Intersection of files文件的交集
【发布时间】:2012-09-15 22:54:13
【问题描述】:

我有两个大文件(27k 行和 450k 行)。它们看起来有点像:

File1:
1 2 A 5
3 2 B 7
6 3 C 8
...

File2:
4 2 C 5
7 2 B 7
6 8 B 8
7 7 F 9
... 

我想要两个文件中第三列在两个文件中的行(注意带有 A 和 F 的行被排除在外):

OUTPUT:
3 2 B 7
6 3 C 8
4 2 C 5
7 2 B 7
6 8 B 8

最好的方法是什么?

【问题讨论】:

  • 最简单的就是扫描一次,提取列,计算交点,然后再次扫描,提取匹配的行。
  • 匹配字段是否像示例数据中的单个字符?你能创建一个可以选择值的字符类吗?

标签: algorithm file unix intersection


【解决方案1】:

首先我们对第三个字段的文件进行排序:

sort -k 3 file1 > file1.sorted
sort -k 3 file2 > file2.sorted

然后我们使用 comm 获得第三个字段的共同值:

comm -12 <(cut -d " " -f 3 file1.sorted | uniq) <(cut -d " " -f 3 file2.sorted | uniq) > common_values.field

现在我们可以在公共值上加入每个排序的文件:

join -1 3 -o '1.1,1.2,1.3,1.4' file1.sorted common_values.field > file.joined
join -1 3 -o '1.1,1.2,1.3,1.4' file2.sorted common_values.field >> file.joined

输出已格式化,因此我们获得与文件中​​使用的字段顺序相同的字段顺序。 使用的标准 unix 工具:sort、comm、cut、uniq、join。 &lt;( ) 适用于 bash,对于其他 shell,您可以使用临时文件。

【讨论】:

    【解决方案2】:

    这是一个使用 grep、sed 和 cut 的选项。

    提取第 3 列:

    cut -d' ' -f3 file1 > f1c
    cut -d' ' -f3 file2 > f2c
    

    file1中查找匹配行:

    grep -nFf f2c f1c | cut -d: -f1 | sed 's/$/p/' | sed -n -f - file1  > out
    

    file2中查找匹配行:

    grep -nFf f1c f2c | cut -d: -f1 | sed 's/$/p/' | sed -n -f - file2 >> out
    

    输出:

    3 2 B 7
    6 3 C 8
    4 2 C 5
    7 2 B 7
    6 8 B 8
    

    更新

    如果您有非对称数据文件并且较小的数据文件适合内存,那么这种一次性 awk 解决方案将非常有效:

    parse.awk

    FNR == NR {
      a[$3] = $0
      p[$3] = 1
      next
    }  
    
    a[$3]
    
    p[$3] {
      print a[$3]
      delete p[$3]
    }
    

    像这样运行它:

    awk -f parse.awk file1 file2
    

    file1 是两者中较小的一个。

    解释

    • FNR == NR 块将file1 读入两个哈希值。
    • 如果$3a 中的键,则a[$3] 打印file2 行。
    • 如果$3p 中的一个键,则p[$3] 打印file1 行并删除该键(仅打印一次)。

    【讨论】:

      【解决方案3】:
      awk '{print $3}' file1 | sort | uniq > file1col3
      awk '{print $3}' file2 | sort | uniq > file2col3
      grep -Fx -f file1col3 file2col3 | awk '{print "\\w+ \\w+ " $1 " \\w+"}' > col3regexp
      egrep -xh -f col3regexp file1 file2
      

      获取两个文件中所有唯一的第 3 列,将它们相交(使用grep -F),打印一堆将匹配您想要的列的正则表达式,然后使用egrep 从两个文件中提取它们。

      【讨论】:

      • 谢谢,我做了几乎完全一样的事情!
      【解决方案4】:

      首先从第三列获取公共值。然后过滤两个文件中第三列匹配的行。

      如果列由单个字符分隔,您可以使用cut 提取一列。对于可以由任意数量的空格分隔的列,请使用awk。获取公共第 3 列值的一种方法是提取列,对它们进行排序并调用comm。使用 bash/ksh/zsh 进程替换:

      comm -12 <(awk '{print $3}' file1 | sort -u) <(awk '{print $3}' file2 | sort -u)
      

      现在将这些转换为grep 模式,然后过滤。

      comm -12 <(awk '{print $3}' file1 | sort -u) <(awk '{print $3}' file2 | sort -u) |
      sed -e 's/[][.\|?*+^$]/\\&/g' \
          -e 's/.*/^[^[:space]]+[[:space]]+[^[:space]]+[[:space]]+\1[[:space]]/' |
      grep -E -f - file1 file2
      

      上面的方法应该可以很好地处理大文件。但是在 500k 行时,您没有大文件。这些文件应该很适合内存,一个简单的 Perl 解决方案就可以了。加载两个文件,提取列值,打印匹配的列。

      perl -n -e '
          @lines += 1;
          $c = (split)[2];
          $seen{$c}{$ARGV} = 1;
      END {
          foreach (@lines) {
              $c = (split)[2];
              print if %{$seen{$c}} == 2;
          }
      }' file1 file2
      

      【讨论】:

        猜你喜欢
        • 2018-08-27
        • 2011-09-22
        • 1970-01-01
        • 2017-03-06
        • 1970-01-01
        • 1970-01-01
        • 2011-09-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多