Wie generiere ich eine laufende kumulative Summe der Zahlen in einer Textdatei?

9

Ich habe eine Textdatei mit 2 Millionen Zeilen. Jede Zeile hat eine positive ganze Zahl. Ich versuche so etwas wie eine Frequenztabelle zu bilden.

Eingabedatei:

3
4
5
8

Die Ausgabe sollte sein:

3
7
12
20

Wie mache ich das?

Monty Harder
quelle
1
In Ihrem Text, sagen Sie, dass Sie eine Frequenz wollen Tabelle . Ihr Ausgabebeispiel ist eine Liste. Können Sie das bitte klarstellen?
Wayne_Yux
In der Tat ist Ihre Ausgabe keine Häufigkeitstabelle
don.joey
Es tut mir Leid. Ich meinte eine kumulative Häufigkeitstabelle. Habe die Frage geändert. Vielen Dank.
Es ist nicht sehr cool, aber ich mache normalerweise nur solche Sachen in einer Tabelle.
John U
@ JohnU mache ich normalerweise, aber die Datei, die ich habe, hat 1 Million Nummern.

Antworten:

19

Mit awk:

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

$0ist die aktuelle Zeile. Also füge ich es für jede Zeile hinzu total, setze die Zeile auf die neue totalund dann ist das Trailing 1eine awk-Verknüpfung - es druckt die aktuelle Zeile für jede wahre Bedingung und wird 1als Bedingung als wahr ausgewertet.

muru
quelle
Könnten Sie bitte Ihren Code erklären?
George Udosen
Kann das Wort printauch verwendet werden?
George Udosen
Ja, print total}statt$0 = total}1
muru
1
@ George ah, nein.
Muru
9
Eine kürzere und vielleicht verständlichere Art, das awk-Skript zu schreiben, wäre{print(total += $0)}
Miles
9

In einem Python-Skript:

#!/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")

Benutzen

  • Kopieren Sie das Skript in eine leere Datei und speichern Sie es unter add_last.py
  • Führen Sie es mit der Quelldatei und der Zielausgabedatei als Argumente aus:

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

Erläuterung

Der Code ist ziemlich lesbar, aber im Detail:

  • Öffnen Sie die Ausgabedatei, um Ergebnisse zu schreiben

    with open(out, "wt") as wr:
    
  • Öffnen Sie die Eingabedatei zum Lesen pro Zeile

    with open(f) as read:
        for l in read:
    
  • Lesen Sie die Zeilen und addieren Sie den Wert der neuen Zeile zur Gesamtsumme:

    n = n + int(l)
    
  • Schreiben Sie das Ergebnis in die Ausgabedatei:

    wr.write(str(n)+"\n")
    
Jacob Vlijm
quelle
3
Es geht nicht um Kürze oder Zeitleistung (Millionen Zeilen sind keine Big Data). Der Code in Ihrer Antwort ist kein idiomatisches Python. Meine Antwort ist nur eine pythonischere Version von dir.
JFS
8
@JFSebastian Wenn die idiomatischere Version langsamer ist, warum sollte es jemand bevorzugen? Es ist nichts Besonderes daran, "pythonisch" zu sein. Dies ist nur eine Konvention, die Python-Entwicklern hilft, Code und Standards für die Lesbarkeit gemeinsam zu nutzen. Wenn die idiomatischere Version weniger effizient (langsamer) ist, sollte sie nur verwendet werden, wenn Sie in einer Umgebung arbeiten, in der Standardisierung wichtiger ist als Leistung (was für mich nach einer schrecklichen Idee klingt).
Terdon
2
@terdon es gibt etwas zu sagen über vorzeitige Optimierung. Die Lesbarkeit kann aufgrund der langfristigen Wartbarkeit wichtig sein.
Muru
4
@ Guru sicher, aber das ist perfekt lesbar. Es ist nur Verbrechen, nicht "pythonisch" zu sein. Ganz zu schweigen davon, dass es sich um 7 Codezeilen handelt, nicht um ein riesiges Projekt. Effizienz im Namen von Stilkonventionen zu opfern, scheint der falsche Ansatz zu sein.
Terdon
9

Nur zum Spaß

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

Dies funktioniert , indem eine ppending +pan jede Zeile der Eingabe, und dann vorbei das Ergebnis an den dcRechner , wo

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

dann

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

Das -e0Argument wird 0auf den dcStapel verschoben, um die Summe zu initialisieren.

Steeldriver
quelle
So etwas könnte tatsächlich das schnellste über einen großen Datensatz sein
Digital Trauma
@ DigitalTrauma auf 1,3 Millionen Zeilen, eigentlich fast die langsamste:real 0m4.234s
Jacob Vlijm
Spaß ist alles, was es für eine Gegenstimme braucht: D schrullig ist auch genug: D: D
Rinzwind
Bitte erklären Sie es ein wenig.
AmanicA
8

In Bash:

#! /bin/bash

file="YOUR_FILE.txt"

TOTAL=0
while IFS= read -r line
do
    TOTAL=$(( TOTAL + line ))
    echo $TOTAL
done <"$file"
Julen Larrucea
quelle
Bash ist extrem langsam: real 0m53.116sfast eine Minute, auf 1,3 Millionen Zeilen :)
Jacob Vlijm
@JacobVlijm Dash ist ungefähr doppelt so schnell, Busybox Ash und Zsh (im Sh-Modus) 1,5-mal, aber natürlich ist sogar Dash 5-mal langsamer als Python.
Muru
6

So drucken Sie Teilsummen von Ganzzahlen, die auf der Standardeingabe angegeben sind, eine pro Zeile:

#!/usr/bin/env python3
import sys

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

Ausführbares Beispiel .

Wenn der Befehl aus irgendeinem Grund zu langsam ist; Sie könnten das C-Programm verwenden:

#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;
}

Geben Sie Folgendes ein, um es zu erstellen und auszuführen:

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

Ausführbares Beispiel .

UINTMAX_MAXist 18446744073709551615.

Der C-Code ist um ein Vielfaches schneller als der Befehl awk auf meinem Computer für die Eingabedatei, die generiert wird von:

#!/usr/bin/env python3
import numpy.random
print(*numpy.random.random_integers(100, size=2000000), sep='\n')
jfs
quelle
2
Es kann auch erwähnenswert sein, das accumulate()itertool
David Z
5

Sie möchten wahrscheinlich so etwas:

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

Erläuterung des Befehls:

  • sort -n <filename> | uniq -c sortiert die Eingabe und gibt eine Frequenztabelle zurück
  • | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}' verwandelt den Ausgang in ein schöneres Format

Beispiel:
Eingabedatei list.txt:

4
5
3
4
4
2
3
4
5

Der Befehl:

$ 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
quelle
Ich mag das, die
Ausgabe
5

Sie können dies in vim tun. Öffnen Sie die Datei und geben Sie die folgenden Tastenanschläge ein:

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

Beachten Sie, dass <C-a>tatsächlich ctrl-a ist, und <cr>ist Carriage Return , dh die Enter - Taste.

So funktioniert das Zunächst möchten wir das Register 'a' löschen, damit es beim ersten Mal keine Nebenwirkungen hat. Das ist einfach qaq. Dann machen wir folgendes:

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

Nachdem dieses rekursive Makro ausgeführt wurde, rufen wir einfach :wq<cr>zum Speichern und Beenden auf.

James
quelle
1
+1 für das Aufbrechen der magischen Beschwörung und das Erklären aller Teile. Viel zu selten um diese Teile.
John U
5

Perl Einzeiler:

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

Bei 2,5 Millionen Zahlenzeilen dauert die Verarbeitung etwa 6,6 Sekunden:

$ 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
quelle
real 0m0.908s, ganz nett.
Jacob Vlijm
@JacobVlijm das ist in einer ziemlich kleinen Datei. Ich habe einen kleinen Test mit einer Datei mit 2,5 Millionen Zeilen hinzugefügt. 6,64 Sekunden
Sergiy Kolodyazhnyy
1
Ich lief 1,3 Millionen Zeilen auf einem alten System
Jacob Vlijm
3

Ein einfacher Bash Einzeiler:

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

xist die kumulierte Summe aller Zahlen aus der aktuellen Zeile und darüber.
nist die Nummer in der aktuellen Zeile.

Wir durchlaufen alle Zeilen nvon INPUT_FILEund addieren ihren numerischen Wert zu unserer Variablen xund drucken diese Summe während jeder Iteration.

Bash ist hier allerdings etwas langsam. Sie können davon ausgehen, dass dies für eine Datei mit 2 Millionen Einträgen etwa 20 bis 30 Sekunden dauert, ohne dass die Ausgabe auf die Konsole gedruckt wird (was unabhängig von der verwendeten Methode sogar noch langsamer ist).

Byte Commander
quelle
3

Ähnlich wie bei @ steeldrivers Antwort, jedoch mit dem etwas weniger arkanen bc:

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

Das Schöne an bc(und dc) ist, dass es sich um willkürliche Präzisionsrechner handelt, die also niemals überlaufen oder bei ganzen Zahlen an Präzision leiden.

Der sedAusdruck transformiert die Eingabe in:

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

Dies wird dann von ausgewertet bc. Die aVariable bc wird automatisch auf 0 initialisiert. Jede Zeile wird inkrementiert aund dann explizit gedruckt.

Digitales Trauma
quelle
real 0m5.642sauf 1,3 Millionen Zeilen. sed ist wirklich langsam dabei.
Jacob Vlijm