Comment générer un total cumulé cumulé des nombres dans un fichier texte?


9

J'ai un fichier texte avec 2 millions de lignes. Chaque ligne a un entier positif. J'essaie de former une sorte de tableau de fréquences.

Fichier d'entrée:

3
4
5
8

La sortie doit être:

3
7
12
20

Comment dois-je procéder?


1
Dans votre texte, vous dites que vous voulez un tableau des fréquences . Votre échantillon de sortie est une liste. Pouvez-vous clarifier cela?
Wayne_Yux

En effet, votre sortie n'est pas une table de fréquences
don.joey

Je suis désolé. Je voulais dire une table de fréquences cumulées. Ont modifié la question. Merci.

Ce n'est pas très cool mais je fais généralement des trucs comme ça dans une feuille de calcul.
John U

@JohnU Je le fais habituellement, mais le fichier que j'ai possède 1 million de numéros.

Réponses:


20

Avec awk:

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

$0est la ligne actuelle. Donc, pour chaque ligne, je l'ajoute à la total, définit la ligne sur la nouvelle total, puis la fin 1est un raccourci awk - il imprime la ligne actuelle pour chaque condition vraie, et 1comme une condition est évaluée comme vraie.


Pourriez-vous expliquer votre code?
George Udosen

Le mot printpeut-il également être utilisé?
George Udosen

Oui, print total}au lieu de$0 = total}1
muru

1
@George ah, non.
muru

9
Une façon plus courte et peut-être plus compréhensible d'écrire le script awk serait{print(total += $0)}
Miles

9

Dans un 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")

Utiliser

  • Copiez le script dans un fichier vide, enregistrez-le sous add_last.py
  • Exécutez-le avec le fichier source et le fichier de sortie ciblé comme arguments:

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

Explication

Le code est plutôt lisible, mais en détail:

  • Ouvrir le fichier de sortie pour écrire les résultats

    with open(out, "wt") as wr:
    
  • Ouvrir le fichier d'entrée pour la lecture par ligne

    with open(f) as read:
        for l in read:
    
  • Lisez les lignes, en ajoutant la valeur de la nouvelle ligne au total:

    n = n + int(l)
    
  • Écrivez le résultat dans le fichier de sortie:

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


3
Il ne s'agit pas de brièveté ou de performances temporelles (un million de lignes n'est pas un big data). Le code dans votre réponse n'est pas Python idiomatique. Ma réponse est juste une version plus pythonique de la vôtre.
jfs

8
@JFSebastian si la version plus idiomatique est plus lente, pourquoi est-ce que quelqu'un la préférerait? Être «pythonique» n'a rien de spécial, c'est juste une convention qui aide les développeurs de python à partager du code et des normes de lisibilité. Si la version plus idiomatique est moins efficace (plus lente), elle ne devrait pas être utilisée sauf si vous travaillez dans un environnement où la normalisation est plus importante que la performance (ce qui me semble une horrible idée).
terdon

2
@terdon il y a quelque chose à dire sur l'optimisation prématurée. La lisibilité peut être importante en raison de la maintenabilité à long terme.
muru

4
@muru bien sûr, mais c'est parfaitement lisible. C'est seulement que le crime n'est pas "pythonique". Sans oublier que nous parlons de 7 lignes de code, pas d'un projet géant. Sacrifier l'efficacité au nom des conventions de style semble être la mauvaise approche.
terdon

9

Juste pour le fun

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

Cela fonctionne par un ppending +pà chaque ligne de l'entrée, et en faisant passer le résultat à la dccalculatrice où

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

puis

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

L' -e0argument pousse 0sur la dcpile pour initialiser la somme.


Quelque chose comme ça pourrait être le plus rapide sur un grand ensemble de données
Digital Trauma

@DigitalTrauma sur 1,3 million de lignes, en fait presque la plus lente:real 0m4.234s
Jacob Vlijm

le plaisir est tout ce dont il a besoin pour un vote positif: D excentrique suffit aussi: D: D
Rinzwind

Veuillez l'expliquer un peu.
AmanicA

8

Dans Bash:

#! /bin/bash

file="YOUR_FILE.txt"

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

bash est extrêmement lent à ce sujet: real 0m53.116sprès d'une minute, sur 1,3 million de lignes :)
Jacob Vlijm

@JacobVlijm dash est environ deux fois plus rapide, busybox ash et zsh (en mode sh) 1,5 fois, mais bien sûr, même dash est 5 fois plus lent que python.
muru

6

Pour imprimer une somme partielle d'entiers donnée sur l'entrée standard, une par ligne:

#!/usr/bin/env python3
import sys

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

Exemple exécutable .

Si pour une raison quelconque, la commande est trop lente; vous pouvez utiliser le programme 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;
}

Pour le construire et l'exécuter, tapez:

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

Exemple exécutable .

UINTMAX_MAX est 18446744073709551615 .

Le code C est plusieurs fois plus rapide que la commande awk sur ma machine pour le fichier d'entrée généré par:

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

2
Il peut également être utile de mentionner l' accumulate()itertool
David Z

5

Vous voulez probablement quelque chose comme ça:

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

Explication de la commande:

  • sort -n <filename> | uniq -c trie l'entrée et renvoie une table de fréquences
  • | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}' transforme la sortie en un format plus agréable

Exemple:
fichier d'entrée list.txt:

4
5
3
4
4
2
3
4
5

La commande:

$ 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

J'aime ça, le résultat est sympa
:)

5

Vous pouvez le faire dans vim. Ouvrez le fichier et saisissez les touches suivantes:

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

Notez que <C-a>c'est en fait ctrl-a, et <cr>est un retour chariot , c'est-à-dire le bouton Entrée.

Voici comment cela fonctionne. Tout d'abord, nous voulons effacer le registre «a» afin qu'il n'ait aucun effet secondaire la première fois. C'est tout simplement qaq. Ensuite, nous faisons ce qui suit:

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

Une fois cette macro récursive exécutée, nous appelons simplement :wq<cr>à enregistrer et à quitter.


1
+1 pour briser l'incantation magique et expliquer toutes les parties. Beaucoup trop rare autour de ces pièces.
John U

5

Perl one-liner:

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

Avec 2,5 millions de lignes de chiffres, il faut environ 6,6 secondes pour traiter:

$ 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, plutôt sympa.
Jacob Vlijm

@JacobVlijm qui est sur un assez petit fichier. J'ai ajouté un petit test avec un fichier de 2,5 millions de lignes. 6,64 secondes
Sergiy Kolodyazhnyy

1
J'ai exécuté 1,3 million de lignes sur un ancien système
Jacob Vlijm

3

Une simple doublure Bash:

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

x est la somme cumulée de tous les nombres de la ligne actuelle et au-dessus.
nest le numéro de la ligne actuelle.

Nous bouclons sur toutes les lignes nde INPUT_FILEet ajoutons leur valeur numérique à notre variablex et imprimons cette somme à chaque itération.

Bash est un peu lent ici cependant, vous pouvez vous attendre à ce que cela dure environ 20-30 secondes pour un fichier avec 2 millions d'entrées, sans imprimer la sortie sur la console (ce qui est encore plus lent, indépendamment de la méthode que vous utilisez).


3

Semblable à la réponse de @ steeldriver, mais avec un peu moins d'arcane à la bcplace:

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

La bonne chose à propos de bc(etdc ) est qu'ils sont des calculateurs de précision arbitraires, donc ils ne déborderont jamais ou ne souffriront pas d'un manque de précision sur les entiers.

L' sedexpression transforme l'entrée en:

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

Ceci est ensuite évalué par bc. La avariable bc est initialisée automatiquement à 0. Chaque ligne s'incrémente a, puis l'imprime explicitement.


real 0m5.642ssur 1,3 million de lignes. sed est vraiment lent à ce sujet.
Jacob Vlijm
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.