【问题标题】:Count duplicate pairs in file with constraint计算具有约束的文件中的重复对
【发布时间】:2014-09-10 06:59:39
【问题描述】:

问题介绍

我有以下文件(文件按所有三列排序):

D000001 D000001 1975
D000001 D000001 1976
D000001 D002413 1976
D000001 D002413 1979
D000001 D002413 1987
D000001 D004298 1976
D000002 D000002 1985
D000003 D000900 1975
D000003 D000900 1990
D000003 D004134 1983
D000003 D004134 1986

我需要计算重复的对(在第 1 列和第 2 列中),并为每个这样的对分配第 3 列中的最小值。对于我的玩具文件,输出应该是:

D000001 D000001 2 1975
D000001 D002413 3 1976
D000001 D004298 1 1976
D000002 D000002 1 1985
D000003 D000900 2 1975
D000003 D004134 2 1983

我的问题

  1. 文件很大(1 GB 到 5 GB),我想知道在这种设置中实现最合适的编程结构是什么?
  2. 如何正确打印最后(第三)列?在当前设置中(检查下面的代码),程序会打印最后一个(最高)值。

我对当前输出的初步尝试如下。

my_dict = {}

with open('test.srt') as infile:
  for line in infile:
    line = line.rstrip()
    word1, word2, year = line.split('|')
    year = int(year)
    my_tuple = (word1, word2)
    if my_tuple in my_dict:
      freq += 1
      my_dict[my_tuple] = (freq, year)
    else:
      freq = 1
      my_dict[my_tuple] = (freq, year)

for key, value in my_dict.items():
  print key[0], key[1], value

当前输出:

D000001 D000001 (2, 1976) ## Should be 1976 etc.
D000001 D002413 (3, 1987)
D000001 D004298 (1, 1976)
D000002 D000002 (1, 1985)
D000003 D000900 (2, 1990)
D000003 D004134 (2, 1986)

【问题讨论】:

  • 为什么文件很大排序?准备这样的东西似乎浪费了时间,尽管它确实大大简化了现阶段的处理,因为重复对是连续的。

标签: python algorithm optimization duplicates text-processing


【解决方案1】:

由于文件很大,您不应该使用内存中的字典来管理数据。开始读取源文件并将结果直接输出到目标文件,你真正需要的只是3个变量,

一个存储当前元组,第二个存储计数,第三个存储最大值。当元组发生变化时,将值写入输出文件并继续。

这将占用很少的内存,也可以处理非常大的文件。但当然,这只是因为你的元组是排序的。

【讨论】:

    【解决方案2】:

    解决方案:

    #!/usr/bin/env python
    
    
    def readdata(filename):
        last = []
        count = 0
    
        with open(filename, "r") as fd:
            for line in fd:
                tokens = line.strip().split()
                tokens[2] = int(tokens[2])
    
                if not last:
                    last = tokens
    
                if tokens[:2] != last[:2]:
                    yield last[:2], count or 1, last[2]
                    last = tokens
                    count = 1
                else:
                    count += 1
    
                tokens[2] = min(tokens[2], last[2])
    
            yield last[:2], count, last[2]
    
    
    with open("output.txt", "w") as fd:
        for words, count, year in readdata("data.txt"):
            fd.write(
                "{0:s} {1:s} ({2:d} {3:d})\n".format(
                    words[0], words[1], count, year
                )
            )
    

    输出:

    D000001 D000001 (2 1975)
    D000001 D002413 (3 1976)
    D000001 D004298 (1 1976)
    D000002 D000002 (1 1985)
    D000003 D000900 (2 1975)
    D000003 D004134 (2 1983)
    

    讨论:

    • 这会迭代地读取和处理数据(Python 2.x),因此它不会将所有内容都读入内存,从而可以处理非常大的数据文件。
    • 只要输入数据经过排序,也不需要复杂的数据结构。我们只需要跟踪最后一组令牌并跟踪每组“重复”的最小年份。

    该算法实际上与itertools.groupby 非常相似(请参阅使用此算法但假设为 Python 3.x 的其他答案)。

    可能值得注意的是,这个实现也是 ``O(n`)) (Big O)。

    【讨论】:

    • 1.5 GB 行大约需要 60 秒 :)
    • @James:干得好。欢迎稍作解释。特别强调不需要字典。
    【解决方案3】:

    Groupby 和生成器的发展方向:

    import csv
    from itertools import groupby
    
    def count_duplicate(it):
        # group by frist two fields
        groups = groupby(it, lambda line: line[:2])
        # this will produce (key, group) pairs, where a group is an iterator
        # containing ['field0', 'field1', year] values were the field0 and field1
        # strings are the same respectively
        # the min_and_count function converts such a group into count and min pair
        def min_and_count(group):
            i, min_year = 0, 99999
            for _, _, year in group:
                i += 1
                min_year = year if year < min_year else min_year
            return (i, min_year)
    
        yield from map(lambda x: x[0] + [min_and_count(x[1])], groups)
    
    
    with open("test.srt") as fp:
        # this reads the lines in a lazy fashion and filter empty lines out
        lines = filter(bool, csv.reader(fp, delimiter=' '))
        # convert the last value to integer (still in a lazy fashion)
        lines = map(lambda line: [line[0], line[1], int(line[2])], lines)
        # write result to another file
        with open("result_file", "w") as rf:
            for record in count_duplicate(lines):
                rf.write(str(record) + '\n')
    

    注意:此解决方案是 Python 3.x 解决方案,其中 filtermap 返回迭代器,而不是像在 Python 2.x 中那样返回 list(s)

    【讨论】:

      【解决方案4】:

      TXR:

      @(repeat)
      @dleft @dright @num
      @  (collect :gap 0 :vars (dupenum))
      @dleft @dright @dupenum
      @  (end)
      @  (output)
      @dleft @dright @(+ 1 (length dupenum)) @num
      @  (end)
      @(end)
      

      运行:

      $ txr data.txr data
      D000001 D000001 2 1975
      D000001 D002413 3 1976
      D000001 D004298 1 1976
      D000002 D000002 1 1985
      D000003 D000900 2 1975
      D000003 D004134 2 1983
      

      AWK:

      $1 != old1 || $2 != old2 { printf("%s", out);
                                 count = 0
                                 old1 = $1
                                 old2 = $2
                                 old3 = $3 }
      
                               { out = $1 " " $2 " " ++count " " old3 "\n" }
      
      END                      { printf("%s", out); }
      
      $ awk -f data.awk data
      D000001 D000001 2 1975
      D000001 D002413 3 1976
      D000001 D004298 1 1976
      D000002 D000002 1 1985
      D000003 D000900 2 1975
      D000003 D004134 2 1983
      

      TXR Lisp 函数式单线:

      $ txr -t '[(opip (mapcar* (op split-str @1 " "))
                       (partition-by [callf list first second])
                       (mapcar* (aret `@[@1 0..2] @(+ 1 (length @rest)) @[@1 2]`)))
                 (get-lines)]' < data
      D000001 D000001 2 1975
      D000001 D002413 3 1976
      D000001 D004298 1 1976
      D000002 D000002 1 1985
      D000003 D000900 2 1975
      D000003 D004134 2 1983
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-28
        • 2010-12-20
        • 2017-03-22
        • 2011-08-23
        • 2013-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多