Hapus garis duplikat berpasangan?


16

Saya menemukan kasus penggunaan ini hari ini. Tampaknya sederhana pada pandangan pertama, tapi mengutak-atik sekitar dengan sort, uniq, seddan awkmengungkapkan bahwa itu trivial.

Bagaimana saya bisa menghapus semua pasangan garis duplikat? Dengan kata lain, jika ada jumlah duplikat dari suatu baris tertentu, hapus semuanya; jika ada jumlah garis duplikat ganjil, hapus semua kecuali satu. (Input yang disortir dapat diasumsikan.)

Solusi elegan bersih lebih disukai.

Input contoh:

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

Contoh output:

a
d
e

Jawaban:


6

Saya menemukan sedjawabannya tidak lama setelah saya memposting pertanyaan ini; tidak ada orang lain yang telah menggunakan sedsejauh ini jadi ini dia:

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

Sedikit bermain-main dengan masalah yang lebih umum (bagaimana dengan menghapus garis di set tiga? Atau empat, atau lima?) Memberikan solusi yang dapat diperluas berikut:

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

Diperpanjang untuk menghapus tiga kali lipat garis:

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

Atau untuk menghapus quads of lines:

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

sed memiliki keunggulan tambahan dibandingkan sebagian besar opsi lain, yaitu kemampuannya untuk benar-benar beroperasi dalam aliran, tanpa penyimpanan memori yang lebih dibutuhkan daripada jumlah baris aktual yang akan diperiksa untuk duplikat.


Seperti ditunjukkan cuonglm dalam komentar , pengaturan lokal ke C diperlukan untuk menghindari kegagalan untuk menghapus baris yang berisi karakter multi-byte dengan benar. Jadi perintah di atas menjadi:

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
@ Kartu Memori: Anda mungkin ingin mengatur lokal ke C, jika tidak dalam multi-byte lokal, karakter yang tidak valid di lokal itu menyebabkan perintah gagal.
cuonglm

4

Itu tidak terlalu elegan, tapi sesederhana yang saya dapat dengan:

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

Substr () hanya memotong uniqoutput. Itu akan bekerja sampai Anda memiliki lebih dari 9.999.999 duplikat dari sebuah baris (dalam hal ini keluaran uniq dapat meluas lebih dari 9 karakter).


Saya mencoba uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }'dan tampaknya berfungsi sama baiknya. Ada alasan mengapa substrversinya lebih baik?
Joseph R.

1
@ JosephR., Jika ada spasi putih di baris, versi dalam komentar Anda akan gagal.
Wildcard

Itu benar. Dalam hal ini, tidak akan loop untuk mencetak ladang $2untuk $NFmenjadi lebih kuat?
Joseph R.

@ YosephR.: Mengapa Anda percaya bahwa alternatif Anda akan lebih kuat? Anda mungkin mengalami kesulitan untuk membuatnya berfungsi dengan benar ketika ada beberapa ruang berurutan; misalnya foo   bar,.
G-Man Mengatakan 'Reinstate Monica'

@ JosephRR., Tidak, karena itu akan mengubah / menghilangkan pembatasan spasi putih. uniq(setidaknya dalam GNU coreutils) tampaknya andal menggunakan tepat 9 karakter sebelum teks itu sendiri; Saya tidak dapat menemukan ini didokumentasikan di mana pun, dan itu tidak ada dalam spesifikasi POSIX .
Wildcard

4

Cobalah awkskrip ini di bawah ini:

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

Diasumsikan bahwa lines.txtfile tersebut diurutkan.

Ujian:

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

4

Dengan pcregrepuntuk sampel yang diberikan:

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

atau dengan cara yang lebih umum:

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

Tidakkah seharusnya ada jangkar "ujung garis" di ujungnya? Kalau tidak, Anda akan gagal pada garis yang cocok dengan garis sebelum itu selain memiliki karakter tambahan.
Wildcard

@ Kartu Memori ya, itu lebih baik. dikoreksi, terima kasih.
jimmij

Sangat keren! (+1)
JJoao

4

Jika input diurutkan:

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

Anda mengalami kegagalan berlabuh di sini. Coba jalankan di eg pineapple\napple\ncoconutdan hasilnya adalah pinecoconut.
Wildcard

@ Kartu Memori: terima kasih. Kamu benar. Lihat apakah pembaruan saya masuk akal ...
JJoao

1
Ya. Saya bertanya-tanya mengapa Anda menggunakan \nalih-alih $memberikan /mpengubah, tetapi kemudian saya menyadari bahwa menggunakan $akan meninggalkan baris kosong di tempat baris yang dihapus. Terlihat bagus sekarang; Saya telah menghapus versi yang salah karena baru saja menambahkan noise. :)
Wildcard

@ kartu kartu, terima kasih atas pengurangan kebisingan ☺
JJoao

3

Saya suka pythonuntuk ini, misalnya dengan 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

Ketika saya memahami pertanyaan yang saya pilih untuk awk, menggunakan hash dari setiap record, dalam hal ini saya berasumsi bahwa RS = \ n, tetapi dapat diubah untuk mempertimbangkan segala jenis pengaturan lainnya, dapat diatur untuk mempertimbangkan suatu genap jumlah repetisi, bukan yang ganjil, dengan parameter atau dialog kecil. Setiap baris digunakan sebagai hash dan jumlahnya meningkat, pada akhir file array dipindai dan mencetak setiap hitungan genap dari catatan. Saya menyertakan hitungan untuk memeriksa tetapi, menghapus [x] sudah cukup untuk menyelesaikan masalah itu.

HTH

kode hitungan mundur

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

Contoh data:

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

Contoh Run:

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

Ini adalah awkkode yang bagus, tetapi sayangnya awkarray asosiatif tidak dipesan sama sekali, dan juga tidak menjaga pesanan.
Wildcard

@Wildcard, saya setuju dengan Anda, jika Anda memerlukan urutan input, daripada urutan, dapat diterapkan melalui kunci hash tambahan, keuntungannya adalah Anda tidak perlu menyortir input, karena urutan sortir dapat dibuat pada akhirnya dengan output yang lebih kecil;)
Moises Najar

@ Kartu Memori jika Anda ingin agar pesanan tetap dipertahankan, harap sebutkan dalam pertanyaan. Pendekatan ini juga merupakan pemikiran pertama saya dan Anda tidak menyebut urutan selain untuk mengatakan bahwa kami dapat menganggap file diurutkan. Tentu saja, jika file diurutkan, Anda selalu dapat melewatkan output dari solusi ini sort.
terdon

@terdon, tentu saja Anda benar; output hanya bisa disortir lagi. Poin bagus. Perlu juga dicatat bahwa !=0ini tersirat oleh bagaimana awkmengkonversi angka ke nilai benar / salah, membuat ini dapat direduksi menjadiawk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard

1

Jika input diurutkan, bagaimana dengan ini 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

dengan perl:

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

1

Menggunakan konstruksi shell,

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

1
Itu terputus dengan garis yang dimulai atau berakhir dengan spasi putih (atau lebih, karena Anda lupa mengutip $b).
Gilles 'SANGAT berhenti menjadi jahat'

1

Teka-teki yang menyenangkan!

Dalam 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

Secara verbal di 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 di Haskell:

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

0

versi: Saya menggunakan "pembatas" untuk menyederhanakan loop dalam (ia menganggap baris pertama tidak __unlikely_beginning__dan mengasumsikan teks tidak berakhir dengan baris __unlikely_ending__:, dan menambahkan garis pembatas khusus di akhir baris yang dimasukkan. Dengan demikian, algoritma dapat mengasumsikan keduanya:)

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

Jadi:

  • kita ingat pola yang kita lihat saat ini, bertambah satu kali setiap kali terjadi. [dan jika itu terjadi kembali, kita lewati 2 tindakan berikutnya, yang untuk kasus ketika polanya berubah]
  • Saat pola PERUBAHAN:
    • jika bukan kelipatan 2, kami mencetak satu kemunculan pola yang dihafal
    • dan dalam setiap kasus ketika polanya telah berubah: pola baru yang dihafal adalah pola saat ini, dan kami hanya melihatnya sekali.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.