【问题标题】:Suggestions on processing large file - python or command line?关于处理大文件的建议 - python 或命令行?
【发布时间】:2011-09-06 20:46:20
【问题描述】:

给定两个文件,一个包含以下形式的条目:

label1 label2 name1
label1 label3 name2

和其他的形式:

label1 label2 name1 0.1 1000
label9 label6 name7 0.8 0.5

假设您想从文件 2 中提取前三个元素出现在文件 1 中的一行(顺序很重要)的那些行 - 关于如何快速实现圆顶的任何建议?

给定上述示例数据的任何此类脚本的输出文件将是:

label1 label2 name1 0.1 1000

我玩过蟒蛇:

inp = open(file1.txt, 'r')
look_up = [i.split() for i in inp.readlines()]
inp.close()

inp = open('file2', 'wt')

holder = []

line = inp.readline()
while line:
    line = line.split()
    if [line[0], line[1], line[2]] in look_up:
        holder.append(line)
    line = inp.readline()

不过,这似乎需要一段时间。这些文件相当大。

谢谢!

【问题讨论】:

  • “相当大”有多大?兆字节?千兆字节?太字节?
  • 我很想知道您的长期尝试是否能够在您写下并得到这个问题的答案的时间内完成。对于一次性问题,最简单的解决方案通常是最好的,即使它不是最优的。
  • @ Mark - 我的长期运行尝试被划分为 16 个作业并放在一个集群上。 6小时后还在运行!哎呀!

标签: python parsing command-line


【解决方案1】:

您的 python 版本效率相当低,因为您正在测试列表中的成员资格,而不是集合或字典(即 O(n) 查找时间而不是 O(1))。

尝试使用 set 元组或 set 字符串。元组将是更好的选择,因为这两个文件可以在不同的分隔符上拆分,但我认为您不会看到特别大的性能差异。与测试很长列表的成员资格相比,tuple('something'.split()) 相对较快。

另外,无需致电inp.readlines()。换句话说,你可以这样做

look_up = set(tuple(line.split()) for line in inp)

您应该会看到显着的加速,而无需更改代码的任何其他部分,而不是 tuple(line[:3]) 而不是 [line[0], line[1], line[2]]

实际上,grep 和 bash 非常适合这个...(未经测试,但它应该可以工作。)

while read line
do
    grep "$line" "file2.txt"
done < "file1.txt"

要查看哪个更快,我们可以generate some test datafile1.txt 中的约 4500 个键和file2.txt 中的 1000000 行),并对同一事物的简单 python 版本进行基准测试(大致......这些行将是以与 grep 版本不同的顺序打印。)。

with open('file1.txt', 'r') as keyfile:
    lookup = set(tuple(line.split()) for line in keyfile)

with open('file2.txt', 'r') as datafile:
    for line in datafile:
        if tuple(line.split()[:3]) in lookup:
            print line,

python 版本的速度提高了约 70 倍:

jofer@cornbread:~/so> time sh so_temp149.sh > a

real    1m47.617s
user    0m51.199s
sys     0m54.391s

对比

jofer@cornbread:~/so> time python so_temp149.py > b

real    0m1.631s
user    0m1.558s
sys     0m0.071s

当然,这两个示例以完全不同的方式解决问题。我们实际上是在比较两种算法,而不是两种实现。例如,如果我们在 file1 中只有几行关键行,那么 bash/grep 解决方案很容易获胜。

(bash 是否有某种内置容器,具有 O(1) 查找成员资格?(我认为 bash 4 可能有一个哈希表,但我对此一无所知......)这会很有趣尝试在 bash 中实现与上面的 python 示例类似的算法......)

【讨论】:

  • 您不能拥有set 的列表。每个set 元素都必须是可散列的(完全不可变)。
  • @John Y - 对!我没有思考,也没有测试任何东西......谢谢!
  • set( tuple(line.split()) for line in inp ) 可能是更好的方法。
  • @S.Lott - 实际上,我正在把它改成这样。谢谢。
  • 那么,Python 解决方案与 grep/bash 相比如何?
【解决方案2】:

Hacky bash/sort/Perl 解决方案:

$ cat > 1
label1 label2 name1
label1 label3 name2

$ cat > 2
label1 label2 name1 0.1 1000
label9 label6 name7 0.8 0.5

$ (cat 1; cat 2; ) | sort | perl -ne 'INIT{$pattern_re="(?:\\S+) (?:\\S+) (?:\\S+)"; $current_pattern="";} if(/^($pattern_re)$/o){$current_pattern=$1} else {if(/^($pattern_re)/o) { print if $1 eq $current_pattern} }'
label1 label2 name1 0.1 1000

它将两个文件合并到一个列表中,对它进行排序(因此我们得到具有相同键的数据块,从文件 1 开始逐行引导),然后使用特殊的 Perl oneliner 只留下具有前置“标题”的格式良好的行" 来自文件 1。

【讨论】:

    【解决方案3】:

    您可以尝试使用字符串“label1 label2 name1”作为键,而不是值的三元组。

    【讨论】:

      【解决方案4】:

      我会使用散列来存储第一个文件中的值。虽然不是那种错误恢复能力(每个项目之间只有 1 个空格),但你会明白一般的想法......

      #!/usr/bin/env python
      
      labels={}
      with open('log') as fd:
          for line in fd:
              line=line.strip()
              labels[line]=True
      
      with open('log2') as fd:
          for line in fd:
              if " ".join(line.split()[0:3]) in labels:
                  print line
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多