Apagar linhas duplicadas em pares?

16

Eu encontrei este caso de uso hoje. Parece simples à primeira vista, mas mexer com sort , uniq , sed e awk revelou que não é 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. (Entrada ordenada pode ser assumida.)

Uma solução elegante e limpa é 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
    
por Wildcard 18.04.2016 / 22:50

12 respostas

6

Eu resolvi a resposta sed pouco depois de postar essa pergunta; ninguém mais usou sed até agora, então aqui está:

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

Um pouco de brincadeira com o problema mais geral (que sobre 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$/d;P;D' temp

Extensão para remover triplas de linhas:

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

Ou para remover quadras de linhas:

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

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

Como cuonglm apontou nos comentários , a definição do código de idioma para C é necessária para evitar falhas na remoção adequada de linhas que contenham caracteres de múltiplos bytes. Então os comandos acima se tornam:

LC_ALL=C sed '$!N;/^\(.*\)\n$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\n$/d;P;D' temp
# Etc.
    
por 18.04.2016 / 23:14
4

Não é muito elegante, mas é tão simples quanto eu posso imaginar:

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

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

    
por 18.04.2016 / 23:00
4

Experimente este script awk 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 arquivo lines.txt esteja classificado.

O teste:

$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e
    
por 18.04.2016 / 23:10
4

Com pcregrep para uma amostra:

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

ou de uma forma mais geral:

pcregrep -Mv '(^.*)\n$' file
    
por 18.04.2016 / 23:41
4

Se a entrada estiver ordenada:

perl -0pe  'while(s/^(.*)\n\n//m){}'
    
por 19.04.2016 / 01:10
3

Eu gosto de python , por exemplo, com python 2.7 +

from itertools import groupby
with open('input') as f:
    for k, g in groupby(f):
            if len(list(g)) % 2:
                    print(k),
    
por 19.04.2016 / 02:47
2

Como eu entendi a questão eu optei pelo awk, usando um hash de cada registro, neste caso estou assumindo que RS = \ n, mas ele pode ser alterado para considerar qualquer outro tipo de arranjos, ele pode ser arranjado considerar um número par de repetições, em vez do ímpar, 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 cada contagem do registro. Estou incluindo a contagem para verificar, mas remover um [x] é suficiente para resolver esse problema.

HTH

código das linhas de contagem

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

Dados da 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

Execução da amostra:

countlines feed.txt
j 1
k 1
x 1
a 3
One Sunny Day 3
d 5
e 1
    
por 19.04.2016 / 04:50
1

Se a entrada estiver classificada, e quanto a esse awk :

awk '{ x[$0]++; if (prev != $0 && x[prev] % 2 == 1) { print prev; } prev = $0; } END { if (x[prev] % 2 == 1) print prev; }' sorted
    
por 18.04.2016 / 23:03
1

com perl:

uniq -c file | perl -lne 'if (m(^\s*(\d+) (.*)$)) {print $2 if $1 % 2 == 1}'
    
por 18.04.2016 / 23:06
1

Usando construções de shell,

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done
    
por 18.04.2016 / 23:03
1

Puzzle 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)

Resumidamente em Haskell:

import Data.List (group)
main = interact $ unlines . map head . filter (odd . length) . group . lines
    
por 19.04.2016 / 23:04
0

uma versão: eu uso "delimitadores" para simplificar o laço interno (ele assume que a primeira linha não é __unlikely_beginning__ e assume que o texto não está terminando com a linha: __unlikely_ending__ e adiciona aquela linha delimitadora especial em o fim das linhas inseridas, assim o 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 do padrão que estamos observando no momento, aumentando-o por um a cada vez que ele ocorrer novamente. [e se isso ocorrer novamente, pulamos as próximas 2 ações, que são para o caso quando o padrão muda]
  • Quando o padrão CHANGES:
    • se não for um múltiplo de 2, imprimimos uma ocorrência do padrão memorizado
    • e em todos os casos quando o padrão foi alterado: o novo padrão memorizado é o padrão atual e só o vimos uma vez.
por 19.04.2016 / 17:09