Supposons que j'ai un fichier (appelez-le sample.txt) qui ressemble à ceci:
Row1,10
Row2,20
Row3,30
Row4,40
Je veux pouvoir travailler sur un flux de ce fichier qui est essentiellement la combinaison par paire des quatre lignes (nous devrions donc nous retrouver avec 16 au total). Par exemple, je recherche une commande de streaming (c'est-à-dire efficace) où la sortie est:
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40
Mon cas d'utilisation est que je veux diffuser cette sortie dans une autre commande (comme awk) pour calculer une métrique sur cette combinaison par paire.
J'ai un moyen de le faire dans awk mais ma préoccupation est que mon utilisation du bloc END {} signifie que je stocke essentiellement le fichier entier en mémoire avant de le sortir. Exemple de code:
awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Existe-t-il un moyen de streaming efficace pour le faire sans avoir à stocker essentiellement le fichier en mémoire, puis à le sortir dans le bloc END?
la source
Réponses:
Voici comment le faire dans awk afin qu'il n'ait pas à stocker le fichier entier dans un tableau. Il s'agit essentiellement du même algorithme que celui de terdon.
Si vous le souhaitez, vous pouvez même lui donner plusieurs noms de fichiers sur la ligne de commande et il traitera chaque fichier indépendamment, concaténant les résultats ensemble.
Sur mon système, cela s'exécute dans environ 2/3 du temps de la solution Perl de Terdon.
la source
Je ne suis pas sûr que ce soit mieux que de le faire en mémoire, mais avec un
sed
quir
lit son fichier pour chaque ligne de son fichier et un autre de l'autre côté d'un tuyau alternant l'H
ancien espace avec des lignes d'entrée ...PRODUCTION
Je l'ai fait d'une autre manière. Il en stocke certains en mémoire - il stocke une chaîne comme:
... pour chaque ligne du fichier.
C'est très rapide. C'est
cat
le fichier autant de fois qu'il y a de lignes dans le fichier vers a|pipe
. De l'autre côté du canal, cette entrée est fusionnée avec le fichier lui-même autant de fois qu'il y a de lignes dans le fichier.Le
case
contenu est juste pour la portabilité -yash
et leszsh
deux ajoutent un élément à la division, tandismksh
que lesposh
deux en perdent un.ksh
,dash
,busybox
Etbash
toutes divisées en exactement autant de champs comme il y a des zéros comme imprimé parprintf
. Tel qu'écrit, ce qui précède donne les mêmes résultats pour chacun des obus mentionnés ci-dessus sur ma machine.Si le fichier est très long, il peut y avoir des
$ARGMAX
problèmes avec trop d'arguments, auquel cas vous devrez également introduirexargs
ou similaire.Étant donné la même entrée que j'ai utilisée avant la sortie est identique. Mais si je devais aller plus loin ...
Cela génère un fichier presque identique à ce que j'utilisais auparavant (sans 'Row') - mais à 1000 lignes. Vous pouvez voir par vous-même à quelle vitesse il est:
À 1000 lignes, il y a une légère variation de performances entre les shells -
bash
c'est invariablement la plus lente - mais parce que le seul travail qu'ils font de toute façon est de générer la chaîne d'argument (1000 copies defilename -
) l'effet est minime. La différence de performances entrezsh
- comme ci-dessus - etbash
est de 100e de seconde ici.Voici une autre version qui devrait fonctionner pour un fichier de n'importe quelle longueur:
Il crée un lien logiciel vers son premier argument
/tmp
avec un nom semi-aléatoire afin qu'il ne se bloque pas sur des noms de fichiers étranges. C'est important parce quecat
les arguments sont alimentés via un tuyau viaxargs
.cat
La sortie de 'est enregistrée dans<&3
tandissed
p
qu'imprime chaque ligne dans le premier argument autant de fois qu'il y a de lignes dans ce fichier - et son script lui est également envoyé via un tube.paste
Fusionne à nouveau son entrée, mais cette fois, il ne prend à nouveau que deux arguments-
pour son entrée standard et le nom du lien/dev/fd/3
.Ce dernier - le
/dev/fd/[num]
lien - devrait fonctionner sur n'importe quel système Linux et bien d'autres encore, mais s'il ne crée pas de canal nommé avecmkfifo
et l'utilise à la place, cela devrait également fonctionner.La dernière chose qu'il fait est
rm
le lien logiciel qu'il crée avant de quitter.Cette version est en fait encore plus rapide sur mon système. Je suppose que c'est parce que bien qu'il exécute plus d'applications, il commence à leur transmettre immédiatement leurs arguments - alors qu'avant, il les empilait tous en premier.
la source
ctrl+v; ctrl+j
pour obtenir des nouvelles lignes comme je le fais.. ./file; fn_name
dans ce cas.Eh bien, vous pouvez toujours le faire dans votre shell:
C'est beaucoup plus lent que votre
awk
solution (sur ma machine, cela a pris ~ 11 secondes pour 1000 lignes, contre ~ 0,3 secondeawk
), mais au moins, il ne contient jamais plus de quelques lignes en mémoire.La boucle ci-dessus fonctionne pour les données très simples que vous avez dans votre exemple. Il s'étouffera avec les contre-obliques et mangera les espaces de fuite et de tête. Une version plus robuste de la même chose est:
Un autre choix est d'utiliser à la
perl
place:Le script ci-dessus lira chaque ligne du fichier d'entrée (
-ln
), l'enregistrera sous$l
, rouvrirasample.txt
et imprimera chaque ligne avec$l
. Le résultat est toutes les combinaisons par paire alors que seulement 2 lignes sont jamais stockées en mémoire. Sur mon système, cela ne prenait que0.6
quelques secondes sur 1 000 lignes.la source
echo
pourrait être un problème. Ce que j'avais écrit (j'ajouteprintf
maintenant) devrait fonctionner avec tous, n'est-ce pas? Quant à lawhile
boucle, pourquoi? Qu'est-ce qui ne va paswhile read f; do ..; done < file
? Vous ne proposez certainement pas unefor
boucle! Quelle est l'autre alternative?Avec
zsh
:$^a
sur un tableau active l'expansion de type accolade (comme dans{elt1,elt2}
) pour le tableau.la source
Vous pouvez compiler ce code c ++ pour des résultats assez rapides.
Il se termine en environ 0,19 à 0,27 seconde sur un fichier de 1 000 lignes.
Il lit actuellement les
10000
lignes en mémoire (pour accélérer l'impression à l'écran) qui, si vous aviez des1000
caractères par ligne, utiliserait moins que la10mb
mémoire, ce qui ne serait pas un problème selon moi . Vous pouvez cependant supprimer complètement cette section et l'imprimer directement à l'écran si cela pose un problème.Vous pouvez compiler en utilisant
g++ -o "NAME" "NAME.cpp"
Où
NAME
est le nom du fichier pour l'enregistrer etNAME.cpp
est le fichier dans lequel ce code est enregistréCTEST.cpp:
Manifestation
la source
Le champ 2 est vide et égal pour tous les éléments du fichier.txt donc
join
va concaténer chaque élément avec tous les autres: il s'agit en fait de calculer le produit cartésien.la source
Une option avec Python consiste à mapper en mémoire le fichier et à tirer parti du fait que la bibliothèque d'expressions régulières Python peut fonctionner directement avec des fichiers mappés en mémoire. Bien que cela ait l'apparence d'exécuter des boucles imbriquées sur le fichier, le mappage de la mémoire garantit que le système d'exploitation met la RAM physique disponible en jeu de manière optimale.
Alternativement, une solution rapide en Python, bien que l'efficacité de la mémoire puisse toujours être un problème
la source
En bash, ksh devrait également fonctionner, en utilisant uniquement les fonctions intégrées du shell:
Notez que bien que cela contienne le fichier entier en mémoire dans une variable shell, il n'a besoin que d'un seul accès en lecture.
la source
sed
Solution.Explication:
sed 'r file2' file1
- lire tout le contenu du fichier de fichier2 pour chaque ligne de fichier1.1~i
signifie 1-ème ligne, puis 1 + ligne i, 1 + 2 * i, 1 + 3 * i, etc. Par conséquent, des1~$((line_num + 1)){h;d}
moyensh
ancienne ligne pointue dans la mémoire tampon, l'd
espace de modèle elete et commencer à nouveau cycle.'G;s/(.*)\n(.*)/\2 \1/'
- pour toutes les lignes, sauf celles choisies à l'étape précédente, faites ensuite:G
et ligne depuis le tampon de maintien et ajoutez-la à la ligne actuelle. Échangez ensuite les emplacements des lignes. Étaitcurrent_line\nbuffer_line\n
, est devenubuffer_line\ncurrent_line\n
Production
la source