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
Wildcard
sumber

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.
Wildcard
sumber
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).

Jeff Schaller
sumber
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
Jay jargot
sumber
4

Dengan pcregrepuntuk sampel yang diberikan:

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

atau dengan cara yang lebih umum:

pcregrep -Mv '(^.*)\n\1$' file
jimmij
sumber
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){}'
Joao
sumber
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),
iruvar
sumber
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
Moises Najar
sumber
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
taliezin
sumber
1

dengan perl:

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

Menggunakan konstruksi shell,

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done
Guido
sumber
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
Greg Bacon
sumber
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.
Olivier Dulac
sumber