2011-01-06 16 views
20

Todo está en el título. Me pregunto si alguien sabe que una memoria rápida y razonable requiere una forma aleatoria de mezclar todas las líneas de un archivo de 3 millones de líneas. Supongo que no es posible con un simple comando vim, por lo que cualquier script simple usando Python. Intenté con Python usando un generador de números aleatorios, pero no logré encontrar una salida simple.Mezcla aleatoriamente líneas de 3 millones de archivos de línea

+2

se puede ver [esta cuestión] (http://stackoverflow.co m/questions/1287567/c-is-using-random-and-orderby-a-good-shuffle-algorithm) para algunas ideas. –

+1

"no pude encontrar una salida simple". De Verdad? Por favor, publique el código que se volvió demasiado complejo. –

+0

Debería haber dicho, "no logró encontrar una salida". Soy bastante nuevo con Python, así que solo sé algunos comandos. A lo que me dirigía era a poner todo en un vector, elegir un número aleatorio entre 1 y 3 millones, sacar esa línea y comenzar de nuevo con un nuevo número aleatorio con una condición adicional que excluye los números aleatorios anteriores. Etc. De ahí mi pregunta de una manera simple (que usted y otros proporcionaron). Aceptaré la tuya ya que tienes más votos. Gracias a todos, aunque ... ¡aprendí mucho! – Nigu

Respuesta

21
import random 
with open('the_file','r') as source: 
    data = [ (random.random(), line) for line in source ] 
data.sort() 
with open('another_file','w') as target: 
    for _, line in data: 
     target.write(line) 

Eso debería hacerlo. 3 millones de líneas caben en la memoria de la mayoría de las máquinas a menos que las líneas sean ENORMES (más de 512 caracteres).

+0

3 millones de líneas con un promedio de 80 caracteres por línea será de 240M Bytes, lo cual es enorme para cargar un archivo en la memoria. –

+1

@ Vikram.exe. Realmente no. Esta máquina tiene 4Gb de memoria. 240M no es nada. –

+0

@ S.Lott, sí, estoy de acuerdo en que no es nada, pero me preguntaba si podemos hacerlo de alguna forma (con poco esfuerzo) sin cargar todo el archivo en la memoria. –

34

Toma sólo unos pocos segundos en Python:

>>> import random 
>>> lines = open('3mil.txt').readlines() 
>>> random.shuffle(lines) 
>>> open('3mil.txt', 'w').writelines(lines) 
+1

Esto no funciona. 'shuffle' solo funciona para listas relativamente pequeñas, aproximadamente 2,000 elementos o menos. También puede no tener demandas de memoria "razonables", dependiendo de la longitud de las líneas. Ahora, si solo necesita un pedido "aleatorio", quizás esto sea suficiente. Pero tal vez no. Vea http://stackoverflow.com/questions/3062741/maximal-length-of-list-to-shuffle-with-python-random-shuffle para más detalles. –

+6

Sin duda * does * work, y funciona bien. Que solo puede generar 2 ** 19937 permutaciones es trivial que bordea lo irrelevante. Cualquier barajado basado en RNG tendrá la misma "limitación". –

+2

¿Cómo es una solución basada en 'sort()' mejor que 'shuffle()'? No evita este supuesto problema. –

3

En muchos sistemas el comando shell sort toma -R selecciona aleatoriamente a su entrada.

+2

Tenga en cuenta que la opción '-R' seguirá ordenando líneas idénticas, lo que puede no ser el comportamiento deseado. –

+3

'shuf' aleatorizará líneas sin tener en cuenta la igualdad, y es quizás la solución más rápida – fuzzyTew

2

Aquí hay otra versión

En la carcasa, utilízalo.

python decorate.py | sort | python undecorate.py 

decorate.py

import sys 
import random 
for line in sys.stdin: 
    sys.stdout.write("{0}|{1}".format(random.random(), line)) 

undecorate.py

import sys 
for line in sys.stdin: 
    _, _, data= line.partition("|") 
    sys.stdout.write(line) 

casi no utiliza la memoria.

+0

Como se publicó anteriormente, 'sort -R' ordena por clave aleatoria. Más fácil que decorar y decodificar el archivo. –

+0

@Chris B. Como mencionaste anteriormente, '-R' aún agrupará líneas idénticas. Esto no lo hará. Entonces, si ese es el comportamiento deseado, entonces este es el camino a seguir. – aaronasterling

+1

Como fuzzyTew señaló anteriormente,' shuf' aleatorizará líneas con cada permutación igualmente probable, y no requiere código personalizado. Eso es cl mucho mejor que escribir y depurar su propio programa. –

1

Esto es lo mismo que el Sr. Kugelman de, pero el uso de vim interfaz integrada de Python:

:py import vim, random as r; cb = vim.current.buffer ; l = cb[:] ; r.shuffle(l) ; cb[:] = l 
1

Si lo hace no quieren cargar todo en la memoria y ordenarla allí, tienen para almacenar las líneas en el disco mientras haces la clasificación aleatoria. Eso será muy lento.

Aquí hay una versión muy simple, estúpida y lenta. Tenga en cuenta que esto puede tomar una cantidad sorprendente de espacio de disco, y será muy lento. Lo ejecuté con 300,000 líneas, y lleva varios minutos. 3 millones de líneas bien podrían tomar una hora. Entonces: hazlo en la memoria. De Verdad. No es tan grande.

import os 
import tempfile 
import shutil 
import random 
tempdir = tempfile.mkdtemp() 
print tempdir 

files = [] 
# Split the lines: 
with open('/tmp/sorted.txt', 'rt') as infile: 
    counter = 0  
    for line in infile: 
     outfilename = os.path.join(tempdir, '%09i.txt' % counter) 
     with open(outfilename, 'wt') as outfile: 
      outfile.write(line) 
     counter += 1 
     files.append(outfilename) 

with open('/tmp/random.txt', 'wt') as outfile: 
    while files: 
     index = random.randint(0, len(files) - 1) 
     filename = files.pop(index) 
     outfile.write(open(filename, 'rt').read()) 

shutil.rmtree(tempdir) 

Otra versión sería almacenar los archivos en una base de datos SQLite y extraer las líneas al azar de esa base de datos. Eso probablemente sea más rápido que esto.

+0

"¿Eso será muy lento"? Más lento sí. Muy lento es discutible. Cada paso individual es bastante rápido. –

+1

@ S.Lott: Bueno, depende del sistema de archivos. Usé ext3. 30,000 elementos tomaron 5.5 segundos. 100.000 elementos tomaron 16.3 segundos. 200,000 elementos toman 339 segundos. Creo que la búsqueda en el directorio se vuelve lenta con muchos elementos. 3 millones de elementos tomarán * horas *. Al menos. Una base de datos puede ser razonablemente rápida, pero no puedo molestarme en probarla. :-) Otra opción sería leer el archivo y hacer un índice sobre la posición de inicio de cada elemento, y buscar seek() s. Eso debería ser más rápido que esto. –

+0

Datos interesantes. Supongo que he pasado demasiado tiempo usando servidores muy grandes. –

12

Acabo de probar esto en un archivo con 4.3M de líneas y lo más rápido fue el comando 'shuf' en Linux. Úselo así:

shuf huge_file.txt -o shuffled_lines_huge_file.txt 

Tardó 2-3 segundos para terminar.

0

Aquí es otra manera usando random.choice, esto puede proporcionar alguna memoria gradual aliviar también, pero con un peor Big-O :)

from random import choice 

with open('data.txt', 'r') as r: 
    lines = r.readlines() 

with open('shuffled_data.txt', 'w') as w: 
    while lines: 
     l = choice(lines) 
     lines.remove(l) 
     w.write(l) 
+0

"un mejor Big-O" <- Desafortunadamente no :-(. La eliminación repetida en 'lines.remove (l)' le da a su algoritmo un tiempo de funcionamiento que es cuadrático en el número de líneas. Será inutilizable (tiempo de ejecución) de horas a días) para un archivo de 3 millones de líneas. –

+0

Vaya, tiene razón :-) simplemente lo arregló –

0

Los siguientes Vimscript puede ser utilizado para intercambiar líneas:

function! Random()              
    let nswaps = 100              
    let firstline = 1              
    let lastline = 10              
    let i = 0                
    while i <= nswaps              
    exe "let line = system('shuf -i ".firstline."-".lastline." -n 1')[:-2]" 
    exe line.'d'               
    exe "let line = system('shuf -i ".firstline."-".lastline." -n 1')[:-2]" 
    exe "normal! " . line . 'Gp'           
    let i += 1               
    endwhile                
endfunction 

Seleccione la función en modo visual y escriba :@" luego ejecutarlo con :call Random()

Cuestiones relacionadas