Удалить дубликаты попарно?


16

Я столкнулся с этим вариантом использования сегодня. Это кажется простым , на первый взгляд, но возиться с sort, uniq, sedи awkвыяснилось , что это нетривиально.

Как я могу удалить все пары повторяющихся строк? Другими словами, если есть четное количество дубликатов данной строки, удалите все из них; если есть нечетное количество повторяющихся строк, удалите все, кроме одной. (Сортированный ввод может быть принят.)

Чистое элегантное решение является предпочтительным.

Пример ввода:

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

Пример вывода:

a
d
e

Ответы:


6

Я выработал sedответ вскоре после того, как опубликовал этот вопрос; никто еще не использовал, sedтак что вот оно:

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

Немного поиграв с более общей проблемой (как насчет удаления строк в наборах из трех? Или четырех или пяти?), Мы получили следующее расширяемое решение:

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

Расширено для удаления тройки строк:

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

Или удалить квадраты линий:

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

sed обладает дополнительным преимуществом по сравнению с большинством других опций, которое заключается в его способности по-настоящему работать в потоке, при этом не требуется больше памяти, чем фактическое количество строк, проверяемых на наличие дубликатов.


Как отметил cuonglm в комментариях , установка языкового стандарта на C необходима, чтобы избежать сбоев в правильном удалении строк, содержащих многобайтовые символы. Таким образом, приведенные выше команды становятся:

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.

2
@Wildcard: вы можете установить для локали значение C, в противном случае в многобайтовой локали недопустимый символ в этой локали приведет к сбою команды.
cuonglm

4

Это не очень элегантно, но так просто, как я могу придумать:

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

Substr () просто обрезает uniqвывод. Это будет работать до тех пор, пока у вас не будет более 9 999 999 дубликатов строки (в этом случае вывод uniq может превысить 9 символов).


Я пытался, uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }'и это, казалось, работало одинаково хорошо. По какой причине substrверсия лучше?
Джозеф Р.

1
@JosephR., Если в строках есть пробелы, версия в вашем комментарии потерпит неудачу.
Wildcard

Это правда. В этом случае не будет петля для печати поля , $2чтобы $NFбыть более надежными?
Джозеф Р.

@JosephR .: Почему вы считаете, что ваша альтернатива будет более надежной? Вам может быть трудно заставить его работать правильно, когда есть несколько последовательных пробелов; например, foo   bar.
G-Man говорит: «Восстанови Монику»

@JosephR., Нет, потому что это изменило бы / исключило разделение пробелов. uniq(по крайней мере, в GNU coreutils), кажется, надежно использует ровно 9 символов перед самим текстом; Я не могу найти это нигде в документации, и это не в спецификации POSIX .
Wildcard

4

Попробуйте этот awkскрипт ниже:

#!/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;
  }
}

Предполагается, что lines.txtфайл отсортирован.

Тест:

$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e

4

С pcregrepдля данного образца:

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

или в более общем виде:

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

Разве в конце не должно быть якоря «конец строки»? В противном случае вы потерпите неудачу в строке, которая совпадает со строкой перед ней, кроме конечных символов.
Wildcard

@ Wildcard да, так лучше. исправлено, спасибо.
Джимми

Очень круто! (+1)
Жоао

4

Если вход отсортирован:

perl -0pe  'while(s/^(.*)\n\1\n//m){}'

У вас здесь ошибка привязки. Попробуйте запустить его, например, pineapple\napple\ncoconutи результат будет pinecoconut.
Wildcard

@Wildcard: спасибо. Вы правы. Посмотрите, имеет ли мое обновление смысл ...
JJoao

1
Ага. Мне было интересно, почему вы использовали \nвместо того, чтобы $дать /mмодификатор, но потом я понял, что использование $оставит пустую строку вместо удаленных строк. Хорошо выглядит сейчас; Я удалил неправильную версию, так как она просто добавила шум. :)
Подстановочный

@wildcard, спасибо за снижение шума ☺
JJoao

3

Мне нравится pythonза это например с python2.7+

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

2

Как я понял, вопрос, который я выбрал для awk, используя хэш каждой записи, в этом случае я предполагаю, что RS = \ n, но его можно изменить, чтобы рассмотреть любые другие виды соглашений, он может быть рассмотрен для рассмотрения четное количество повторений, а не нечетное, с параметром или небольшим диалогом. Каждая строка используется в качестве хэша, и ее количество увеличивается, в конце файла массив сканируется и печатает каждый четный счет записи. Я включил счет для проверки, но удаления [x] достаточно, чтобы решить эту проблему.

НТН

код счетных линий

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

Образец данных:

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

Пробный прогон:

countlines feed.txt
j 1
k 1
x 1
a 3
One Sunny Day 3
d 5
e 1

Это хороший кусок awkкода, но, к сожалению, awkассоциативные массивы вообще не упорядочены и не сохраняют порядок.
Wildcard

@Wildcard, я согласен с вами, если вам требуется порядок ввода, а не порядок сортировки, его можно реализовать с помощью дополнительного хеш-ключа, преимущество в том, что вам не нужно сортировать ввод, так как порядок сортировки можно сделать в конце с меньшим выводом;)
Моизес Наджар

@ Wildcard, если вам нужно сохранить порядок, пожалуйста, укажите это в вопросе. Этот подход был также моей первой мыслью, и вы не упомянули порядок, кроме как сказать, что мы можем предположить, что файл отсортирован. Конечно, если файл отсортирован, вы всегда можете передать результаты этого решения sort.
Тердон

@terdon, конечно ты прав; вывод можно просто отсортировать снова. Хорошая точка зрения. Стоит также отметить, что !=0это подразумевается тем, как awkчисла преобразуются в истинные / ложные значения, что делает это сводимым кawk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard

1

Если вход отсортирован, что об этом awk:

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

1

с Perl:

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

1

Используя конструкции оболочки,

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done

1
Это разрывается со строками, начинающимися или заканчивающимися пробелами (или более, потому что вы забыли процитировать $b).
Жиль "ТАК - перестань быть злым"

1

Веселая головоломка!

В 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

Многословно в Хаскеле:

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)

Кратко в Haskell:

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

0

версия: я использую «разделители», чтобы упростить внутренний цикл (предполагается, что первая строка не является, __unlikely_beginning__и предполагается, что текст не заканчивается строкой:, __unlikely_ending__и добавить эту специальную строку разделителя в конце вставленных строк. Таким образом, Алгоритм может предполагать оба:)

{ 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; }
'

Так :

  • мы помним паттерн, который мы сейчас наблюдаем, увеличивая его на единицу каждый раз, когда он повторяется. [и если это повторилось, мы пропускаем следующие 2 действия, которые относятся к случаю, когда шаблон изменяется]
  • Когда шаблон ИЗМЕНЕНИЯ:
    • если не кратно 2, мы печатаем один раз запомненный образец
    • и в каждом случае, когда шаблон изменился: новый запомненный шаблон является текущим шаблоном, и мы видели его только один раз.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.