Jak wygenerować bieżącą sumę liczb w pliku tekstowym?

9

Mam plik tekstowy z 2 milionami linii. Każda linia ma dodatnią liczbę całkowitą. Próbuję utworzyć coś w rodzaju tabeli częstotliwości.

Plik wejściowy:

3
4
5
8

Dane wyjściowe powinny wynosić:

3
7
12
20

Jak mam to zrobić?

Monty Harder
źródło
1
W swoim tekście mówisz, że chcesz tabelę częstotliwości . Twoja próbka wyjściowa to lista. Czy możesz to wyjaśnić?
Wayne_Yux
Rzeczywiście, twój wynik nie jest tabelą częstotliwości
don.joey
Przepraszam. Miałem na myśli tabelę częstotliwości skumulowanych. Zmodyfikowałem pytanie. Dzięki.
Nie jest to zbyt fajne, ale zwykle robię takie rzeczy w arkuszu kalkulacyjnym.
John U
@JohnU Zwykle tak robię, ale plik, który mam, ma 1 milion numerów.

Odpowiedzi:

20

Z awk:

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

$0jest bieżącą linią. Tak więc dla każdej linii dodaję ją do total, ustawiam linię na nową total, a następnie trailing 1jest skrótem awk - drukuje bieżącą linię dla każdego prawdziwego warunku i 1jako warunek jest oceniany na prawdziwy.

muru
źródło
Czy mógłbyś wyjaśnić swój kod?
George Udosen
Czy printmożna również użyć tego słowa ?
George Udosen
Tak, print total}zamiast$0 = total}1
muru
1
@George ah, nie.
muru
9
Krótszym i być może bardziej zrozumiałym sposobem napisania skryptu awk byłby{print(total += $0)}
Miles
9

W skrypcie 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")

Używać

  • Skopiuj skrypt do pustego pliku i zapisz go jako add_last.py
  • Uruchom go jako plik źródłowy i docelowy plik wyjściowy jako argumenty:

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

Wyjaśnienie

Kod jest raczej czytelny, ale szczegółowo:

  • Otwórz plik wyjściowy do zapisywania wyników

    with open(out, "wt") as wr:
    
  • Otwórz plik wejściowy do odczytu według wiersza

    with open(f) as read:
        for l in read:
    
  • Przeczytaj wiersze, dodając wartość nowego wiersza do sumy:

    n = n + int(l)
    
  • Zapisz wynik w pliku wyjściowym:

    wr.write(str(n)+"\n")
    
Jacob Vlijm
źródło
3
Nie chodzi o krótkość ani wydajność czasową (miliony linii to nie duże zbiory danych). Kod w twojej odpowiedzi nie jest idiomatycznym Pythonem. Moja odpowiedź to po prostu twoja wersja pythonowa.
jfs
8
@JFSebastian, jeśli wersja bardziej idiomatyczna jest wolniejsza, dlaczego ktoś miałby ją preferować? Nie ma nic specjalnego w byciu „pythonowym”, to tylko konwencja, która pomaga programistom Pythona współdzielić kod i standardy czytelności. Jeśli wersja bardziej idiomatyczna jest mniej wydajna (wolniejsza), nie należy jej używać, chyba że pracujesz w środowisku, w którym standaryzacja jest ważniejsza niż wydajność (co wydaje mi się okropnym pomysłem).
terdon
2
@terdon jest coś, co można powiedzieć o przedwczesnej optymalizacji. Czytelność może być ważna ze względu na długoterminową łatwość konserwacji.
muru
4
@muru jasne, ale jest to doskonale czytelne. Jedynym przestępstwem nie jest bycie „pytonicznym”. Nie wspominając już o tym, że mówimy o 7 liniach kodu, a nie o jakimś wielkim projekcie. Poświęcenie wydajności w imię konwencji stylu wydaje się złym podejściem.
terdon
9

Dla żartu

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

Działa to przez ppending do każdej linii wejścia, a następnie przekazując wynik do kalkulatora gdzie+pdc

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

następnie

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

W -e0popycha argumentów 0na dcstosie, aby zainicjować sumę.

steeldriver
źródło
Coś takiego może być najszybsze w porównaniu z dużym zbiorem danych
Digital Trauma
@DigitalTrauma na 1,3 miliona linii, właściwie prawie najwolniejszy:real 0m4.234s
Jacob Vlijm
zabawa jest wszystkim, czego potrzeba, aby uzyskać entuzjazm: D dziwaczny też wystarczy: D: D
Rinzwind
Proszę wytłumacz to trochę.
AmanicA
8

W Bash:

#! /bin/bash

file="YOUR_FILE.txt"

TOTAL=0
while IFS= read -r line
do
    TOTAL=$(( TOTAL + line ))
    echo $TOTAL
done <"$file"
Julen Larrucea
źródło
bash jest wyjątkowo powolny: real 0m53.116sprawie minutę, na 1,3 miliona linii :)
Jacob Vlijm
@JacobVlijm dash jest około dwa razy szybszy, a zajęty ash i zsh (w trybie sh) 1,5 razy, ale oczywiście nawet myślnik jest 5 razy wolniejszy niż python.
muru
6

Aby wydrukować częściowe sumy liczb całkowitych podane na standardowym wejściu, po jednym w wierszu:

#!/usr/bin/env python3
import sys

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

Przykład możliwy do uruchomienia .

Jeśli z jakiegoś powodu polecenie jest zbyt wolne; możesz użyć programu 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;
}

Aby go zbudować i uruchomić, wpisz:

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

Przykład możliwy do uruchomienia .

UINTMAX_MAXjest 18446744073709551615.

Kod C jest kilka razy szybszy niż polecenie awk na moim komputerze dla pliku wejściowego wygenerowanego przez:

#!/usr/bin/env python3
import numpy.random
print(*numpy.random.random_integers(100, size=2000000), sep='\n')
jfs
źródło
2
Warto też wspomnieć o narzędziu accumulate()itert
David Z
5

Prawdopodobnie chcesz czegoś takiego:

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

Objaśnienie polecenia:

  • sort -n <filename> | uniq -c sortuje dane wejściowe i zwraca tabelę częstotliwości
  • | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}' zamienia wyjście w ładniejszy format

Przykład:
plik wejściowy list.txt:

4
5
3
4
4
2
3
4
5

Komenda:

$ 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
źródło
Podoba mi się, że ten out jest fajny
:)
5

Możesz to zrobić w vimie. Otwórz plik i wpisz następujące naciśnięcia klawiszy:

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

Zauważ, że <C-a>tak naprawdę to ctrl-a i <cr>to powrót karetki , tzn. Przycisk Enter.

Oto jak to działa. Po pierwsze, chcemy wyczyścić rejestr „a”, aby po raz pierwszy nie miał żadnych skutków ubocznych. To jest po prostu qaq. Następnie wykonujemy następujące czynności:

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

Po zakończeniu działania tego rekurencyjnego makra, po prostu wywołujemy, :wq<cr>aby zapisać i wyjść.

James
źródło
1
+1 za rozbicie magicznej inkantacji i wyjaśnienie wszystkich części. Zbyt rzadkie w tych częściach.
John U
5

Perl One-Liner:

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

Przy 2,5 milionach linii liczb przetworzenie zajmuje około 6,6 sekundy:

$ 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
źródło
real 0m0.908s, całkiem miły.
Jacob Vlijm
@JacobVlijm, który znajduje się na dość małym pliku. Dodałem mały test z plikiem 2,5 miliona linii. 6,64 sekundy
Sergiy Kolodyazhnyy
1
Uruchomiłem 1,3 miliona linii w starożytnym systemie
Jacob Vlijm
3

Prosta wyściółka Bash:

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

xto skumulowana suma wszystkich liczb z bieżącej linii i powyżej.
nto liczba w bieżącym wierszu.

Mamy pętla nad wszystkimi liniami nod INPUT_FILEi dodać ich wartość numeryczną do naszej zmiennej xi wydrukować tę sumę podczas każdej iteracji.

Bash jest tu jednak trochę powolny, możesz oczekiwać, że uruchomi się to około 20-30 sekund dla pliku z 2 milionami wpisów, bez drukowania danych wyjściowych na konsoli (co jest jeszcze wolniejsze, niezależnie od używanej metody).

Bajt Dowódca
źródło
3

Podobne do odpowiedzi @ steeldriver, ale z nieco mniej tajemnym bc:

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

Zaletą bc(i dc) jest to, że są one dowolnymi kalkulatorami precyzji, więc nigdy nie przepełnią się ani nie odczują braku precyzji w stosunku do liczb całkowitych.

sedWyrażenie przekształca dane wejściowe do:

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

Jest to następnie oceniane przez bc. aZmienna bc jest automatycznie inicjowany na 0. Każdy przyrosty liniowe a, a następnie wyraźnie drukuje go.

Cyfrowa trauma
źródło
real 0m5.642sna 1,3 mln linii. sed jest bardzo powolny.
Jacob Vlijm