Supongamos que tengo un archivo (llámelo sample.txt) que se ve así:
Row1,10
Row2,20
Row3,30
Row4,40
Quiero poder trabajar en una secuencia de este archivo que es esencialmente la combinación por pares de las cuatro filas (por lo que deberíamos terminar con 16 en total). Por ejemplo, estoy buscando un comando de transmisión (es decir, eficiente) donde la salida es:
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40
Mi caso de uso es que quiero transmitir esta salida a otro comando (como awk) para calcular alguna métrica sobre esta combinación por pares.
Tengo una manera de hacer esto en awk, pero mi preocupación es que mi uso del bloque END {} significa que básicamente estoy almacenando todo el archivo en la memoria antes de la salida. Código de ejemplo:
awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
¿Existe una forma eficiente de transmisión para hacer esto sin tener que almacenar esencialmente el archivo en la memoria y luego emitirlo en el bloque END?
fuente
Respuestas:
Aquí se explica cómo hacerlo en awk para que no tenga que almacenar todo el archivo en una matriz. Este es básicamente el mismo algoritmo que el de terdon.
Si lo desea, incluso puede darle varios nombres de archivo en la línea de comando y procesará cada archivo de forma independiente, concatenando los resultados juntos.
En mi sistema, esto se ejecuta en aproximadamente 2/3 del tiempo de la solución perl de terdon.
fuente
No estoy seguro de que esto sea mejor que hacerlo en la memoria, pero con un
sed
quer
borra su archivo para cada línea en su archivo y otro en el otro lado de una tubería que alterna elH
espacio antiguo con líneas de entrada ...SALIDA
Hice esto de otra manera. Almacena algunos en la memoria, almacena una cadena como:
... para cada línea en el archivo.
Es muy rápido. Es
cat
el archivo tantas veces como haya líneas en el archivo a|pipe
. En el otro lado de la tubería, esa entrada se fusiona con el archivo en sí tantas veces como haya líneas en el archivo.El
case
material es sólo para la portabilidad -yash
yzsh
tanto un elemento añadir a la división, mientrasmksh
yposh
tanto uno perder.ksh
,dash
,busybox
, Ybash
toda división a cabo exactamente como muchos campos, ya que hay ceros como impreso porprintf
. Como está escrito, lo anterior genera los mismos resultados para cada uno de los shells mencionados anteriormente en mi máquina.Si el archivo es muy largo, puede haber
$ARGMAX
problemas con demasiados argumentos, en cuyo caso necesitaría introducirxargs
o similar también.Dada la misma entrada que usé antes de que la salida sea idéntica. Pero, si fuera más grande ...
Eso genera un archivo casi idéntico al que usé antes (sin 'Fila') , pero con 1000 líneas. Puedes ver por ti mismo lo rápido que es:
A 1000 líneas hay una ligera variación en el rendimiento entre los shells,
bash
es invariablemente el más lento, pero debido a que el único trabajo que hacen de todos modos es generar la cadena arg (1000 copias defilename -
) el efecto es mínimo. La diferencia en el rendimiento entrezsh
- como arriba - ybash
es la centésima de segundo aquí.Aquí hay otra versión que debería funcionar para un archivo de cualquier longitud:
Crea un enlace suave a su primer argumento
/tmp
con un nombre semi-aleatorio para que no se obsesione con nombres de archivos extraños. Eso es importante porquecat
los args se alimentan a través de una tuberíaxargs
.cat
La salida de se guarda en<&3
mientrassed
p
borra cada línea en el primer argumento tantas veces como haya líneas en ese archivo, y su script también se alimenta a través de una tubería. Nuevamentepaste
fusiona su entrada, pero esta vez solo toma dos argumentos-
nuevamente para su entrada estándar y el nombre del enlace/dev/fd/3
.Ese último, el
/dev/fd/[num]
enlace, debería funcionar en cualquier sistema Linux y muchos más, pero si no crea una tubería con nombremkfifo
y usarlo, también debería funcionar.Lo último que hace es
rm
el enlace suave que crea antes de salir.Esta versión es realmente más rápida aún en mi sistema. Supongo que es porque aunque ejecuta más aplicaciones, comienza a entregarles sus argumentos de inmediato, mientras que antes los apilaba primero.
fuente
ctrl+v; ctrl+j
para obtener nuevas líneas como lo hago.. ./file; fn_name
en ese caso y en ese caso.Bueno, siempre puedes hacerlo en tu caparazón:
Es mucho más lento que su
awk
solución (en mi máquina, tardó ~ 11 segundos para 1000 líneas, versus ~ 0.3 segundosawk
), pero al menos nunca tiene más de un par de líneas en la memoria.El bucle anterior funciona para los datos muy simples que tiene en su ejemplo. Se ahogará con las barras invertidas y comerá espacios finales y principales. Una versión más robusta de lo mismo es:
Otra opción es usar
perl
en su lugar:La secuencia de comandos anterior leerá cada línea del archivo de entrada (
-ln
), la guardará como$l
, se abrirásample.txt
nuevamente e imprimirá cada línea junto con$l
. El resultado son todas las combinaciones por pares, mientras que solo 2 líneas se almacenan en la memoria. En mi sistema, eso tomó solo unos0.6
segundos en 1000 líneas.fuente
echo
pudiera haber un problema. Lo que había escrito (agreguéprintf
ahora) debería funcionar con todos ellos, ¿verdad? En cuanto alwhile
bucle, ¿por qué? ¿Qué tiene de malowhile read f; do ..; done < file
? ¡Seguramente no estás sugiriendo unfor
bucle! ¿Cuál es la otra alternativa?Con
zsh
:$^a
en una matriz activa la expansión similar a una llave (como en{elt1,elt2}
) para la matriz.fuente
Puede compilar este código de C ++ para obtener resultados bastante rápidos.
Se completa en alrededor de 0.19 - 0.27 segundos en un archivo de 1000 líneas.
Actualmente lee
10000
líneas en la memoria (para acelerar la impresión en la pantalla) que si tuviera1000
caracteres por línea usaría menos de la10mb
memoria, lo que no creo que sea un problema. Sin embargo, puede eliminar esa sección por completo e imprimir directamente en la pantalla si causa un problema.Puede compilar usando
g++ -o "NAME" "NAME.cpp"
Where
NAME
es el nombre del archivo para guardarlo yNAME.cpp
es el archivo en el que se guarda este códigoCTEST.cpp:
Demostración
fuente
El campo 2 está vacío e igual para todos los elementos en file.txt, por
join
lo que concatenará cada elemento con todos los demás: de hecho, está calculando el producto cartesiano.fuente
Una opción con Python es mapear el archivo en memoria y aprovechar el hecho de que la biblioteca de expresiones regulares de Python puede trabajar directamente con archivos mapeados en memoria. Aunque esto tiene la apariencia de ejecutar bucles anidados sobre el archivo, la asignación de memoria asegura que el sistema operativo ponga en juego la RAM física disponible de manera óptima
Alternativamente, una solución rápida en Python, aunque la eficiencia de la memoria podría ser una preocupación.
fuente
En bash, ksh debería funcionar también, utilizando solo los componentes integrados de shell:
Tenga en cuenta que si bien esto contiene todo el archivo en la memoria en una variable de shell, solo necesita un único acceso de lectura.
fuente
sed
solución.Explicación:
sed 'r file2' file1
- lea todo el contenido del archivo2 para cada línea del archivo1.1~i
significa la 1ª línea, luego la línea 1 + i, 1 + 2 * i, 1 + 3 * i, etc. Por lo tanto,1~$((line_num + 1)){h;d}
significa lah
antigua línea puntiaguda al búfer,d
elegir el espacio del patrón y comenzar un nuevo ciclo.'G;s/(.*)\n(.*)/\2 \1/'
- para todas las líneas, excepto las seleccionadas en el paso anterior, haga lo siguiente:G
et line from hold buffer y añádalo a la línea actual. Luego intercambie lugares de líneas. Eracurrent_line\nbuffer_line\n
, se convirtióbuffer_line\ncurrent_line\n
Salida
fuente