Python中的随机化减少文本文件

时间:2013-02-27 10:05:19

标签: python parsing text random reduction

我在bash中解决了以下问题,但考虑到我需要减少的文件大小,我觉得它效率很低而且非常慢。希望有人知道如何在Python中做同样的事情并希望加快速度。

最初的问题是减少非常大的文本文件(50-60万行,制表符分隔列)。 其中一列被视为一个键,即我们确定文件中有多少行具有唯一键,然后随机选择它们的百分比(例如,如果减少75%,则为总数的四分之一)以附加到一个新文件,将保持我们的结果。我们继续检查其余的密钥,随机化,然后将包含每个唯一密钥的所有行减少相同的百分比。如果无法进行缩减 - 我们只需将所有行传送到生成的文件中。

正如我所说,我的bash脚本工作得很好,但它很慢并且将各种awk和grep构造串起来。从各方面来看,Python应该以更优雅的方式处理这个问题而不会过多地损害内存(同样,在这种情况下我们处理的是5000多万行文件)。 任何建议/技巧都会有所帮助!谢谢!

3 个答案:

答案 0 :(得分:2)

简单的解决方案是按键列对文件进行排序,例如,sort第二列的{{3}}制表符分隔输入:

#!/bin/bash
printf "a\tz\nb\ty\nc\tx" | sort -k 2 -t $'\t'

然后解决一个更简单的问题,即为每个唯一键检索25%的随机行,其中所有具有相等键的行都相邻,约束条件是每个唯一键至少应保留一行:

#!/usr/bin/env python
import random
import sys
from itertools import chain, groupby

def choose_random(iterator, fraction, random=random.random):
    """Lazy analog of:

        L = list(iterator)
        k = int(len(L) * fraction + .5) or 1 # get at least one
        result = random.sample(L, k)

    Note: this function doesn't randomize the order of elements
          that would require to keep selected elements in memory
          and number of output elements is not exactly k
    """
    # always yield at least one item if input is not empty
    item = next(iterator)
    it = (x for x in chain([item], iterator) if random() < fraction)
    for x in chain([next(it, item)], it):
        yield x

def getkey(line):
    return line.split("\t")[1] # 2nd column

for key, group in groupby(sys.stdin, key=getkey):
    sys.stdout.writelines(choose_random(group, fraction=0.25))

注意:输入文件中的最后一行应包含换行符,否则如果选择了最后一行,则输出会被破坏。

脚本接受stdin上的排序(通过键列)输入,并将减少的输出打印到stdout。它一次只需要在内存中存储一​​行。它是单程算法(O(n))。

答案 1 :(得分:1)

因为你的问题很模糊,我会给出一个高水平的解决方案

  1. 不要读取内存fileObj.read()fileObj.readlines()中的整个文件,而是遍历文件for line in fileObj。为什么?这将是记忆油炸
  2. 根据List

    创建Queue实现
    class Queue(object):
        def __init__(self, max_size):
            self.queue = []
            self.max_size = max_size
        def __getitem__(self, index):
            if 0 <= index < max_size:
                return self.queue[index]
            else:
                raise IndexError
        def __iter__(self):
            return iter(self.queue)
        def push(seq):
            if isinstance(seq, Iterable):
                if len(self.queue) + len(seq) > self.max_size:
                    raise Full
                self.queue = seq
            else:
                if len(self.queue) + 1 > self.max_size:
                    raise Full
                self.queue.append(seq)
        def pop():
            if self.queue:
                return self.queue.pop(0)
    
  3. 创建一个队列字典,其中maxsize = 2 *所选项目的百分比

  4. 这样的东西
        PCT_SELECTED = 100
        MAXSIZE = 2 * PCT_SELECTED
        KEY_START = 10
        KEY_STOP = 15 
        from collection import defaultdict
        queue_dict = defaultdict(Queue(MAXSIZE))
    
    1. 将队列中的元素放入非阻塞区域
    2. 如果Queue已满,则会引发异常Full,在这种情况下,您会从队列中随机选择50%的元素并丢弃其余元素。
    3. 类似

          with open("your-file") as fin:
              for line in fin:
                  key = line[KEY_START: KEY_STOP]
                  try:
                      queue_dict[key].push(line)
                  except Full:
                      queue_dict[key] = random.sample(queue_dict[key], PCT_SELECTED)
      
      1. 最后遍历字典并随机删除队列

        queue_dict = {key: random.sample(value, PCT_SELECTED) for key, value in queue_dict.items()}
        
      2. 现在您可以阅读dictinary并写入文件。

答案 2 :(得分:0)

对于大量项目,只需选择75%就可以通过检查每个项目的随机数来完成。

import random

with open('input') as f:
for line in f:
    if random.random() < 0.75:
        print line

如果您需要保证每个键中至少有一个项目(即使它只有两行):

import random
keys = set()

with open('input') as f:
    for line in f:
        columns = line.split('\t')
        key = columns[0]

        if not key in keys:
           print line
           keys.add(key)
           continue

        if random.random() < 0.75:
            print line