Видалити повторювані рядки попарно?


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: Ви можете встановити параметр "locale" 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., Якщо в рядках є пробіл, версія у вашому коментарі не вдасться.
Уайлдкард

Це правда. У цьому випадку не буде петля для друку поля , $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 так, це краще. виправлений, thx.
jimmij

Дуже круто! (+1)
Джояо

4

Якщо вхід сортується:

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

У вас тут невдача прив’язки. Спробуйте запустити його, наприклад, pineapple\napple\ncoconutі вихід є pinecoconut.
Wildcard

@Wildcard: дякую. Ти правий. Подивіться, чи має сенс моє оновлення ...
JJoao

1
Так. Мені було цікаво, чому ви використовуєте \nзамість $даного /mмодифікатора, але потім зрозумів, що використання $залишить порожній рядок замість видалених рядків. Зараз добре виглядає; Я видалив неправильну версію, оскільки вона просто додала шум. :)
Wildcard

@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].

HTH

код зворотних рядків

#!/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, я згоден з вами, якщо ви вимагаєте порядку введення, а не порядку сортування, він може бути реалізований за допомогою додаткового хеш-ключа, перевага цього в тому, що вам не доведеться сортувати введення, оскільки порядок сортування можна зробити в кінці з меншим виходом;)
Moises Najar

@Wildcard, якщо вам потрібно зберегти замовлення, будь ласка, зазначте це у питанні. Цей підхід був також моєю першою думкою, і ви не згадуєте іншого порядку, крім того, щоб сказати, що ми можемо вважати, що файл відсортований. Звичайно, якщо файл відсортований, ви завжди можете передати вихід цього рішення sort.
terdon

@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

Дослідно в 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)

Терселі в Хаскеллі:

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.