Làm thế nào để loại bỏ các dòng trùng lặp trong một tập tin văn bản?


126

Một tệp văn bản khổng lồ (tối đa 2 GiB) của tôi chứa khoảng 100 bản sao chính xác của mỗi dòng trong đó (vô dụng trong trường hợp của tôi, vì tệp là một bảng dữ liệu giống như CSV).

Những gì tôi cần là loại bỏ tất cả các lần lặp lại trong khi (tốt nhất là, nhưng điều này có thể được hy sinh để tăng hiệu suất đáng kể) duy trì thứ tự trình tự ban đầu. Trong kết quả, mỗi dòng là duy nhất. Nếu có 100 dòng bằng nhau (thường là các bản sao được trải đều trên tệp và sẽ không là hàng xóm) thì chỉ còn một dòng duy nhất.

Tôi đã viết một chương trình bằng Scala (coi đó là Java nếu bạn không biết về Scala) để thực hiện điều này. Nhưng có lẽ có những công cụ bản địa viết C nhanh hơn có thể làm điều này nhanh hơn?

CẬP NHẬT: awk '!seen[$0]++' filenamegiải pháp có vẻ hoạt động tốt đối với tôi miễn là các tệp gần 2 GiB hoặc nhỏ hơn nhưng bây giờ tôi đang dọn sạch tệp 8 GiB thì nó không còn hoạt động nữa. Có vẻ như mất vô hạn trên máy Mac với RAM 4 GiB và PC Windows 7 64 bit với RAM 4 GiB và 6 GiB trao đổi vừa hết bộ nhớ. Và tôi không cảm thấy hào hứng khi thử nó trên Linux với RAM 4 GiB cho trải nghiệm này.


điều này sẽ phá hủy đơn đặt hàng của bạn, nhưng bạn đã thử sắp xếp -u, tôi không biết làm thế nào hoặc nếu nó có thể chạy trên một tệp khổng lồ như vậy
0x7c0

5
C thường không nhanh hơn đáng kể so với Java và nếu bây giờ bạn đang chạy nó (theo thứ tự), sẽ có cơ hội công bằng nó sẽ kết thúc trước khi bạn nhận được câu trả lời ở đây, thực hiện nó và nó kết thúc chạy; ra khỏi trật tự, sort -ucó lẽ sẽ nhanh hơn
Kevin

Câu trả lời:


215

Một awkgiải pháp được thấy trên #bash (Freenode):

awk '!seen[$0]++' filename

1
Chỉ cần thử điều này trên tệp 2G và mất ba phút trên máy tính xách tay của tôi. Không tệ. Tôi cũng đã thử tên tập tin uniq | awk '! thấy [$ 0] ++', nhưng nó không nhanh hơn.
mgjk

Tốc độ này nhanh hơn đáng ngạc nhiên so với awkphiên bản dài dòng hơn bằng cách sử dụng 2 tra cứu mảng (được hiển thị dưới dạng giải thích mở rộng trong câu trả lời của Gilles): 0m36.132s so với 0m49.958s .. trong 50 triệu dòng .. Tôi nghĩ nút cổ chai sẽ là I / O, nhưng việc tìm kiếm mảng bổ sung là ... 1 triệu phần tử trong mảng dường như tạo ra một vết lõm khá quan trọng ...
Peter.O

Nhưng làm thế nào mà so sánh với sort -u ....?
HashWizard

1
@HashWizard: lệnh này không sắp xếp, nhưng loại bỏ mọi lần xuất hiện tiếp theo của cùng một dòng
enzotib

1
@MaxWilliams vâng, nó hoạt động là chúng được phân phối ngẫu nhiên.
setholopolus

47

Có một phương pháp đơn giản (không thể nói là rõ ràng) bằng cách sử dụng các tiện ích tiêu chuẩn không yêu cầu bộ nhớ lớn ngoại trừ để chạy sort, trong hầu hết các triển khai có tối ưu hóa cụ thể cho các tệp lớn (thuật toán sắp xếp bên ngoài tốt). Một lợi thế của phương pháp này là nó chỉ lặp trên tất cả các dòng bên trong các tiện ích đặc biệt, không bao giờ bên trong các ngôn ngữ được giải thích.

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

Nếu tất cả các dòng bắt đầu bằng một ký tự không phải khoảng trắng, bạn có thể phân phối với một số tùy chọn:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

Đối với một số lượng lớn sao chép, một phương thức chỉ yêu cầu lưu trữ một bản sao duy nhất của mỗi dòng trong bộ nhớ sẽ hoạt động tốt hơn. Với một số overhead giải thích, có một kịch bản awk rất súc tích cho rằng (đã được đăng bởi enzotib ):

<input awk '!seen[$0]++'

Ít chính xác hơn : !seen[$0] {print} {seen[$0] += 1}, tức là in dòng hiện tại nếu nó chưa được nhìn thấy, sau đó tăng bộ seenđếm cho dòng này (các biến chưa được khởi tạo hoặc các phần tử mảng có giá trị bằng 0).

Đối với các dòng dài, bạn có thể lưu bộ nhớ bằng cách chỉ giữ một tổng kiểm tra không thể giả mạo (ví dụ: bản tóm tắt mật mã) của mỗi dòng. Ví dụ: sử dụng SHA-1, bạn chỉ cần 20 byte cộng với chi phí không đổi trên mỗi dòng. Nhưng tiêu hóa điện toán khá chậm; phương pháp này sẽ chỉ giành chiến thắng nếu bạn có CPU nhanh (đặc biệt là CPU có bộ tăng tốc phần cứng để tính toán các bản tóm tắt) và không có nhiều bộ nhớ liên quan đến kích thước của tệp và đủ dòng dài. Không có tiện ích cơ bản nào cho phép bạn tính toán tổng kiểm tra cho mỗi dòng; bạn phải chịu chi phí giải thích của Perl / Python / Ruby / Lỗi hoặc viết một chương trình được biên dịch chuyên dụng.

<input perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output

@Gilles Dựa trên lời giải thích của bạn awk '!seen[$0]++', điều đó có nghĩa là nếu awk nhìn thấy 2 dòng trùng lặp, nó sẽ giữ dòng đầu tiên và bỏ qua tất cả các dòng tiếp theo? (Hoặc nó sẽ giữ cái cuối cùng?)
user779159

1
@ user779159 Nó giữ cái đầu tiên: mỗi dòng đầu vào được in ngay lập tức (lần xuất hiện đầu tiên) hoặc hoàn toàn không xảy ra (lặp lại lần nữa).
Gilles

Nhưng làm thế nào mà so sánh với sắp xếp -u ...?
HashWizard

@HashWizard Một đơn giản sort -uthay đổi thứ tự. Câu trả lời của tôi cho thấy các giải pháp duy trì trật tự (thứ tự của lần xuất hiện đầu tiên, chính xác).
Gilles

@Gilles bạn có thể nói rằng nó nhanh hơn sắp xếp -u cho các tệp lớn (10G) với 50% trùng lặp không?
HashWizard

25
sort -u big-csv-file.csv > duplicates-removed.csv

Lưu ý rằng tập tin đầu ra sẽ được sắp xếp.


1
Không nhanh như awklệnh trong các câu trả lời khác, nhưng về mặt khái niệm thì đơn giản!
Johann

@Johann Tôi đang làm điều này khá thường xuyên trên các tệp có hàng trăm nghìn (thậm chí triệu) chuỗi kết thúc dòng mới ngắn. Tôi nhận được kết quả khá nhanh cho các thí nghiệm tôi đang làm. Nó có thể quan trọng hơn nếu được sử dụng trong các tập lệnh được chạy đi chạy lại, tiết kiệm thời gian có thể là đáng kể.
Vladislavs Dovgalecs

1
Sử dụng sort -uđể loại bỏ trùng lặp trong quá trình sắp xếp, thay vì sau. (Và tiết kiệm băng thông bộ nhớ) dẫn nó đến một chương trình khác). Điều này chỉ tốt hơn awkphiên bản nếu bạn cũng muốn đầu ra của mình được sắp xếp. (Các OP về câu hỏi này muốn đặt hàng ban đầu của mình bảo quản , vì vậy đây là một câu trả lời tốt cho một use-case hơi khác nhau.)
Peter Cordes

Mất khoảng một phút, đối với tôi, đối với tệp dòng 5,5 triệu (tổng cộng 1,8 GB). Xuất sắc.
Max Williams

18

Giả sử bạn có thể đủ khả năng giữ nhiều tệp bị sao chép trong bộ nhớ (nếu dữ liệu của bạn thực sự được nhân đôi bởi hệ số 100, thì phải là khoảng 20MiB + trên đầu), bạn có thể thực hiện việc này rất dễ dàng với Perl.

$ perl -ne 'print unless $dup{$_}++;' input_file > output_file

Điều này bảo tồn trật tự quá.

Bạn có thể trích xuất số lần xuất hiện của mỗi dòng từ %duphàm băm nếu bạn muốn, như một phần thưởng miễn phí bổ sung.

Nếu bạn thích awk, điều này cũng nên làm điều đó (logic tương tự như phiên bản perl, cùng thứ tự, cùng dữ liệu được thu thập trong dupbiến):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file

Điều này là quá tốt @Mat, tôi đã sắp sửa xóa tập tin, lol ;-).
Nikhil Mulley

Bây giờ, hãy chờ đợi @ManAtWork cho khả năng dệt phép thuật và quyến rũ của anh ấy :-)
Nikhil Mulley

tuyệt vời một lần nữa cho mẹo awk :-)
Nikhil Mulley

1
Có thể thay đổi tập lệnh perl để chỉ loại bỏ các dòng liền kề trùng lặp?
dumbledad

2
@dumbledad: tự mình uniqlàm tất cả
Mat

3

Vì không có câu trả lời nào khác được cung cấp hỗ trợ tại chỗ, đây là một:

gawk -i inplace '!a[$0]++' file

Điều này có bảo vệ trật tự? Nhân tiện, điều này không làm việc cho tôi. Phiên bản của tôi là:GNU Awk 4.0.2
Leonid

1
@Leonid vâng, đúng vậy. Nó in sự xuất hiện đầu tiên của bất kỳ dòng duy nhất. Hỗ trợ tại chỗ được giới thiệu lần đầu tiên trong phiên bản 4.1, được phát hành vào năm 2013.
Jan Chren - rindeal

3

Bạn có thể sử dụng uniq http://www.computerhope.com/unix/uuniq.htm

uniq báo cáo hoặc lọc ra các dòng lặp đi lặp lại trong một tập tin.


Khi đưa ra câu trả lời, tốt nhất là đưa ra một số lời giải thích về lý do TẠI SAO câu trả lời của bạncâu trả lời . Vì vậy, làm thế nào để câu trả lời này khác với một số câu trả lời trước?
Stephen Rauch

1
Từ trang man uniq: Lưu ý: 'uniq' does not detect repeated lines unless they are adjacent. Vì vậy, trước tiên bạn phải sắp xếp nó và mất thứ tự của các dòng không trùng lặp.
Vindolin

2

Lớp lót Python One:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile

điều này làm cho toàn bộ tập tin bị nhét vào bộ nhớ và có thể không phù hợp với vấn đề của OP. Cũng không được đảm bảo để giữ lại đơn hàng
iruvar

Cảm ơn lời đề nghị, tôi vừa mới học trăn .. chỉ cần thử điều này cho mục đích học tập .. :)
Rahul Patil

Đây là phiên bản Python 2.7 không phải là một lớp lót nhưng (ngắn gọn) trả về thứ tự duy trì các dòng duy nhất mà không tải toàn bộ tệp vào bộ nhớ hoặc tạo một chuỗi khổng lồ duy nhất để cung cấp để in
iruvar

Cảm ơn @ 1_CR Tôi có vài thứ học được hôm nay :)OrderedDict
Rahul Patil

0

Không có câu trả lời nào ở đây làm việc cho tôi trên máy Mac của tôi vì vậy tôi đã viết một kịch bản python đơn giản phù hợp với tôi. Tôi đang bỏ qua khoảng trắng hàng đầu / dấu và cũng không quan tâm đến việc tiêu thụ bộ nhớ.

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

Lưu phần trên vào unique lạ và chạy như thế này:

python unique.py inputfile.txt outputfile.txt

-1

Với bash 4, một giải pháp bash thuần túy tận dụng các mảng kết hợp có thể được sử dụng. Đây là một ví dụ

unset llist; declare -A llist;
while read -r line; do
if [[ ${llist[$line]} ]]; then
  continue
else 
  printf '%s\n' "$line"
  llist[$line]="x"
fi
done < file.txt

2
Đừng sử dụng readcác vòng lặp để xử lý các tệp văn bản lớn. bash phải đọc từng byte một lần để tránh làm quá mức một dòng mới. Bash cũng không nhanh lắm trong việc xử lý văn bản nói chung so với awk. Nếu bạn sử dụng điều này, read -rasẽ tránh ăn gạch chéo trong đầu vào của bạn. Ngoài ra, đừng quên unset llist sau vòng lặp, nếu bạn đặt nó trong hàm shell hoặc sử dụng nó một cách tương tác.
Peter Cordes

2
@PeterCordes, hoặc bạn có thể vừa tham khảo điều này :-)
iruvar
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.