¿Cómo genero un total acumulado acumulado de los números en un archivo de texto?

9

Tengo un archivo de texto con 2 millones de líneas. Cada línea tiene un entero positivo. Estoy tratando de formar un tipo de tabla de frecuencias.

Fichero de entrada:

3
4
5
8

La salida debe ser:

3
7
12
20

¿Cómo voy a hacer esto?

Monty Harder
fuente
1
En su texto, usted dice que quiere una tabla de frecuencias . Su muestra de salida es una lista. ¿Puedes por favor aclarar esto?
Wayne_Yux
De hecho, su salida no es una tabla de frecuencias
don.joey
Lo siento. Me refería a una tabla de frecuencia acumulativa. Han modificado la pregunta. Gracias.
No es muy bueno, pero generalmente solo hago cosas como esta en una hoja de cálculo.
John U
@JohnU Normalmente lo hago, pero el archivo que tengo tiene 1 millón de números.

Respuestas:

20

Con awk:

awk '{total += $0; $0 = total}1'

$0Es la línea actual. Entonces, para cada línea, lo agrego a total, establezco la línea en la nueva total, y luego el final 1es un atajo awk: imprime la línea actual para cada condición verdadera, y 1como condición se evalúa como verdadera.

muru
fuente
Por favor, ¿podría explicar su código?
George Udosen
¿Se printpuede usar la palabra también?
George Udosen
Sí, en print total}lugar de$0 = total}1
muru
1
@ George ah, no.
muru
99
Una forma más corta y quizás más comprensible de escribir el guión awk sería{print(total += $0)}
Miles
9

En un script de python:

#!/usr/bin/env python3
import sys

f = sys.argv[1]; out = sys.argv[2]

n = 0

with open(out, "wt") as wr:
    with open(f) as read:
        for l in read:
            n = n + int(l); wr.write(str(n)+"\n")

Usar

  • Copie el script en un archivo vacío, guárdelo como add_last.py
  • Ejecútelo con el archivo de origen y el archivo de salida de destino como argumentos:

    python3 /path/to/add_last.py <input_file> <output_file>
    

Explicación

El código es bastante legible, pero en detalle:

  • Abrir archivo de salida para escribir resultados

    with open(out, "wt") as wr:
    
  • Abrir archivo de entrada para leer por línea

    with open(f) as read:
        for l in read:
    
  • Lea las líneas y agregue el valor de la nueva línea al total:

    n = n + int(l)
    
  • Escriba el resultado en el archivo de salida:

    wr.write(str(n)+"\n")
    
Jacob Vlijm
fuente
3
No se trata de la brevedad o el rendimiento del tiempo (millones de líneas no son grandes datos). El código en su respuesta no es Python idiomático. Mi respuesta es solo una versión más pitón tuya.
jfs
8
@JFSebastian si la versión más idiomática es más lenta, ¿por qué alguien lo preferiría? No hay nada especial en ser "pitón", es solo una convención que ayuda a los desarrolladores de python a compartir código y estándares de legibilidad. Si la versión más idiomática es menos eficiente (más lenta), entonces no debería usarse a menos que esté trabajando en un entorno donde la estandarización es más importante que el rendimiento (lo cual me parece una idea horrible).
terdon
2
@terdon hay algo que decir sobre la optimización prematura. La legibilidad puede ser importante debido a la mantenibilidad a largo plazo.
muru
44
@muru seguro, pero esto es perfectamente legible. Es solo que el crimen no es ser "pitónico". Sin mencionar que estamos hablando de 7 líneas de código, no de un proyecto gigante. Sacrificar la eficiencia en nombre de las convenciones de estilo parece un enfoque equivocado.
terdon
9

Solo por diversión

$ sed 'a+p' file | dc -e0 -
3
7
12
20

Esto funciona mediante una pendiente +pa cada línea de la entrada, y luego pasando el resultado a la dccalculadora donde

   +      Pops two values off the stack, adds them, and pushes the result.
          The precision of the result is determined only by the values  of
          the arguments, and is enough to be exact.

entonces

   p      Prints  the  value on the top of the stack, without altering the
          stack.  A newline is printed after the value.

El -e0argumento empuja 0a la dcpila para inicializar la suma.

conductor de acero
fuente
Algo como esto podría ser el más rápido en un gran conjunto de datos
Digital Trauma
@DigitalTrauma en 1.3 millones de líneas, en realidad casi la más lenta:real 0m4.234s
Jacob Vlijm
la diversión es todo lo que necesita para un voto positivo: D peculiar también es suficiente: D: D
Rinzwind
Por favor explícalo un poco.
AmanicA
8

En Bash:

#! /bin/bash

file="YOUR_FILE.txt"

TOTAL=0
while IFS= read -r line
do
    TOTAL=$(( TOTAL + line ))
    echo $TOTAL
done <"$file"
Julen Larrucea
fuente
bash es extremadamente lento en esto:, real 0m53.116scasi un minuto, en 1.3 millones de líneas :)
Jacob Vlijm
@JacobVlijm dash es aproximadamente dos veces más rápido, busybox ash y zsh (en modo sh) 1,5 veces, pero, por supuesto, incluso dash es 5 veces más lento que python.
muru
6

Para imprimir sumas parciales de enteros dados en la entrada estándar, uno por línea:

#!/usr/bin/env python3
import sys

partial_sum = 0
for n in map(int, sys.stdin):
    partial_sum += n
    print(partial_sum)

Ejemplo ejecutable .

Si por alguna razón el comando es demasiado lento; podrías usar el programa C:

#include <stdint.h>
#include <ctype.h>
#include <stdio.h>

int main(void)
{
  uintmax_t cumsum = 0, n = 0;
  for (int c = EOF; (c = getchar()) != EOF; ) {
    if (isdigit(c))
      n = n * 10 + (c - '0');
    else if (n) { // complete number
      cumsum += n;
      printf("%ju\n", cumsum);
      n = 0;
    }
  }
  if (n)
    printf("%ju\n", cumsum + n);
  return feof(stdin) ? 0 : 1;
}

Para construirlo y ejecutarlo, escriba:

$ cc cumsum.c -o cumsum
$ ./cumsum < input > output

Ejemplo ejecutable .

UINTMAX_MAXes 18446744073709551615.

El código C es varias veces más rápido que el comando awk en mi máquina para el archivo de entrada generado por:

#!/usr/bin/env python3
import numpy.random
print(*numpy.random.random_integers(100, size=2000000), sep='\n')
jfs
fuente
2
También vale la pena mencionar el accumulate()itertool
David Z
5

Probablemente quieras algo como esto:

sort -n <filename> | uniq -c | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}'

Explicación del comando:

  • sort -n <filename> | uniq -c ordena la entrada y devuelve una tabla de frecuencias
  • | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}' convierte la salida en un formato más agradable

Ejemplo:
archivo de entrada list.txt:

4
5
3
4
4
2
3
4
5

El comando:

$ sort -n list.txt | uniq -c | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}'
Number  Frequency
2   1
3   2
4   4
5   2
Wayne_Yux
fuente
Me gusta esto, la salida es agradable
:)
5

Puedes hacer esto en vim. Abra el archivo y escriba las siguientes teclas:

qaqqayiwj@"<C-a>@aq@a:wq<cr>

Tenga en cuenta que en <C-a>realidad es ctrl-a, y <cr>es retorno de carro , es decir, el botón enter.

Así es como funciona esto. En primer lugar, queremos borrar el registro 'a' para que no tenga efectos secundarios la primera vez. Esto es simplemente qaq. Luego hacemos lo siguiente:

qa                  " Start recording keystrokes into register 'a'
  yiw               " Yank this current number
     j              " Move down one line. This will break the loop on the last line
      @"            " Run the number we yanked as if it was typed, and then
        <C-a>       " increment the number under the cursor *n* times
             @a     " Call macro 'a'. While recording this will do nothing
               q    " Stop recording
                @a  " Call macro 'a', which will call itself creating a loop

Después de que esta macro recursiva haya terminado de ejecutarse, simplemente llamamos :wq<cr>para guardar y salir.

James
fuente
1
+1 por romper el encantamiento mágico y explicar todas las partes. Demasiado raro alrededor de estas partes.
John U
5

Perl one-liner:

$ perl -lne 'print $sum+=$_' input.txt                                                                
3
7
12
20

Con 2.5 millones de líneas de números, el procesamiento tarda aproximadamente 6.6 segundos:

$ time perl -lne 'print $sum+=$_' large_input.txt > output.txt                                        
    0m06.64s real     0m05.42s user     0m00.09s system

$ wc -l large_input.txt
2500000 large_input.txt
Sergiy Kolodyazhnyy
fuente
real 0m0.908s, muy agradable.
Jacob Vlijm
@JacobVlijm que está en un archivo bastante pequeño. Agregué una pequeña prueba con un archivo de 2.5 millones de líneas. 6.64 segundos
Sergiy Kolodyazhnyy
1
Corrí 1.3 millones de líneas en un sistema antiguo
Jacob Vlijm
3

Un simple Bash de una sola línea:

x=0 ; while read n ; do x=$((x+n)) ; echo $x ; done < INPUT_FILE

xes la suma acumulada de todos los números de la línea actual y superior.
nes el número en la línea actual.

Nos bucle sobre todas las líneas nde INPUT_FILEy añadir su valor numérico para la variable xe imprimir esa suma durante cada iteración.

Sin embargo, Bash es un poco lento aquí, puede esperar que esto se ejecute alrededor de 20-30 segundos para un archivo con 2 millones de entradas, sin imprimir el resultado en la consola (que es aún más lento, independientemente del método que utilice).

Byte Commander
fuente
3

Similar a la respuesta de @ steeldriver, pero con un poco menos arcano en su bclugar:

sed 's/.*/a+=&;a/' input | bc

Lo bueno de bc(y dc) es que son calculadoras de precisión arbitrarias, por lo que nunca se desbordarán ni sufrirán falta de precisión sobre los enteros.

La sedexpresión transforma la entrada en:

a+=3;a
a+=4;a
a+=5;a
a+=8;a

Esto luego es evaluado por bc. La avariable bc se inicializa automáticamente a 0. Cada línea se incrementa a, luego la imprime explícitamente.

Trauma digital
fuente
real 0m5.642sen 1.3 millones de líneas. sed es muy lento en esto.
Jacob Vlijm