중복 된 줄을 쌍으로 삭제 하시겠습니까?


16

오늘이 사용 사례가 발생했습니다. 그것은 첫눈에 간단하게 보이지만, 주변에 바이올린을 켜고 sort, uniq, sedawk는 사소하다고 밝혔다.

어떻게 모두 삭제할 수 있습니다 쌍의 중복 라인을? 다시 말해, 주어진 행에 짝수의 복제본이 있으면 모두 삭제하십시오. 홀수의 중복 행이 있으면 하나를 제외하고 모두 삭제하십시오. (정렬 된 입력을 가정 할 수 있습니다.)

깨끗하고 우아한 솔루션이 바람직합니다.

입력 예 :

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'

보다 일반적인 문제 (3 개 또는 4 개 또는 5 개 세트에서 행을 삭제하는 것은 어떻습니까)로 약간의 장난은 다음과 같은 확장 가능한 솔루션을 제공했습니다.

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버전이 더 좋은 이유는 무엇입니까?
Joseph R.

1
@JosephR., 줄에 공백이 있으면 주석의 버전이 실패합니다.
와일드 카드

사실입니다. 이 경우 필드 $2$NF보다 견고 하게 인쇄하는 루프가 아니 겠습니까?
Joseph R.

@JosephR .: 대체 대안이 더 강력 할 것이라고 생각하는 이유는 무엇입니까? 연속 된 공백이 여러 개인 경우 올바르게 작동하지 못할 수 있습니다. 예를 들어, foo   bar.
G-Man은 'Reinstate Monica'라고

@JosephR., 아니요, 공백 구분을 변경 / 제거하기 때문입니다. uniq(적어도 GNU coreutils에서) 텍스트 자체 앞에 정확히 9자를 정확하게 사용하는 것 같습니다. 나는이 문서를 어디에서나 찾을 수 없으며 POSIX 사양 에는 없습니다 .
와일드 카드

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 네, 더 좋습니다. 수정되었습니다.
jimmij

아주 멋지다! (+1)
JJoao

4

입력이 정렬 된 경우 :

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

여기에 앵커링 실패가 있습니다. 예를 들어 실행 해보십시오 . pineapple\napple\ncoconut출력은 pinecoconut입니다.
와일드 카드

@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이라고 가정하지만 다른 종류의 배열을 고려하도록 변경 될 수 있습니다. 매개 변수 또는 작은 대화 상자를 사용하여 홀수 대신 짝수 번 반복하십시오. 모든 줄은 해시로 사용되며 그 수가 증가합니다. 파일 끝에서 배열이 스캔되고 모든 짝수의 레코드를 인쇄합니다. 확인하기 위해 개수를 포함하고 있지만 a [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, 나는 당신에게 동의합니다. 정렬 순서가 아닌 입력 순서가 필요한 경우 추가 해시 키를 통해 구현할 수 있습니다. 이의 장점은 정렬 순서 때문에 입력을 정렬해야한다는 것입니다 더 작은 출력으로 최종적으로 만들 수 있습니다;)
Moises Najar

@Wildcard 주문을 유지하려면 질문에 언급하십시오. 이 접근 방식은 저의 첫 번째 생각이기도하며 파일이 정렬되었다고 가정 할 수있는 것 외에 다른 순서는 언급하지 않았습니다. 물론 파일이 정렬되면 언제든지이 솔루션의 출력을 전달할 수 있습니다 sort.
terdon

물론 @terdon은 정확합니다. 출력을 다시 정렬 할 수 있습니다. 좋은 지적. 또한 숫자를 참 / 거짓 값으로 변환 !=0하는 방법에 의해 암시 될 수 있음을 주목할 가치가 있습니다.awkawk '{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

펄로 :

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)으로 구분됩니다.
Gilles 'SO- 악의를 멈춰라'

1

재미있는 퍼즐!

펄에서 :

#! /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)

하스켈에서 간절히 :

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의 배수가 아닌 경우, 우리는 기억 된 패턴의 하나의 발생을 인쇄합니다
    • 그리고 패턴이 변경된 모든 경우에 : 새로운 기억 된 패턴은 현재 패턴이며, 우리는 한 번만 본 것입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.