Suponha que eu tenha um arquivo (chame-o de exemplo.txt) que se parece com isso:
Row1,10
Row2,20
Row3,30
Row4,40
Quero poder trabalhar em um fluxo desse arquivo que é essencialmente a combinação em pares de todas as quatro linhas (portanto, devemos terminar com 16 no total). Por exemplo, estou procurando um comando de streaming (ou seja, eficiente) em que a saída é:
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
Meu caso de uso é que desejo transmitir essa saída para outro comando (como o awk) para calcular algumas métricas sobre essa combinação em pares.
Eu tenho uma maneira de fazer isso no awk, mas minha preocupação é que meu uso do bloco END {} signifique que eu estou basicamente armazenando o arquivo inteiro na memória antes de sair. Código de exemplo:
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 uma maneira eficiente de streaming para fazer isso sem ter que armazenar essencialmente o arquivo na memória e, em seguida, imprimir no bloco END?
fonte
Respostas:
Veja como fazê-lo no awk para que ele não precise armazenar o arquivo inteiro em uma matriz. Este é basicamente o mesmo algoritmo que o de Terdon.
Se desejar, você pode até fornecer vários nomes de arquivos na linha de comando e ele processará cada arquivo independentemente, concatenando os resultados juntos.
No meu sistema, isso é executado em cerca de 2/3 do tempo da solução perl do terdon.
fonte
Não tenho certeza se isso é melhor do que fazê-lo na memória, mas com um
sed
quer
preenche seu infile para cada linha em seu infile e outro no outro lado de um tubo alternando oH
espaço antigo com as linhas de entrada ...RESULTADO
Eu fiz isso de outra maneira. Ele armazena um pouco na memória - armazena uma string como:
... para cada linha no arquivo.
É muito rápido. É
cat
o arquivo quantas vezes houver linhas no arquivo para a|pipe
. No outro lado do canal, essa entrada é mesclada com o próprio arquivo quantas vezes houver linhas no arquivo.O
case
material é apenas para portabilidade -yash
ezsh
tanto um elemento de adicionar à divisão, enquantomksh
eposh
tanto um perder.ksh
,dash
,busybox
, Ebash
tudo dividido para exatamente quantos campos existem zeros como impressa peloprintf
. Conforme escrito, o acima apresenta os mesmos resultados para cada uma das conchas acima mencionadas na minha máquina.Se o arquivo for muito longo, poderá haver
$ARGMAX
problemas com muitos argumentos; nesse caso, você precisará introduzirxargs
ou similar também.Dada a mesma entrada que usei antes da saída é idêntica. Mas se eu fosse maior ...
Isso gera um arquivo quase idêntico ao que eu usei antes (sans 'Row') - mas com 1000 linhas. Você pode ver por si mesmo o quão rápido é:
Em 1000 linhas, há uma ligeira variação no desempenho entre os shells -
bash
é invariavelmente o mais lento - mas como o único trabalho que eles fazem é gerar a string arg (1000 cópiasfilename -
), o efeito é mínimo. A diferença de desempenho entrezsh
- como acima - ebash
é centésimo de segundo aqui.Aqui está outra versão que deve funcionar para um arquivo de qualquer tamanho:
Ele cria um link suave para o seu primeiro argumento
/tmp
com um nome semi-aleatório, para que não fique preso a nomes de arquivos estranhos. Isso é importante porquecat
os argumentos são alimentados através de um cano viaxargs
.cat
A saída do arquivo é salva<&3
enquantosed
p
as linhas do primeiro argumento são copiadas quantas vezes houver linhas nesse arquivo - e seu script também é alimentado por meio de um pipe. Novamentepaste
mescla sua entrada, mas desta vez são necessários apenas dois argumentos-
novamente para sua entrada padrão e o nome do link/dev/fd/3
.Esse último - o
/dev/fd/[num]
link - deve funcionar em qualquer sistema Linux e muito mais, mas se não criar um pipe nomeadomkfifo
e usá-lo, também funcionará.A última coisa que ele faz é
rm
o link direto criado antes de sair.Esta versão é realmente mais rápida ainda no meu sistema. Eu acho que é porque, apesar de executar mais aplicativos, ele começa a entregar seus argumentos imediatamente - enquanto antes os empilhava primeiro.
fonte
ctrl+v; ctrl+j
para obter novas linhas como eu faço.. ./file; fn_name
nesse caso.Bem, você sempre pode fazer isso no seu shell:
É muito mais lento que a sua
awk
solução (na minha máquina, demorou ~ 11 segundos para 1000 linhas, contra ~ 0,3 segundosawk
), mas pelo menos nunca mantém mais do que algumas linhas na memória.O loop acima funciona para os dados muito simples que você possui no seu exemplo. Ele engasga com barras invertidas e come espaços à direita e à esquerda. Uma versão mais robusta da mesma coisa é:
Outra opção é usar
perl
:O script acima lerá cada linha do arquivo de entrada (
-ln
), salve-o como$l
, abrasample.txt
novamente e imprima cada linha junto com$l
. O resultado são todas as combinações aos pares, enquanto apenas 2 linhas são armazenadas na memória. No meu sistema, isso levou apenas0.6
alguns segundos em 1000 linhas.fonte
echo
possa ser um problema. O que eu escrevi (adicioneiprintf
agora) deve funcionar com todos eles, certo? Quanto aowhile
loop, por quê? O que há de erradowhile read f; do ..; done < file
? Certamente você não está sugerindo umfor
loop! Qual é a outra alternativa?Com
zsh
:$^a
em uma matriz ativa a expansão do tipo cinta (como em{elt1,elt2}
) para a matriz.fonte
Você pode compilar esse código c ++ para obter resultados bastante rápidos.
É concluído em cerca de 0,19 - 0,27 segundos em um arquivo de 1000 linhas.
Atualmente lê
10000
linhas na memória (para acelerar a impressão na tela) que, se você tivesse1000
caracteres por linha, usaria menos que a10mb
memória, o que eu acho que não seria um problema. Você pode remover completamente essa seção e apenas imprimir diretamente na tela, se isso causar um problema.Você pode compilar usando
g++ -o "NAME" "NAME.cpp"
Onde
NAME
é o nome do arquivo para salvá-lo eNAME.cpp
é o arquivo em que esse código é salvoCTEST.cpp:
Demonstração
fonte
O campo 2 está vazio e igual para todo o elemento em file.txt, portanto
join
concatenará cada elemento com todos os outros: na verdade, está calculando o produto cartesiano.fonte
Uma opção do Python é mapear o arquivo na memória e tirar proveito do fato de que a biblioteca de expressões regulares do Python pode trabalhar diretamente com arquivos mapeados na memória. Embora isso pareça executar ciclos aninhados sobre o arquivo, o mapeamento de memória garante que o sistema operacional coloque a RAM física disponível de maneira ideal em jogo
Como alternativa, uma solução rápida em Python, embora a eficiência da memória ainda possa ser uma preocupação
fonte
No bash, o ksh também deve funcionar, usando apenas os recursos internos do shell:
Observe que, enquanto isso mantém o arquivo inteiro na memória em uma variável de shell, ele precisa apenas de um único acesso de leitura.
fonte
sed
solução.Explicação:
sed 'r file2' file1
- leia todo o conteúdo do arquivo2 para cada linha do arquivo1.1~i
significa 1ª linha, depois 1 + i linha, 1 + 2 * i, 1 + 3 * i, etc. Portanto,1~$((line_num + 1)){h;d}
significah
antiga linha pontiaguda para o buffer,d
elimina o espaço do padrão e inicia um novo ciclo.'G;s/(.*)\n(.*)/\2 \1/'
- para todas as linhas, exceto as selecionadas na etapa anterior, faça o seguinte:G
et line a partir do buffer de retenção e anexe-o à linha atual. Então troque os lugares das linhas. Foicurrent_line\nbuffer_line\n
, tornou-sebuffer_line\ncurrent_line\n
Resultado
fonte