Xóa các dòng trùng lặp theo cặp?


16

Tôi gặp trường hợp sử dụng ngày hôm nay. Có vẻ như đơn giản ở cái nhìn đầu tiên, nhưng quan trọng xung quanh với sort, uniq, sedawktiết lộ rằng đó là không tầm thường.

Làm thế nào tôi có thể xóa tất cả các cặp dòng trùng lặp? Nói cách khác, nếu có số lượng trùng lặp chẵn của một dòng nhất định, hãy xóa tất cả chúng; nếu có một số lẻ các dòng trùng lặp, hãy xóa tất cả trừ một dòng. (Đầu vào được sắp xếp có thể được giả định.)

Một giải pháp thanh lịch sạch sẽ là thích hợp hơn.

Ví dụ đầu vào:

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

Ví dụ đầu ra:

a
d
e

Câu trả lời:


6

Tôi đã tìm ra sedcâu trả lời không lâu sau khi tôi đăng câu hỏi này; không ai khác đã sử dụng sedcho đến nay nó là:

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

Một chút chơi xung quanh với vấn đề chung hơn (về việc xóa các dòng trong bộ ba? Hoặc bốn hoặc năm?) Đã cung cấp giải pháp mở rộng sau:

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

Mở rộng để loại bỏ ba lần dòng:

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

Hoặc để loại bỏ bốn dòng:

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

sed có một lợi thế bổ sung so với hầu hết các tùy chọn khác, đó là khả năng thực sự hoạt động trong luồng, không cần nhiều bộ nhớ hơn so với số lượng dòng thực tế cần kiểm tra trùng lặp.


Như cuonglm đã chỉ ra trong các bình luận , việc đặt ngôn ngữ thành C là cần thiết để tránh thất bại trong việc loại bỏ đúng các dòng chứa các ký tự nhiều byte. Vì vậy, các lệnh trên trở thành:

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: Bạn có thể muốn đặt ngôn ngữ thành C, nếu không, trong miền địa phương nhiều byte, ký tự không hợp lệ trong miền địa phương đó khiến lệnh bị lỗi.
cuonglm

4

Nó không thanh lịch lắm, nhưng nó đơn giản như tôi có thể nghĩ ra:

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

Các chất nền () chỉ cắt bỏ uniqđầu ra. Điều đó sẽ hoạt động cho đến khi bạn có hơn 9,999.999 bản sao của một dòng (trong trường hợp đó, đầu ra của uniq có thể vượt quá 9 ký tự).


Tôi đã thử uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }'và nó dường như hoạt động tốt như nhau. Bất kỳ lý do substrphiên bản là tốt hơn?
Joseph R.

1
@JosephR., Nếu có bất kỳ khoảng trắng nào trong các dòng thì phiên bản trong bình luận của bạn sẽ thất bại.
tự đại diện

Điều đó đúng. Trong trường hợp đó, sẽ không phải là một vòng lặp để in các lĩnh vực $2để $NFđược mạnh mẽ hơn?
Joseph R.

@JosephR.: Tại sao bạn tin rằng sự thay thế của bạn sẽ mạnh mẽ hơn? Bạn có thể gặp khó khăn để làm cho nó hoạt động chính xác khi có nhiều không gian liên tiếp; ví dụ foo   bar.
G-Man Says 'Khôi phục Monica'

@JosephR., Không, bởi vì nó sẽ thay đổi / loại bỏ phân định khoảng trắng. uniq(ít nhất là trong lõi GNU) dường như sử dụng chính xác 9 ký tự trước chính văn bản; Tuy nhiên, tôi không thể tìm thấy tài liệu này ở bất kỳ đâu và nó không có trong thông số kỹ thuật POSIX .
tự đại diện

4

Hãy thử awkkịch bản này dưới đây:

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

Nó được giả định rằng các lines.txttập tin được sắp xếp.

Các bài kiểm tra:

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

4

Với pcregrepmột mẫu nhất định:

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

hoặc một cách tổng quát hơn:

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

Không nên có một neo "cuối dòng" ở cuối? Nếu không, bạn sẽ thất bại trên một dòng khớp với dòng trước nó ngoài việc có các ký tự dấu.
tự đại diện

@Wildcard yeah, điều đó tốt hơn. sửa chữa, thx.
jimmij

Rất tuyệt! (+1)
JJoao

4

Nếu đầu vào được sắp xếp:

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

Bạn có một thất bại neo ở đây. Hãy thử chạy nó trên ví dụ pineapple\napple\ncoconutvà đầu ra là pinecoconut.
tự đại diện

@Wildcard: cảm ơn bạn. Bạn đúng rồi. Xem bản cập nhật của tôi có hợp lý không ...
JJoao

1
Vâng. Tôi đã tự hỏi tại sao bạn sử dụng \nthay vì $đưa ra công cụ /msửa đổi, nhưng sau đó tôi nhận ra rằng việc sử dụng $sẽ để lại một dòng trống thay cho các dòng bị xóa. Có vẻ tốt bây giờ; Tôi đã xóa phiên bản không chính xác vì nó chỉ thêm tiếng ồn. :)
tự đại diện

@wildcard, cảm ơn bạn đã giảm tiếng ồn ☺
JJoao

3

Tôi thích pythonđiều này, ví dụ với 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

Khi tôi hiểu câu hỏi tôi đã chọn awk, sử dụng hàm băm của mỗi bản ghi, trong trường hợp này tôi giả sử rằng RS = \ n, nhưng nó có thể được thay đổi để xem xét bất kỳ cách sắp xếp nào khác, nó có thể được sắp xếp để xem xét số lượng chẵn, thay vì số lẻ, với một tham số hoặc hộp thoại nhỏ. Mỗi dòng được sử dụng như hàm băm và số lượng của nó được tăng lên, ở cuối tệp, mảng được quét và in mọi số chẵn của bản ghi. Tôi đang tính cả số đếm để kiểm tra nhưng, xóa [x] là đủ để giải quyết vấn đề đó.

HTH

mã đếm

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

Dữ liệu mẫu:

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

Chạy mẫu:

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

Đó là một đoạn awkmã đẹp, nhưng thật không may, awkcác mảng kết hợp hoàn toàn không được đặt hàng, chúng cũng không được bảo toàn trật tự.
Wildcard

@Wildcard, tôi đồng ý với bạn, nếu bạn đang yêu cầu thứ tự đầu vào, thay vì thứ tự sắp xếp, nó có thể được thực hiện thông qua khóa băm phụ, lợi thế của việc này là bạn không phải sắp xếp đầu vào, vì thứ tự sắp xếp có thể được thực hiện ở cuối với sản lượng nhỏ hơn;)
Moise Najar

@Wildcard nếu bạn cần bảo quản đơn hàng, vui lòng đề cập đến điều đó trong câu hỏi. Cách tiếp cận này cũng là suy nghĩ đầu tiên của tôi và bạn không đề cập đến thứ tự nào khác ngoài việc nói rằng chúng ta có thể giả sử tệp được sắp xếp. Tất nhiên, nếu tệp được sắp xếp, bạn luôn có thể chuyển đầu ra của giải pháp này thông qua sort.
terdon

@terdon, tất nhiên là bạn đúng; đầu ra chỉ có thể được sắp xếp lại. Điểm tốt. Cũng đáng lưu ý rằng điều !=0này được ngụ ý bằng cách awkchuyển đổi số thành giá trị đúng / sai, làm cho điều này có thể rút gọn thànhawk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard

1

Nếu đầu vào được sắp xếp những gì về điều này 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

với perl:

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

1

Sử dụng cấu trúc shell,

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

1
Điều đó phá vỡ với các dòng bắt đầu hoặc kết thúc với khoảng trắng (hoặc nhiều hơn, vì bạn quên trích dẫn $b).
Gilles 'SO- ngừng trở nên xấu xa'

1

Câu đố vui!

Trong 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

Có thể nói rõ bằng 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)

Khó khăn trong Haskell:

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

0

một phiên bản: Tôi sử dụng "dấu phân cách" để đơn giản hóa vòng lặp bên trong (nó giả sử dòng đầu tiên không phải __unlikely_beginning__và nó giả sử văn bản không kết thúc bằng dòng: __unlikely_ending__và thêm dòng phân cách đặc biệt đó vào cuối các dòng được nhập. thuật toán có thể giả sử cả hai :)

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

Vì thế :

  • chúng tôi nhớ mô hình mà chúng tôi hiện đang xem xét, tăng nó lên một lần mỗi khi nó xuất hiện trở lại. [và nếu nó đã lặp lại, chúng tôi bỏ qua 2 hành động tiếp theo, dành cho trường hợp khi mẫu thay đổi]
  • Khi mẫu THAY ĐỔI:
    • nếu không phải là bội số của 2, chúng ta sẽ in một mẫu của mẫu ghi nhớ
    • và trong mọi trường hợp khi mẫu đã thay đổi: mẫu ghi nhớ mới là mẫu hiện tại và chúng tôi chỉ nhìn thấy nó một lần.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.