J'ai un fichier texte avec 2 millions de lignes. Chaque ligne a un entier positif. J'essaie de former une sorte de table de fréquences.
Fichier d'entrée:
3
4
5
8
La sortie devrait être:
3
7
12
20
Comment puis-je faire cela?
Avec awk
:
awk '{total += $0; $0 = total}1'
$0
est la ligne en cours. Donc, pour chaque ligne, je l’ajoute à total
, la définit à la nouvelle total
, puis le 1
suivant est un raccourci awk - il affiche la ligne actuelle pour chaque condition vraie et 1
en tant que condition évaluée à true.
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")
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>
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)
Ecrivez le résultat dans le fichier de sortie:
wr.write(str(n)+"\n")
Juste pour le fun
$ sed 'a+p' file | dc -e0 -
3
7
12
20
Cela fonctionne en a en ajoutant +p
à chaque ligne de l'entrée, puis en transmettant le résultat à la calculatrice dc
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'argument -e0
pousse 0
dans la pile dc
pour initialiser la somme.
Dans Bash:
#! /bin/bash
file="YOUR_FILE.txt"
TOTAL=0
while IFS= read -r line
do
TOTAL=$(( TOTAL + line ))
echo $TOTAL
done <"$file"
Pour imprimer des sommes partielles d'entiers données 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)
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
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')
Vous pouvez le faire dans vim. Ouvrez le fichier et tapez les frappes suivantes:
qaqqayiwj@"<C-a>@aq@a:wq<cr>
Notez que <C-a>
est en fait ctrl-a et <cr>
est 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 lors de la première utilisation. C'est 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 que cette macro récursive est exécutée, nous appelons simplement :wq<cr>
pour enregistrer et quitter.
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éableExemple:
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
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
Un simple Bash one-liner:
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 en cours et plus.n
est le numéro de la ligne en cours.
Nous parcourons toutes les lignes n
de INPUT_FILE
et ajoutons leur valeur numérique à notre variable x
et affichons cette somme à chaque itération.
Bash est un peu lent ici, mais vous pouvez vous attendre à environ 20-30 secondes pour un fichier de 2 millions d’entrées, sans imprimer le résultat sur la console (ce qui est encore plus lent, quelle que soit la méthode utilisée).
Semblable à la réponse de @ steeldriver, mais avec le nom un peu moins arcanique bc
name__:
sed 's/.*/a+=&;a/' input | bc
La bonne chose à propos de bc
(et dc
name__) est qu’il s’agit de calculatrices de précision arbitraire, elles ne débordent donc jamais ni ne manquent de précision sur les entiers.
L'expression sed
transforme l'entrée en:
a+=3;a
a+=4;a
a+=5;a
a+=8;a
Ceci est ensuite évalué par bc
name__. La variable a
bc est initialisée automatiquement à 0. Chaque ligne incrémente a
name__, puis l’imprime explicitement.