Excluir linhas duplicadas em pares?

16

Encontrei esse caso de uso hoje. Parece simples à primeira vista, mas brincar com sort, uniq, sede awkrevelou que é trivial.

Como posso excluir todos os pares de linhas duplicadas? Em outras palavras, se houver um número par de duplicatas de uma determinada linha, exclua todas elas; se houver um número ímpar de linhas duplicadas, exclua todas, exceto uma. (A entrada classificada pode ser assumida.)

Uma solução limpa e elegante é preferível.

Exemplo de entrada:

a
a
a
b
b
c
c
c
c
d
d
d
d
d
e

Exemplo de saída:

a
d
e
Curinga
fonte

Respostas:

6

Eu resolvi a sedresposta pouco tempo depois de postar esta pergunta; ninguém mais usou sedaté agora, aqui está:

sed '$!N;/^\(.*\)\n\1$/d;P;D'

Um pouco de brincadeira com o problema mais geral (e a exclusão de linhas em conjuntos de três? Ou quatro ou cinco?) Forneceu a seguinte solução extensível:

sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp

Estendido para remover triplos de linhas:

sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp

Ou para remover quads de linhas:

sed -e ':top' -e '$!{/\n.*\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1\n\1$/d;P;D' temp

sed possui uma vantagem adicional sobre a maioria das outras opções, que é a capacidade de operar verdadeiramente em um fluxo, sem mais memória necessária do que o número real de linhas a serem verificadas quanto a duplicatas.


Como o cuonglm apontou nos comentários , é necessário definir o código de idioma para C para evitar falhas na remoção adequada das linhas que contêm caracteres de vários bytes. Portanto, os comandos acima se tornam:

LC_ALL=C sed '$!N;/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp
# Etc.
Curinga
fonte
2
@Wildcard: convém definir o código de idioma para C, caso contrário, no código de vários bytes, o caractere inválido nesse código de idioma causa a falha do comando.
amigos estão dizendo sobre cuonglm
4

Não é muito elegante, mas é o mais simples que consigo:

uniq -c input | awk '{if ($1 % 2 == 1) { print substr($0, 9) }}'

O substr () apenas apara a uniqsaída. Isso funcionará até que você tenha mais de 9.999.999 duplicatas de uma linha (nesse caso, a saída da uniq pode ultrapassar 9 caracteres).

Jeff Schaller
fonte
Eu tentei uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }'e parecia funcionar igualmente bem. Alguma razão para a substrversão ser melhor?
Joseph R.
11
@ JosephphR., Se houver algum espaço em branco nas linhas, a versão do seu comentário falhará.
Wildcard
Isso é verdade. Nesse caso, um loop para imprimir os campos $2não $NFseria mais robusto?
Joseph R.
@ JosephphR .: Por que você acha que sua alternativa seria mais robusta? Você pode ter dificuldade em fazê-lo funcionar corretamente quando houver vários espaços consecutivos; por exemplo foo   bar,.
G-Man diz 'Reinstate Monica'
@ JosephphR., Não, porque isso mudaria / eliminaria a delimitação de espaço em branco. uniq(pelo menos nos coreutils do GNU) parece usar de maneira confiável exatamente 9 caracteres antes do próprio texto; Não consigo encontrar isso documentado em nenhum lugar, e não está nas especificações do POSIX .
Curinga
4

Experimente este awkscript abaixo:

#!/usr/bin/awk -f
{
  if ((NR!=1) && (previous!=$0) && (count%2==1)) {
    print previous;
    count=0;
  }
  previous=$0;
  count++;
}
END {
  if (count%2==1) {
    print previous;
  }
}

Supõe-se que o lines.txtarquivo esteja classificado.

O teste:

$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e
Jay jargot
fonte
4

Com pcregreppara uma determinada amostra:

pcregrep -Mv '(.)\n\1$' file

ou de uma maneira mais geral:

pcregrep -Mv '(^.*)\n\1$' file
jimmij
fonte
Não deveria haver uma âncora de "fim de linha" no final? Caso contrário, você falhará em uma linha que corresponda à linha anterior a ela, exceto por ter caracteres à direita.
Curinga
@Wildcard sim, isso é melhor. corrigido, thx.
jimmij
Muito legal! (+1)
JJoao
4

Se a entrada for classificada:

perl -0pe  'while(s/^(.*)\n\1\n//m){}'
JJoao
fonte
Você tem uma falha de ancoragem aqui. Tente executá-lo, por exemplo, pineapple\napple\ncoconute a saída é pinecoconut.
Curinga
@Wildcard: obrigado. Você está certo. Veja se minha atualização faz sentido ...
JJoao
11
Sim. Fiquei me perguntando por que você estava usando, em \nvez de $receber o /mmodificador, mas percebi que o uso $deixaria uma linha em branco no lugar das linhas excluídas. Parece bom agora; Eu removi a versão incorreta, pois acabou de adicionar ruído. :)
Curinga
@wildcard, obrigado para a redução de ruído ☺
JJoao
3

Eu gosto pythondisso, por exemplo, com python2.7+

from itertools import groupby
with open('input') as f:
    for k, g in groupby(f):
            if len(list(g)) % 2:
                    print(k),
iruvar
fonte
2

Como entendi a pergunta que optei pelo awk, usando um hash de cada registro, neste caso, estou assumindo que RS = \ n, mas pode ser alterado para considerar qualquer outro tipo de arranjo, pode ser arranjado para considerar um número par de repetições, em vez de ímpares, com um parâmetro ou uma pequena caixa de diálogo. Cada linha é usada como o hash e sua contagem é aumentada. No final do arquivo, a matriz é digitalizada e imprime todas as contagens iguais do registro. Estou incluindo a contagem para verificar, mas remover um [x] é suficiente para resolver esse problema.

HTH

código de linhas de contagem

#!/usr/bin/nawk -f
{a[$0]++}
END{for (x in a) if (a[x]%2!=0) print x,a[x] }

Dados de amostra:

a
One Sunny Day
a
a
b
my best friend
my best friend
b
c
c
c
One Sunny Day
c
d
my best friend
my best friend
d
d
d
One Sunny Day
d
e
x
k
j
my best friend
my best friend

Exemplo de execução:

countlines feed.txt
j 1
k 1
x 1
a 3
One Sunny Day 3
d 5
e 1
Moises Najar
fonte
É um bom pedaço de awkcódigo, mas infelizmente awkas matrizes associativas não são ordenadas, nem preservam a ordem.
Curinga
@Wildcard, eu concordo com você, se você estiver solicitando a ordem de entrada, em vez de uma ordem de classificação, ela pode ser implementada por meio de uma chave hash extra, a vantagem disso é que você não precisa classificar a entrada, pois a ordem de classificação pode ser feito no final com uma saída menor;)
Moises Najar
@ Cartão virtual, se você precisar que o pedido seja preservado, mencione isso na pergunta. Essa abordagem também foi meu primeiro pensamento e você não menciona ordem além de dizer que podemos assumir que o arquivo está classificado. Obviamente, se o arquivo estiver classificado, você sempre poderá transmitir a saída desta solução sort.
terdon
@terdon, é claro que você está correto; a saída pode ser classificada novamente. Bom ponto. É importante notar também que o !=0está implícito pela forma como awkconverte números para verdadeiros falsos valores /, tornando este redutível aawk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard
1

Se a entrada for classificada, e quanto a isso awk:

awk '{ x[$0]++; if (prev != $0 && x[prev] % 2 == 1) { print prev; } prev = $0; } END { if (x[prev] % 2 == 1) print prev; }' sorted
taliezin
fonte
1

com perl:

uniq -c file | perl -lne 'if (m(^\s*(\d+) (.*)$)) {print $2 if $1 % 2 == 1}'
xx4h
fonte
1

Usando construções de shell,

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done
Guido
fonte
11
Isso quebra com as linhas que começam ou terminam com espaço em branco (ou mais, porque você esqueceu de citar $b).
Gilles 'SO- stop being evil'
1

Quebra-cabeça divertido!

Em Perl:

#! /usr/bin/env perl

use strict;
use warnings;

my $prev;
while (<>) {
  $prev = $_, next unless defined $prev;  # prime the pump

  if ($prev ne $_) {
    print $prev;
    $prev = $_;                           # first half of a new pair
  }
  else {
    undef $prev;                          # discard and unprime the pump
  }
}

print $prev if defined $prev;             # possible trailing odd line

Verbosamente em Haskell:

main :: IO ()
main = interact removePairs
  where removePairs = unlines . go . lines
        go [] = []
        go [a] = [a]
        go (a:b:rest)
          | a == b = go rest
          | otherwise = a : go (b:rest)

Concertamente em Haskell:

import Data.List (group)
main = interact $ unlines . map head . filter (odd . length) . group . lines
Greg Bacon
fonte
0

uma versão: eu uso "delimitadores" para simplificar o loop interno (assume que a primeira linha não está __unlikely_beginning__e assume que o texto não está terminando com a linha: __unlikely_ending__e adicione essa linha delimitadora especial no final das linhas inseridas. algoritmo pode assumir ambos:)

{ cat INPUTFILE_or_just_-  ; echo "__unlikely_ending__" ; } | awk '
  BEGIN {mem="__unlikely_beginning__"; occured=0; }  

    ($0 == mem)            { occured++ ; next } 

    ( occured%2 )           { print mem ;} 
                            { mem=$0; occured=1; }
'

Então :

  • lembramos o padrão que estamos vendo atualmente, aumentando-o em um toda vez que ocorre novamente. [e se ocorrer novamente, pularemos as próximas 2 ações, que são o caso quando o padrão muda]
  • Quando o padrão MUDAR:
    • se não for múltiplo de 2, imprimimos uma ocorrência do padrão memorizado
    • e em todos os casos em que o padrão mudou: o novo padrão memorizado é o padrão atual e só o vimos uma vez.
Olivier Dulac
fonte