【问题标题】:Python generator to lazy read large csv files and shuffle the rowsPython 生成器懒惰地读取大型 csv 文件并打乱行
【发布时间】:2021-06-09 03:54:11
【问题描述】:

我想编写一个函数,生成一个 csv 文件的随机行,该文件太大而无法放入内存(约 2500 万行)。

如何构建生成器以逐行生成数据,但与它们在 csv 文件中出现的顺序不同?

是否可以在惰性生成器函数中随机化/打乱行?

def readCSV(csvname, shuffle=True):

    for row in open(csvname, "r"):
        if shuffle:
            # Do something to shuffle the order of the rows
            # But I dont' know how to do this.
        yield row

【问题讨论】:

  • 多大?在所有记录都在内存中之前,您不能随机播放。他们都适合吗?
  • 对于改组,要么寻找内置函数,要么作为练习,您可以使用Fisher-Yates shuffle
  • @TimRoberts 如果内存太大,我们可以进行外部洗牌,类似于外部排序。
  • @SuperbRain 我该如何做外部洗牌
  • @JafetGado 例如,通过使用外部排序,按附加到行的随机数排序。

标签: python csv random generator


【解决方案1】:

您可以通过首先为大型 CSV 文件创建索引来从文件中读取 count 随机行。除非数据更改,否则只需执行一次。该索引将包含所有换行符所在文件的偏移量。

然后可以通过首先寻找所需的偏移量并读入一行来轻松读入随机行。

例如:

import random
import csv
import os
import io

def create_index(index_filename, csv_filename):
    with open(csv_filename, 'rb') as f_csv:
        index = 1
        line_indexes = []       # Use [0] if no header
        linesep = ord(os.linesep[-1])
        
        while True:
            block = f_csv.read(io.DEFAULT_BUFFER_SIZE * 1000)
            
            if block:
                block_index = 0
                line_indexes.extend(offset + index for offset, c in enumerate(block) if c == linesep)
                index += len(block)
            else:
                break
                
    with open(index_filename, 'w') as f_index:
        f_index.write('\n'.join(map(str, line_indexes)))


def get_rows(count, index_filename, csv_filename):
    sys_random = random.SystemRandom()
    
    with open(index_filename) as f_index:
        line_indexes = list(map(int, f_index.read().splitlines()))

    row_count = len(line_indexes)
    
    with open(csv_filename) as f_csv:
        for _ in range(count):
            line_number = sys_random.randint(0, row_count-1)
            f_csv.seek(line_indexes[line_number])
            
            if line_number == row_count - 1:
                line = f_csv.read()
            else:
                line = f_csv.read(line_indexes[line_number + 1] - line_indexes[line_number])
            
            yield line_number, next(csv.reader(io.StringIO(line)))


index_filename = 'index.txt'
csv_filename = 'input.csv'

create_index(index_filename, csv_filename)  # only needed ONCE

for row_number, row in get_rows(10, index_filename, csv_filename):
    print(f"Row {row_number}  {row}")

同样的想法可以用于从随机起始行读取或以随机顺序读取。

显然来回查找不会像顺序读取文件那样快,但它应该比从头开始读取要快很多。

【讨论】:

    猜你喜欢
    • 2012-08-13
    • 2016-11-29
    • 2011-12-31
    • 1970-01-01
    • 2010-09-26
    • 2019-04-19
    • 2016-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多