Dado um arquivo L com um número inteiro não negativo por linha e o arquivo de texto F, qual seria a maneira mais rápida de manter apenas essas linhas em F, cujo número de linha aparece no arquivo L?
Exemplo:
$ cat L.txt
1
3
$ cat F.txt
Hello World
Hallo Welt
Hola mundo
$ command-in-question -x L.txt F.txt
Hello World
Hola mundo
Estou procurando um comando que possa manipular um arquivo L com 500 milhões ou mais de entradas; o arquivo L é classificado numericamente.
Nota: Estou no meio de uma implementação para um, command-in-question
mas eu apenas me perguntei se seria possível usar algumas ferramentas Unix aqui também.
Atualização: Obrigado por todas as respostas, eu aprendi muito hoje! Gostaria de aceitar mais uma resposta, mas isso não é possível.
Respostas:
Com a
C
omissão de mensagens de erro significativas:fonte
xsel -bo | cc -xc - -o cselect
. E funcionou - ele só precisa das duas libs.LINE_MAX
em sua versão, então provavelmente trabalha com linhas muito grandes em seus arquivos. Atualizei o A com uma versão usandogetline()
para remover o limite de tamanho da linha.LINE_MAX
, entãogetline
parece correto.Eu usaria
awk
, mas não armazenaria todo o conteúdo daL.txt
memória e faria pesquisas desnecessárias de hash ;-).fonte
n
, de outro modo (como está) que perde1
emL.txt
command-in-question
script, não seria possível incorporar o nome do arquivo no código.-v list="$opt_x"
também não funciona por causa do processamento de barra invertida feito pelo awk nele. É por isso que eu uso o ENVIRON aqui.grep -n | sort | sed | cut
Isso deve funcionar muito rapidamente (alguns testes cronometrados estão incluídos abaixo) com entradas de qualquer tamanho. Algumas notas sobre como:
export LC_ALL=C
./F
empilhado em linha com./L
o arquivo lineno, os únicos caracteres com os quais realmente precisamos nos preocupar são os[0-9]
dígitos ASCII e os:
dois pontos.grep -n ''
LINENO:
na cabeça de cada linha em stdin - ou<./F
.sort -t: -nmk1,1 ./L -
sort
negligências para classificar seus arquivos de entrada em tudo, e em vez disso (corretamente) presume que eles são pré-classificados e-m
Erges-los em-numerically
ordem de classificação, ignorando basicamente qualquer coisa além de qualquer possível-k1,1
ocorrendo st-t:
carácter dois pontos de qualquer maneira.sort
produzirá um único fluxo no qual qualquer linha de entrada./L
precederá imediatamente as linhas correspondentes./F
../L
As linhas de sempre vêm primeiro porque são mais curtas.sed /:/d\;n
/:/
dois pontos,d
elimine-a da saída. Senão, imprima automaticamente an
linha atual e ext.sed
podasort
a saída apenas para pares de linhas sequenciais que não correspondem a dois pontos e à seguinte linha - ou, apenas a uma linha de./L
e depois a seguinte.cut -sd: -f2-
cut
-s
suprime da saída as linhas de entrada que não contêm pelo menos uma de suas-d:
seqüências de elimitadores - e, portanto./L
, as linhas são completamente removidas.:
delimitado por dois pontos-f
estácut
ausente - e o mesmo ocorre com todosgrep
os lineno inseridos.teste de entrada pequena
... gera 5 linhas de entrada de amostra. Então...
... imprime ...
testes cronometrados maiores
Criei alguns arquivos bastante grandes:
... que coloca linhas de 5mil e linhas de
/tmp/F
1,5mil selecionadas aleatoriamente/tmp/L
. Eu então fiz:Imprimiu:
(Eu adicionei as barras invertidas lá)
Entre as soluções atualmente oferecidas aqui, essa é a mais rápida de todas, exceto uma quando comparada com o conjunto de dados gerado acima na minha máquina. Dos outros, apenas um chegou perto de disputar o segundo lugar, e esse é o meuh
perl
aqui .Esta não é de forma alguma a solução original oferecida - ela perdeu um terço do seu tempo de execução graças a conselhos / inspiração oferecidos por outros. Veja o histórico de publicações para soluções mais lentas (mas por quê?) .
Além disso, vale a pena notar que algumas outras respostas podem muito bem apresentar-se melhor se não fossem a arquitetura de várias CPUs do meu sistema e a execução simultânea de cada um dos processos nesse pipeline. Todos eles trabalham ao mesmo tempo - cada um no seu próprio núcleo de processador - passando os dados e fazendo sua pequena parte do todo. É muito legal.
mas a solução mais rápida é ...
Mas não é a solução mais rápida. A solução mais rápida oferecido aqui, mãos para baixo, é o programa C . Eu chamei
cselect
. Depois de copiá-lo para minha área de transferência do X, compilei-o como:Eu então fiz:
... e os resultados foram ...
fonte
sed -ne'/:/!{n;p;}' | cut -d: -f2-
, em vez desed -ne'/:/!N;/\n/s/[^:]*://p'
sed
s - o quesed
eu estou usando é a herançased
- você pode ver oalias
valor nostime
resultados. A propósito, meu pacote de herança é compilado estaticamente em relação a uma musl libc - cuja implementação de regex é baseada no TRE . Quando eu o troco para o GNUsed
- e o executo semcut
- ele adiciona um segundo inteiro ao tempo de conclusão (2,8 segundos) - aumenta em mais de um terço. E isso é apenas 0,3 segundos mais rápido que o seu no meu sistema.sort -mn
ao contráriosort -nmk1,1
poderia ser melhor como você não precisa fazer a divisão aqui (não testado)-n
é especificado apenas para fazer a primeira string numérica de uma linha, então imaginei, ok-mn
ou-nm
e, por qualquer motivo, as únicas vezes em que ele caiu abaixo de 2 s no tempo de conclusão foi quando adicionei todas as opções como estão. É estranho - e é a razão pela qual ontem não adotei-m
- eu sabia o que era, mas parecia funcionar como algum tipo de otimização automática. Curiosamente, a herançasort
tem uma-z
opção de cadeia de comprimento, que só se aplica a-[cm]
....-n
não é a primeira sequência numérica na linha . Apenas considera a linha como um número, portanto,abc 123
seria 0. Portanto, não pode ser menos eficiente do que com #-t: -k1,1
Eu usaria
awk
:Atualização: eu fiz medidas de desempenho; parece que esta versão é ainda melhor com conjuntos de dados muito grandes (como é o caso dos requisitos declarados), pois a comparação é muito rápida e compensa demais o esforço necessário para criar a tabela de hash.
fonte
awk
s podem lidar com conjuntos de dados tão grandes. - Estou usando o GNUawk
e não há problemas; o teste com 500 milhões de linhas de dados levou 7 minutos.real 16m3.468s
-user 15m48.447s
-sys 0m10.725s
. Ele usou 3,3 GB de RAM testando um tamanho de 1/10 de polegadaL
com 50.000.000 de linhas; eF
com 500.000.000 de linhas - contra o tempo de despertar ansioso de Stéphane Chazelas:real 2m11.637s
-user 2m2.748s
-sys 0m6.424s
- Não estou usando uma caixa rápida, mas a comparação é interessante.seq
de saída e, em seguida, uma menor, subconjunto seleccionado aleatoriamente do mesmo em L .Apenas para completar: podemos mesclar o excelente script awk na resposta de Stéphane Chazelas, e o script perl na resposta de kos, mas sem manter a lista inteira na memória, na esperança de que o perl possa ser mais rápido que o awk. (Alterei a ordem dos argumentos para corresponder à pergunta original).
fonte
awk
. É quase tão rápido quanto o meu - eu testei as três vezes agora e cada vez o meu manipulava meu conjunto de testes de linha de 5mil em 1,8 ... segundos e o seu 1,9 ... segundos de cada vez. O código gen do conjunto de testes está na minha resposta, se você se importa, mas o ponto é que é muito bom. Além do mais, a saída está correta - eu ainda não posso fazer oawk
trabalho ... Ainda assim, ambas as nossas respostas são envergonhadas pelas da FloHimself .awk
s. Na sua amostra, recebo 1,4s com gawk (4s para Janis '), 0,9s com mawk, 1,7s com esta solução perl, 2,3s com kos', 4,5s com o seu (GNU sed) e 1,4s com o seu ( GNU sed) e minha melhoria sugerida (e 0,5s para a solução C).Eu escrevi um script Perl simples para fazer isso:
Usage: script.pl inputfile_f inputfile_f
F.txt
L.txt
L.txt
em uma matrizF.txt
linha por linha, rastreando seu número de linha atual e o índice atual da matriz; aumenta oF.txt
número da linha atual; se oF.txt
número da linha atual corresponder ao conteúdo da matriz no índice atual da matriz, ele imprimirá a linha atual e aumentará o índiceConsiderações de custo e complexidade :
Considerando o custo para fazer as atribuições, o custo para fazer as comparações e o custo para imprimir as linhas, dado N 1 como o número de linhas
F.txt
e N 2 como o número de linhasL.txt
, owhile
loop é executado no máximo N 1 vezes, levando a atribuições 2N 1 + N 2 (obviamente assumindo N 1 > N 2 ), comparações 2N 1 e impressões de N 2 ; dado como igual ao custo de cada operação, o custo total para executar owhile
loop é 4N 1 + 2N 2 , o que leva a uma complexidade do script de O (N).Teste em um arquivo de entrada de 10 milhões de linhas :
Usando um
F.txt
arquivo de 10 milhões de linhas contendo linhas aleatórias de 50 caracteres e umL.txt
arquivo de 10 milhões de linhas contendo números de 1 a 10000000 (pior cenário):fonte
Essa solução perl é mais rápida do que as outras soluções awk ou perl em 20% ou mais, mas não é tão rápida quanto a solução em C.
fonte
Como L.txt está classificado, você pode usar join. Apenas numere cada linha em F.txt, junte os dois arquivos e remova o número da linha. Nenhum arquivo intermediário grande é necessário.
Na verdade, o acima irá alterar suas linhas de dados, substituindo todo o espaço em branco por um único espaço. Para manter a linha intacta, você precisa escolher como delimitador algum caractere que não apareça nos seus dados, por exemplo, "|". O cmd é então
O primeiro sed remove espaços à esquerda da saída "cat -n" e substitui a guia. O segundo sed remove o número da linha e "|".
fonte
join L.txt <(nl F.txt )
mas não funcionará em arquivos grandes. Bem-vindo ao site, a propósito, nem sempre recebemos respostas tão claras e bem formatadas de novos usuários!join
/comm
não possa funcionar com entradas classificadas numericamente.join -t' ' <(<L.txt awk '{printf("%010s\n",$0)}') <(<F.txt awk '{printf("%010s %s\n",NR,$0)}') | cut -d' ' -f2-
- Foi lento! - e mesmo quando eu alimentei os arquivos preparados com as teclas preenchidas com 0join -t' ' L.txt F.txt | cut -d' ' -f2-
, ainda era lento (não incluindo o tempo de preparação) - mais lento que aawk
resposta de @Janis (onde eu postei um comentário sobre o tempo real gasto para ambos resposta de his e @ StéphaneChazelas 'join
+ foi contra Stéphane Chazelas ' usando 50 milhões de linhas, 500 milhões de linhas.awk printf
real 20m11.663s user 19m35.093s sys 0m10.513s
real 2m11.637s user 2m2.748s sys 0m6.424s
L
F
Para completar, outra tentativa de
join
solução:Isso funciona formatando a coluna de número de linha que une funciona como comprimento fixo com zeros à esquerda, para que os números tenham sempre 15 dígitos. Isso contorna o problema de associação, não gostando da ordem de classificação numérica normal, pois a coluna agora foi efetivamente forçada a ser uma classificação de dicionário.
nl
é usado para adicionar números de linha nesse formato ao F.txt. Infelizmente,sed
precisa ser usado para reformatar a numeração em L.txt.Essa abordagem parece funcionar bem nos dados de teste gerados usando o método @ mikeserv. Mas ainda é muito lento - a solução c é 60x mais rápida na minha máquina. cerca de 2/3 do tempo é gasto
sed
e 1/3 poljoin
. Talvez exista uma melhor expressão de sed ...fonte
nl
é super legal, mas você não pode usá-lo com robustez em entradas não testadas. Uma das coisas que o torna tão legal é seu elimitador de página lógica-d
. Por padrão, se houver alguma linha na entrada que consiste apenas das seqüências de caracteres:\`
(mas sem o túmulo à direita) 1, 2, 3 ou três vezes seguidas, suas contagens ficarão um pouco loucas. Experimente com ele - é bem legal. Especialmente ter um olhar para o que acontece quando nl` lê uma linha com a corda 1 delimitador e depois outra w / 3 ou 2Como a resposta aceita está em C, concluí que não há problema em lançar uma solução python aqui:
Se usar uma biblioteca externa como numpy, uma solução pareceria ainda mais elegante:
fonte