ลบบรรทัดที่ซ้ำกันเป็นคู่หรือไม่


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 กล่าวว่า 'Reinstate Monica'

@JosephR., ไม่, เพราะมันจะเปลี่ยน / กำจัด whitespace delimiting uniq(อย่างน้อยใน coreutils ของ GNU) ดูเหมือนว่าจะใช้อักขระ 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 ใช่ดีกว่า แก้ไขขอบคุณ
jimmij

เจ๋งมาก! (+1)
JJoao

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 แต่มันสามารถเปลี่ยนได้เพื่อพิจารณาข้อตกลงประเภทอื่น ๆ มันสามารถจัดเรียงเพื่อพิจารณา จำนวน reps แทนที่จะเป็นเลขคี่ด้วยพารามิเตอร์หรือไดอะล็อกเล็ก ๆ ทุกบรรทัดจะถูกใช้เป็นแฮชและจำนวนของมันจะเพิ่มขึ้นในตอนท้ายของไฟล์อาเรย์จะถูกสแกนและพิมพ์ทุกจำนวนของเร็กคอร์ด ฉันรวมถึงจำนวนเพื่อตรวจสอบ แต่การลบ [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:

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

verbosely ใน 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)

Tersely ใน 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; }
'

ดังนั้น:

  • เราจำรูปแบบที่เรากำลังดูอยู่เพิ่มขึ้นทีละครั้งทุกครั้งที่มัน reoccurs [และถ้ามันเกิดขึ้นอีกเราจะข้ามการกระทำ 2 รายการถัดไปซึ่งเป็นกรณีเมื่อรูปแบบเปลี่ยนไป]
  • เมื่อรูปแบบการเปลี่ยนแปลง:
    • หากไม่ใช่จำนวน 2 เราจะพิมพ์รูปแบบการจดจำหนึ่งครั้ง
    • และในทุกกรณีเมื่อรูปแบบมีการเปลี่ยนแปลง: รูปแบบการจดจำใหม่เป็นรูปแบบปัจจุบันและเราเห็นเพียงครั้งเดียว
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.