txt - Python cómo leer N número de líneas a la vez
python leer columnas (6)
Aquí hay otra forma de usar groupby :
from itertools import count, groupby
N = 16
with open(''test'') as f:
for g, group in groupby(f, key=lambda _, c=count(): c.next()/N):
print list(group)
Cómo funciona:
Básicamente groupby () agrupará las líneas por el valor de retorno del parámetro clave y el parámetro clave es la función lambda _, c=count(): c.next()/N
y utilizando el hecho de que el argumento c será obligado a count() cuando la función se definirá de modo que cada vez groupby()
llame a la función lambda y evalúe el valor de retorno para determinar el mero que agrupará las líneas de modo que:
# 1 iteration.
c.next() => 0
0 / 16 => 0
# 2 iteration.
c.next() => 1
1 / 16 => 0
...
# Start of the second grouper.
c.next() => 16
16/16 => 1
...
Estoy escribiendo un código para tomar un enorme archivo de texto (varios GB) N líneas a la vez, procesar ese lote, y pasar a las siguientes N líneas hasta que haya completado todo el archivo. (No me importa si el último lote no es del tamaño perfecto).
He estado leyendo sobre el uso de itertools islice para esta operación. Creo que estoy a mitad de camino:
from itertools import islice
N = 16
infile = open("my_very_large_text_file", "r")
lines_gen = islice(infile, N)
for lines in lines_gen:
...process my lines...
El problema es que me gustaría procesar el próximo lote de 16 líneas, pero me falta algo
Dado que se agregó el requisito de que haya una distribución estadísticamente uniforme de las líneas seleccionadas del archivo, ofrezco este enfoque simple.
"""randsamp - extract a random subset of n lines from a large file"""
import random
def scan_linepos(path):
"""return a list of seek offsets of the beginning of each line"""
linepos = []
offset = 0
with open(path) as inf:
# WARNING: CPython 2.7 file.tell() is not accurate on file.next()
for line in inf:
linepos.append(offset)
offset += len(line)
return linepos
def sample_lines(path, linepos, nsamp):
"""return nsamp lines from path where line offsets are in linepos"""
offsets = random.sample(linepos, nsamp)
offsets.sort() # this may make file reads more efficient
lines = []
with open(path) as inf:
for offset in offsets:
inf.seek(offset)
lines.append(inf.readline())
return lines
dataset = ''big_data.txt''
nsamp = 5
linepos = scan_linepos(dataset) # the scan only need be done once
lines = sample_lines(dataset, linepos, nsamp)
print ''selecting %d lines from a file of %d'' % (nsamp, len(linepos))
print ''''.join(lines)
Lo probé en un archivo de datos falso de 3 millones de líneas que comprendía 1,7 GB en el disco. scan_linepos
dominó el tiempo de ejecución, tardando unos 20 segundos en mi escritorio no tan caliente.
Para comprobar el rendimiento de sample_lines
, utilicé el módulo timeit
como tal
import timeit
t = timeit.Timer(''sample_lines(dataset, linepos, nsamp)'',
''from __main__ import sample_lines, dataset, linepos, nsamp'')
trials = 10 ** 4
elapsed = t.timeit(number=trials)
print u''%dk trials in %.2f seconds, %.2fµs per trial'' % (trials/1000,
elapsed, (elapsed/trials) * (10 ** 6))
Para varios valores de nsamp
; cuando nsamp
era 100, un solo sample_lines
completaba en 460μs y escalaba linealmente hasta 10k muestras a 47ms por llamada.
La siguiente pregunta natural es ¿Al azar es apenas aleatorio en absoluto? , y la respuesta es "subcriptográfica, pero sin duda adecuada para la bioinformática".
Función chunker usada de ¿Cuál es la forma más "pitónica" de iterar en una lista en fragmentos? :
from itertools import izip_longest
def grouper(iterable, n, fillvalue=None):
"grouper(3, ''ABCDEFG'', ''x'') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(*args, fillvalue=fillvalue)
with open(filename) as f:
for lines in grouper(f, chunk_size, ""): #for every chunk_sized chunk
"""process lines like
lines[0], lines[1] , ... , lines[chunk_size-1]"""
La pregunta parece presumir que se puede ganar eficiencia leyendo un "enorme archivo de texto" en bloques de N líneas a la vez. Esto agrega una capa de aplicación de almacenamiento en búfer sobre la biblioteca stdio
ya altamente optimizada, agrega complejidad y probablemente no le compre absolutamente nada.
Así:
with open(''my_very_large_text_file'') as f:
for line in f:
process(line)
es probablemente superior a cualquier alternativa en tiempo, espacio, complejidad y legibilidad.
Ver también las primeras dos reglas de Rob Pike , Jackson''s Two Rules , y PEP-20 The Zen of Python . Si realmente solo quisieras jugar con islice
, deberías haber islice
las cosas del archivo grande.
Suponiendo que "lote" significa querer procesar los 16 recs a la vez en lugar de individualmente, lea el archivo un registro a la vez y actualice un contador; cuando el contador llegue a 16, procesa ese grupo.
process_list(interim_list)interim_list = []
infile = open("my_very_large_text_file", "r")
ctr = 0
for rec in infile:
interim_list.append(rec)
ctr += 1
if ctr > 15:
process_list(interim_list)
interim_list = []
ctr = 0
the final group
islice()
se puede usar para obtener los siguientes n
elementos de un iterador. Por lo tanto, list(islice(f, n))
devolverá una lista de las siguientes n
líneas del archivo f
. Usar esto dentro de un bucle le dará el archivo en fragmentos de n
líneas. Al final del archivo, la lista puede ser más corta y, finalmente, la llamada devolverá una lista vacía.
with open(...) as f:
while True:
next_n_lines = list(islice(f, n))
if not next_n_lines:
break
# process next_n_lines
Una alternativa es usar el patrón de mero :
with open(...) as f:
for next_n_lines in izip_longest(*[f] * n):
# process next_n_lines