Come posso generare un totale cumulativo corrente dei numeri in un file di testo?


9

Ho un file di testo con 2 milioni di righe. Ogni riga ha un numero intero positivo. Sto cercando di formare una specie di tabella delle frequenze.

File di input:

3
4
5
8

L'output dovrebbe essere:

3
7
12
20

Come procedo a fare questo?


1
Nel tuo testo, dici, che vuoi una tabella delle frequenze . L'esempio di output è un elenco. Potete per favore chiarire questo?
Wayne_Yux,

In effetti il ​​tuo output non è una tabella di frequenza
don.joey

Mi dispiace. Intendevo una tabella di frequenza cumulativa. Ho modificato la domanda. Grazie.

Non è molto bello, ma di solito faccio cose del genere in un foglio di calcolo.
John U,

@JohnU Di solito lo faccio, ma il file che ho ha 1 milione di numeri.

Risposte:


20

Con awk:

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

$0è la linea corrente. Quindi, per ogni riga, la aggiungo a total, imposto la riga sulla nuova totale quindi il trailing 1è un collegamento awk: stampa la riga corrente per ogni condizione vera e 1quando una condizione viene valutata vera.


Per favore, potresti spiegare il tuo codice?
George Udosen,

La parola può printessere usata anche?
George Udosen,

Sì, print total}invece di$0 = total}1
muru

1
@Gorge George, no.
Muru,

9
Un modo più breve e forse più comprensibile di scrivere la sceneggiatura di awk sarebbe{print(total += $0)}
Miles,

9

In uno script 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")

Usare

  • Copia lo script in un file vuoto, salvalo come add_last.py
  • Eseguilo con il file di origine e il file di output di destinazione come argomenti:

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

Spiegazione

Il codice è piuttosto leggibile, ma in dettaglio:

  • Apri il file di output per scrivere i risultati

    with open(out, "wt") as wr:
    
  • Apri il file di input per la lettura per riga

    with open(f) as read:
        for l in read:
    
  • Leggi le righe, aggiungendo il valore della nuova riga al totale:

    n = n + int(l)
    
  • Scrivi il risultato nel file di output:

    wr.write(str(n)+"\n")
    


3
Non si tratta di scarsità o di prestazioni nel tempo (milioni di linee non sono big data). Il codice nella tua risposta non è idiomatico Python. La mia risposta è solo la tua versione più pitonica.
jfs,

8
@JFSebastian se la versione più idiomatica è più lenta perché qualcuno dovrebbe preferirla? Non c'è niente di speciale nell'essere "pythonic" che è solo una convenzione che aiuta gli sviluppatori di Python a condividere codice e standard per la leggibilità. Se la versione più idiomatica è meno efficiente (più lenta), non dovrebbe essere utilizzata a meno che non si lavori in un ambiente in cui la standardizzazione è più importante delle prestazioni (che per me sembra un'idea orribile).
Terdon,

2
@terdon c'è qualcosa da dire sull'ottimizzazione prematura. La leggibilità può essere importante a causa della manutenibilità a lungo termine.
Muru,

4
@muru certo, ma questo è perfettamente leggibile. È solo un crimine non essere "pitonico". Per non parlare del fatto che stiamo parlando di 7 righe di codice, non di alcuni progetti giganti. Sacrificare l'efficienza in nome delle convenzioni di stile sembra un approccio sbagliato.
terdon,

9

Solo per divertimento

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

Questo funziona tramite una ppending +psu ciascuna riga dell'input e quindi passando il risultato al dccalcolatore dove

   +      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.

poi

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

L' -e0argomento viene inserito 0nello dcstack per inizializzare la somma.


Qualcosa del genere potrebbe effettivamente essere il più veloce in un set di dati di grandi dimensioni
Digital Trauma,

@DigitalTrauma su 1,3 milioni di linee, in realtà quasi la più lenta:real 0m4.234s
Jacob Vlijm,

il divertimento è tutto ciò di cui ha bisogno per un voto: D è anche troppo eccentrico: D: D
Rinzwind

Per favore, spiegalo un po '.
Amanic,

8

In Bash:

#! /bin/bash

file="YOUR_FILE.txt"

TOTAL=0
while IFS= read -r line
do
    TOTAL=$(( TOTAL + line ))
    echo $TOTAL
done <"$file"

bash è estremamente lento su questo: real 0m53.116squasi un minuto, su 1,3 milioni di linee :)
Jacob Vlijm,

Il trattino di @JacobVlijm è circa due volte più veloce, occupati ash e zsh (in modalità sh) 1,5 volte, ma ovviamente anche il trattino è 5 volte più lento di Python.
Muru,

6

Per stampare somme parziali di numeri interi fornite sull'input standard una per riga:

#!/usr/bin/env python3
import sys

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

Esempio eseguibile .

Se per qualche motivo il comando è troppo lento; potresti usare il programma 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;
}

Per crearlo ed eseguirlo, digitare:

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

Esempio eseguibile .

UINTMAX_MAXlo è 18446744073709551615.

Il codice C è più volte più veloce del comando awk sulla mia macchina per il file di input generato da:

#!/usr/bin/env python3
import numpy.random
print(*numpy.random.random_integers(100, size=2000000), sep='\n')

2
Potrebbe anche valere la pena ricordare l' accumulate()Itertool
David Z,

5

Probabilmente vuoi qualcosa del genere:

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

Spiegazione del comando:

  • sort -n <filename> | uniq -c ordina l'ingresso e restituisce una tabella di frequenza
  • | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}' trasforma l'output in un formato migliore

Esempio:
file di input list.txt:

4
5
3
4
4
2
3
4
5

Il 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

Mi piace questo il risultato è carino
:)

5

Puoi farlo in vim. Apri il file e digita i seguenti tasti:

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

Si noti che in <C-a>realtà è ctrl-a ed <cr>è il ritorno a capo , ovvero il pulsante di invio.

Ecco come funziona. Prima di tutto, vogliamo cancellare il registro 'a' in modo che non abbia effetti collaterali al primo passaggio. Questo è semplicemente qaq. Quindi facciamo quanto segue:

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

Dopo aver eseguito questa macro ricorsiva, chiamiamo semplicemente :wq<cr>per salvare ed uscire.


1
+1 per abbattere l'incantesimo magico e spiegare tutte le parti. Troppo raro intorno a queste parti.
John U,

5

Perl one-liner:

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

Con 2,5 milioni di righe di numeri, sono necessari circa 6,6 secondi per elaborare:

$ 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

real 0m0.908s, piuttosto bella.
Jacob Vlijm,

@JacobVlijm è su un file piuttosto piccolo. Ho aggiunto un piccolo test con 2,5 milioni di righe di file. 6,64 secondi
Sergiy Kolodyazhnyy,

1
Ho eseguito 1,3 milioni di linee su un sistema antico
Jacob Vlijm,

3

Una semplice linea Bash:

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

xè la somma cumulata di tutti i numeri dalla riga corrente e superiore.
nè il numero nella riga corrente.

Eseguiamo il ciclo su tutte le righe ndi INPUT_FILEe aggiungiamo il loro valore numerico alla nostra variabile xe stampiamo quella somma durante ogni iterazione.

Bash è un po 'lento qui, tuttavia, ci si può aspettare che funzioni circa 20-30 secondi per un file con 2 milioni di voci, senza stampare l'output sulla console (che è ancora più lento, indipendentemente dal metodo che si utilizza).


3

Simile alla risposta di @ steeldriver, ma con un po 'meno arcano bcinvece:

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

La cosa bella di bc(e dc) è che sono calcolatori di precisione arbitrari, quindi non trabocceranno mai o subiranno la mancanza di precisione sugli interi.

L' sedespressione trasforma l'input in:

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

Questo viene quindi valutato da bc. La avariabile bc viene inizializzata automaticamente su 0. Ogni riga viene incrementata a, quindi la stampa esplicitamente.


real 0m5.642ssu 1,3 milioni di linee. sed è molto lento su questo.
Jacob Vlijm,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.