【问题标题】:Counting line occurrence and dividing by total number of lines - unix / python计算行数并除以总行数 - unix / python
【发布时间】:2014-08-06 09:29:07
【问题描述】:

我有一个文本文件 test.in 如下:

english<tab>walawala
foo bar<tab>laa war
foo bar<tab>laa war
hello world<tab>walo lorl
hello world<tab>walo lorl
foo bar<tab>laa war

期望的输出应该是:

english<tab>walawala<tab>0.1666
foo bar<tab>laa war<tab>0.5
hello world<tab>walo lorl<tab>0.3333

新列是行数除以总行数。

目前我正在这样做:

cat test.in | uniq -c | awk '{print $2"\t"$3"\t"$1}' > test.out

但这只能给我行数而不是概率。此外,我的文件非常庞大,例如 1,000,000,000 行,每列至少 20 个字符。

我怎样才能正确快速地获得所需的输出?

有没有同样快的 Pythonic 解决方案?

【问题讨论】:

  • 你试过wc吗? q=`cat test.in | wc -l`;cat test.in | uniq -c | awk '{print $2"\t"$3"\t"$1'/$q'}'
  • @user189 useless use of cat 警报。 :)
  • 注意浮点是四舍五入的...

标签: python unix count text-files uniq


【解决方案1】:

请注意,uniq 只计算重复行,并且必须在其前面加上 sort 才能考虑文件中的所有行。对于sort | uniq -c,使用collections.Counter 的以下代码更有效,因为它根本不需要对任何内容进行排序:

from collections import Counter

with open('test.in') as inf:
    counts = sorted(Counter(line.strip('\r\n') for line in inf).items())
    total_lines = float(sum(i[1] for i in counts))
    for line, freq in counts:
         print("{}\t{:.4f}".format(line, freq / total_lines))

这个脚本输出

english<tab>walawala<tab>0.1667
foo bar<tab>laa war<tab>0.5000
hello world<tab>walo lorl<tab>0.3333

对于您的描述中给出的输入。


但是,如果您只需要合并连续的行,例如 uniq -c,请注意任何使用 Counter 的解决方案都会给出您问题中给出的输出,但您的 uniq -c 方法将 不是uniq -c will be的输出:

  1 english<tab>walawala
  2 foo bar<tab>laa war
  2 hello world<tab>walo lorl
  1 foo bar<tab>laa war

不是

  1 english<tab>walawala
  3 foo bar<tab>laa war
  2 hello world<tab>walo lorl

如果这是您想要的行为,您可以使用itertools.groupby

from itertools import groupby

with open('foo.txt') as inf:
    grouper = groupby(line.strip('\r\n') for line in inf)
    items = [ (k, sum(1 for j in i)) for (k, i) in grouper ]
    total_lines = float(sum(i[1] for i in items))
    for line, freq in items:
        print("{}\t{:.4f}".format(line, freq / total_lines))

不同之处在于,如果test.in 具有您指定的内容,uniq 管道将产生您在示例中给出的输出,而是您会得到:

english<tab>walawala<tab>0.1667
foo bar<tab>laa war<tab>0.3333
hello world<tab>walo lorl<tab>0.3333
foo bar<tab>laa war<tab>0.1667

由于这不是您的输入示例所说的,因此您可能无法在没有 sort 的情况下使用 uniq 来解决您的问题 - 那么您需要求助于我的第一个示例,Python 肯定会更快比你的 Unix 命令行。


顺便说一句,这些在所有 Python > 2.6 中的工作方式都是一样的。

【讨论】:

    【解决方案2】:

    这是一个纯粹的 AWK 解决方案:

    <test.in awk '{a[$0]++} END {for (i in a) {print i, "\t", a[i]/NR}}'
    

    它使用 AWK 的数组和特殊变量 NR,它跟踪行数。

    让我们剖析一下代码。第一个区块

    {a[$0]++}
    

    对输入中的每一行执行一次。这里$0 代表每一行,它被用作数组a 的索引,因此它只是计算每行出现的次数

    第二块

    END {for (i in a) {print i, "\t", a[i]/NR}}
    

    在输入的末尾执行。此时,a 包含输入中每行的出现次数,并由行本身索引:因此,通过循环遍历它,我们能够打印行和相对出现的表(我们除以总数行数,NR)。

    【讨论】:

      【解决方案3】:
      from collections import Counter
      
      with open('data.txt') as infile:
          # Counter will treat infile as an iterator and exhaust it
          counter = Counter(infile)
      
          # Don't know if you need sorting but this will sort in descending order
          counts = ((line.strip(), n) for line, n in counter.most_common())
      
          # Convert to proportional amounts
          total = sum(counter.values())
          probs = [(line, n / total) for line, n in counts]
      
          print("\n".join("{}{}".format(*p) for p in probs))
      

      这有几个优点。它遍历文件中的行而不是加载整个文件,它利用现有的Counter 功能,它可以排序,并且很清楚发生了什么。

      【讨论】:

        【解决方案4】:

        Python 中的解决方案,但我不确定 1,000,000,000 行的性能。

        d = {}
        s = "english<tab>walawala\nfoo bar<tab>laa war\nfoo bar<tab>laa war\nhello world<tab>walo lorl\nhello world<tab>walo lorl\nfoo bar<tab>laa war"
        c = 0
        
        for l in s.split("\n"):
          c += 1
          if d.has_key(l):
            d[l] += 1
          else:
            d[l] = 1
        
        for k,v in d.items():
          print k + " -> " + str(float(v)/float(c))
        

        输出:

        english<tab>walawala -> 0.166666666667
        foo bar<tab>laa war -> 0.5
        hello world<tab>walo lorl -> 0.333333333333
        

        编辑:可以使用 Python 中的 Counter 对象改进此解决方案:https://docs.python.org/2/library/collections.html#collections.Counter

        【讨论】:

        【解决方案5】:

        也许通过在python中使用自动只能有一个值的字典

        from collections import defaultdict
        
        my_dict_counter = defaultdict(float)
        counter = 0
        
        for line in open('test.in'):
            my_dict_counter[line] += 1
            counter += 1 
        
        for line in my_dict_counter:
            print line.strip() + "\t" + str(my_dict_counter[line]/counter)
        

        【讨论】:

          【解决方案6】:

          Python 中的另一种解决方案:

          my_dict = {}
          counter = 0 
          with open('test.in') as f:
              for line in f:
                  counter += 1
                  try:
                      my_dict[line] = (my_dict[line]+1)
                  except:
                      my_dict[line] = 1
          
          for line in my_dict:
              print("%s%s%.4f" % (line[:-1], "<tab>", my_dict[line]/float(counter)))
          

          输出:

          english<tab>walawala<tab>0.1667
          hello world<tab>walo lorl<tab>0.3333
          foo bar<tab>laa war<tab>0.5000
          

          【讨论】:

            【解决方案7】:
            from collections import Counter
            with open("test.in") as f:
                counts = Counter(f)
            total = sum(counts.values())
            for k, v in counts.items():
                print("{0}<tab>{1:0.4f}".format(k.strip(), v / total))
            

            这不是按概率排序的。由于三个循环,性能为 O(3n),通过使用跟踪行的 TexIOBase 的子类或跟踪处理的总行数的 Counter 子类,可以将其减少到 O(2n)。

            【讨论】:

              【解决方案8】:

              如果在 RAM 中进行所有处理过于昂贵,您可以考虑使用简单的数据库。 sqlite 附带所有 python 安装。这个例子可以很容易地进行优化,但在演示该方法时,我觉得简单有利于速度:

              import sqlite3
              
              conn = sqlite3.connect('counts.db')
              c = conn.cursor()
              
              c.execute('CREATE TABLE counts (phrase TEXT PRIMARY KEY, num INT)')
              conn.commit()
              
              recs = 0
              with open('test.in') as fin:
                  for line in fin:
                      recs += 1
              
                      # see if already exists
                      c.execute("SELECT count(1) FROM counts WHERE phrase=?", (line,))
                      count = int(c.fetchone()[0]) + 1
                      if count == 1:
                          # add new record
                          c.execute("INSERT INTO counts VALUES(?,1)", (line,))
                      else:
                          # update record
                          c.execute("UPDATE counts SET num=?", (count,))
              
                      if recs % 10000 == 0:
                          conn.commit()
              
              conn.commit()
              
              for row in c.execute("SELECT phrase,num FROM counts ORDER BY phrase"):
                  print "%s\t%f" % (row[0], float(row[1]) / recs)
              

              【讨论】:

                猜你喜欢
                • 2023-02-22
                • 2023-02-14
                • 1970-01-01
                • 2011-06-02
                • 1970-01-01
                • 2021-12-10
                • 2012-05-21
                • 2011-08-16
                • 1970-01-01
                相关资源
                最近更新 更多