2010-08-10 81 views
8

Tôi có tệp csv mà tôi muốn xóa các hàng trùng lặp, nhưng nó quá lớn để vừa với bộ nhớ. Tôi đã tìm ra cách để hoàn thành nó, nhưng tôi đoán đó không phải là cách tốt nhất.Xóa các hàng trùng lặp khỏi một tệp lớn trong Python

Mỗi hàng chứa 15 trường và vài trăm ký tự và tất cả các trường là cần thiết để xác định tính duy nhất. Thay vì so sánh toàn bộ hàng để tìm một bản sao, tôi so sánh hash(row-as-a-string) để tiết kiệm bộ nhớ. Tôi đặt một bộ lọc phân vùng dữ liệu thành một số hàng gần bằng nhau (ví dụ: ngày trong tuần) và mỗi phân vùng đủ nhỏ để bảng tra cứu giá trị băm cho phân vùng đó sẽ vừa với bộ nhớ. Tôi đi qua các tập tin một lần cho mỗi phân vùng, kiểm tra đối với hàng độc đáo và viết chúng ra vào một tập tin thứ hai (pseudo code):

import csv 

headers={'DayOfWeek':None, 'a':None, 'b':None} 
outs=csv.DictWriter(open('c:\dedupedFile.csv','wb') 
days=['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] 

outs.writerows(headers) 

for day in days: 
    htable={} 
    ins=csv.DictReader(open('c:\bigfile.csv','rb'),headers) 
    for line in ins: 
     hvalue=hash(reduce(lambda x,y:x+y,line.itervalues())) 
     if line['DayOfWeek']==day: 
      if hvalue in htable: 
       pass 
      else: 
       htable[hvalue]=None 
       outs.writerow(line) 

Một cách tôi đã suy nghĩ để tăng tốc độ này lên được bằng cách tìm một bộ lọc tốt hơn để giảm số lượng đường chuyền cần thiết. Giả sử chiều dài của các hàng được phân bố đều, có lẽ thay vì

for day in days: 

if line['DayOfWeek']==day: 

chúng tôi có

for i in range(n): 

if len(reduce(lambda x,y:x+y,line.itervalues())%n)==i: 

nơi 'n' như nhỏ như bộ nhớ sẽ cho phép. Nhưng điều này vẫn còn sử dụng cùng một phương pháp.

Wayne Werner cung cấp giải pháp thiết thực tốt bên dưới; Tôi đã tò mò nếu có cách tốt hơn/nhanh hơn/đơn giản hơn để thực hiện điều này từ góc độ thuật toán.

P.S. Tôi bị giới hạn ở Python 2.5.

+0

Các hàng đầu ra của bạn có cần theo thứ tự giống như trong tệp đầu vào hay không ? Bạn có mong đợi nhiều lần lặp lại hay kích thước của tệp đầu ra sẽ giữ được nhiều hoặc ít hơn cùng một mức độ lớn như của tệp đầu vào (hoặc không thể dự đoán được)? – rbp

+0

Thứ tự của các hàng trong tệp đầu ra không quan trọng. Đối với trường hợp cụ thể này có tương đối ít bản sao. Bạn có nghĩ số lượng bản sao có mang trong trường hợp chung không? Ví dụ: – JonC

+2

Ví dụ: các hàng duy nhất có thể vừa với bộ nhớ (ngay cả khi tệp đầy đủ, trùng lặp, sẽ không). Tôi phải đi một lúc, nhưng tôi sẽ đưa ra gợi ý sau. – rbp

Trả lời

10

Nếu bạn muốn có một cách thực sự đơn giản để làm điều này, chỉ cần tạo một cơ sở dữ liệu SQLite:

import sqlite3 
conn = sqlite3.connect('single.db') 
cur = conn.cursor() 
cur.execute("""create table test(
f1 text, 
f2 text, 
f3 text, 
f4 text, 
f5 text, 
f6 text, 
f7 text, 
f8 text, 
f9 text, 
f10 text, 
f11 text, 
f12 text, 
f13 text, 
f14 text, 
f15 text, 
primary key(f1, f2, f3, f4, f5, f6, f7, 
      f8, f9, f10, f11, f12, f13, f14, f15)) 
""" 
conn.commit() 

#simplified/pseudo code 
for row in reader: 
    #assuming row returns a list-type object 
    try: 
     cur.execute('''insert into test values(?, ?, ?, ?, ?, ?, ?, 
         ?, ?, ?, ?, ?, ?, ?, ?)''', row) 
     conn.commit() 
    except IntegrityError: 
     pass 

conn.commit() 
cur.execute('select * from test') 

for row in cur: 
    #write row to csv file 

Sau đó, bạn sẽ không phải lo lắng về bất kỳ logic so sánh chính mình - chỉ để nâng niu mang sqlite của nó cho bạn. Nó có thể sẽ không nhanh hơn nhiều so với băm dây, nhưng nó có thể dễ dàng hơn nhiều. Tất nhiên bạn sẽ sửa đổi loại được lưu trữ trong cơ sở dữ liệu nếu bạn muốn, hoặc không phải là trường hợp có thể được. Tất nhiên vì bạn đã chuyển đổi dữ liệu thành chuỗi nên bạn chỉ có thể có một trường. Rất nhiều tùy chọn ở đây.

+0

Cảm ơn WW. Đây là một câu trả lời tốt, thực tế mà tôi sẽ upvote khi danh tiếng của tôi đủ cao. Tôi tò mò về giải pháp lý thuyết ... "Ồ, sử dụng thuật toán này với những cấu trúc dữ liệu này!" Tôi sẽ chỉnh sửa bài đăng để phản ánh điều này. – JonC

+0

+1 Nếu nó không phù hợp với bộ nhớ, nó sẽ không phù hợp với bộ nhớ :) Vì vậy, bạn sẽ phải lưu trữ kết quả của bạn trên đĩa! SQLite sẽ lập chỉ mục dữ liệu của bạn để nó sẽ là FASSSTTTT. –

+4

Cân nhắc sử dụng thông số SQLITE ... 'cur.execute (" chèn vào giá trị XXX (?,?,?,?,?) ", (1,2,3,4,5))' –

5

Về cơ bản, bạn đang thực hiện sắp xếp hợp nhất và xóa các mục nhập trùng lặp.

Phân tách đầu vào thành các phần có kích thước bộ nhớ, sắp xếp từng phần, sau đó hợp nhất các phần trong khi xóa trùng lặp là ý tưởng âm thanh nói chung.

Trên thực tế, cho đến một vài hợp đồng biểu diễn tôi sẽ cho phép hệ thống bộ nhớ ảo xử lý nó và chỉ cần viết:

input = open(infilename, 'rb') 
output = open(outfile, 'wb') 

for key, group in itertools.groupby(sorted(input)): 
    output.write(key) 
2

phương pháp hiện tại của bạn không được bảo đảm để làm việc đúng cách.

Thứ nhất, có xác suất nhỏ mà hai dòng thực sự khác nhau có thể tạo ra cùng một giá trị băm.hash(a) == hash(b) không luôn luôn có nghĩa rằng a == b

Thứ hai, bạn đang làm cho xác suất cao hơn với "giảm/lambda" bạch hoa của bạn:

>>> reduce(lambda x,y: x+y, ['foo', '1', '23']) 
'foo123' 
>>> reduce(lambda x,y: x+y, ['foo', '12', '3']) 
'foo123' 
>>> 

BTW, sẽ không "" .join ([ 'foo', '1', '23']) có phần rõ ràng hơn?

BTW2, tại sao không sử dụng số set thay vì dict cho htable?

Dưới đây là một giải pháp thực hiện: có được "utils lõi" gói từ trang GnuWin32, và cài đặt nó. Sau đó:

  1. viết một bản sao của tập tin của bạn mà không đề để (nói) infile.csv
  2. c:\gnuwin32\bin\sort --unique -ooutfile.csv infile.csv
  3. đọc outfile.csv và viết một bản sao với các tiêu đề prepended

Đối mỗi bước 1 & 3, bạn có thể sử dụng tập lệnh Python hoặc một số tiện ích GnuWin32 khác (đầu, đuôi, tee, mèo, ...).

+0

Ah, cảm ơn vì đã đánh bắt tôi trong vụ va chạm đó "caper". Một điểm tốt.Kiểm tra thành viên có nhanh hơn trong một bộ hơn là trong một dict không? – JonC

+0

Theo như tôi biết không có lý do gì để mong đợi sự khác biệt đáng kể về tốc độ kiểm tra thành viên. Chi phí của việc tạo ra giá trị băm bằng cách sử dụng mã Python thay vì mã C có lẽ là giá trị điều tra nếu bạn có kế hoạch tồn tại với phương pháp ban đầu của bạn. –

1

Giải pháp ban đầu của bạn hơi không chính xác: bạn có thể có các dòng khác nhau băm cùng một giá trị (một va chạm băm) và mã của bạn sẽ để lại một trong số chúng.

Xét về độ phức tạp của thuật toán, nếu bạn đang mong đợi tương đối ít bản sao, tôi nghĩ giải pháp nhanh nhất là quét từng dòng tệp, thêm băm của mỗi dòng (như bạn đã làm), nhưng cũng lưu trữ vị trí của dòng đó. Sau đó, khi bạn gặp phải một băm trùng lặp, tìm kiếm vị trí ban đầu để đảm bảo rằng nó là một bản sao và không chỉ là một va chạm băm, và nếu như vậy, tìm kiếm trở lại và bỏ qua dòng. Bằng cách này, nếu các giá trị CSV được chuẩn hóa (tức là, các bản ghi được coi là bằng nhau, các hàng CSV tương ứng là byte-byte tương đương), bạn không cần phải phân tích cú pháp CSV ở đây, chỉ cần xử lý văn bản thuần túy dòng.

0

Vì tôi cho rằng bạn sẽ phải thực hiện điều này một cách thường xuyên (hoặc bạn đã tấn công kịch bản một lần) và bạn đã đề cập đến bạn đã quan tâm đến giải pháp lý thuyết, đây là một khả năng.

Đọc các dòng nhập vào B-Trees, được sắp xếp theo giá trị băm của mỗi dòng đầu vào, ghi chúng vào đĩa khi bộ nhớ đầy. Chúng tôi cẩn thận để lưu trữ, trên B-Trees, các dòng ban đầu gắn liền với băm (như một tập hợp, vì chúng tôi chỉ quan tâm đến các dòng duy nhất). Khi chúng ta đọc một phần tử trùng lặp, chúng ta kiểm tra các dòng được đặt trên phần tử được lưu trữ và thêm nó nếu đó là một dòng mới xảy ra với giá trị băm cho cùng một giá trị.

Tại sao chọn B-Trees? Họ yêu cầu ít đĩa đọc khi bạn chỉ có thể (hoặc muốn) để đọc các phần của chúng vào bộ nhớ. Mức độ (số lượng trẻ em) trên mỗi nút phụ thuộc vào bộ nhớ có sẵn và số dòng, nhưng bạn không muốn có quá nhiều nút.

Khi chúng tôi có B-Trees đó trên đĩa, chúng tôi so sánh phần tử thấp nhất với mỗi phần tử. Chúng tôi loại bỏ thấp nhất của tất cả, từ tất cả B-Trees có nó. Chúng tôi hợp nhất các tập hợp các dòng của chúng, có nghĩa là chúng tôi không còn bản sao nào cho các dòng đó (và chúng tôi cũng không có thêm dòng nào có giá trị băm đó). Sau đó chúng tôi viết các dòng từ sự hợp nhất này vào cấu trúc csv đầu ra.

Chúng tôi có thể tách một nửa bộ nhớ để đọc B-Trees và một nửa để giữ đầu ra csv trong bộ nhớ trong một thời gian. Chúng tôi tuôn ra csv để đĩa khi một nửa của nó là đầy đủ, phụ thêm vào bất cứ điều gì đã được viết. Mỗi B-Tree chúng ta đọc được bao nhiêu trên mỗi bước có thể được tính toán gần bằng (available_memory/2)/number_of_btrees, được làm tròn để chúng ta đọc các nút đầy đủ.

Trong giả Python:

ins = DictReader(...) 
i = 0 
while ins.still_has_lines_to_be_read(): 
    tree = BTree(i) 
    while fits_into_memory: 
     line = ins.readline() 
     tree.add(line, key=hash) 
    tree.write_to_disc() 
    i += 1 
n_btrees = i 

# At this point, we have several (n_btres) B-Trees on disk 
while n_btrees: 
    n_bytes = (available_memory/2)/n_btrees 
    btrees = [read_btree_from_disk(i, n_bytes) 
       for i in enumerate(range(n_btrees))] 
    lowest_candidates = [get_lowest(b) for b in btrees] 
    lowest = min(lowest_candidates) 
    lines = set() 
    for i in range(number_of_btrees): 
     tree = btrees[i] 
     if lowest == lowest_candidates[i]: 
      node = tree.pop_lowest() 
      lines.update(node.lines) 
     if tree.is_empty(): 
     n_btrees -= 1 

    if output_memory_is_full or n_btrees == 0: 
     outs.append_on_disk(lines) 
0

Làm thế nào về việc sử dụng mô-đun heapq để đọc mảnh tập tin lên đến giới hạn bộ nhớ và viết chúng ra các mảnh được sắp xếp (heapq giữ mọi thứ luôn trong thứ tự sắp xếp).

Hoặc bạn có thể nắm bắt từ đầu tiên trong dòng và chia tệp thành từng phần theo cách đó. Sau đó, bạn có thể đọc các dòng (có thể làm '' .join (line.split()) để thống nhất khoảng cách/tab trong dòng nếu nó là OK để thay đổi khoảng cách) trong thiết lập theo thứ tự chữ cái xóa các thiết lập giữa các miếng (bộ loại bỏ trùng lặp) để sắp xếp mọi thứ (tập hợp không theo thứ tự, nếu bạn muốn bạn có thể đọc vào heap và ghi ra để có thứ tự sắp xếp, lần xuất hiện cuối cùng trong tập thay thế giá trị cũ khi bạn đi.) Hoặc bạn cũng có thể sắp xếp và xóa các dòng trùng lặp với giải pháp nhóm của Joe Koberg. Cuối cùng, bạn có thể ghép các mảnh lại với nhau (bạn có thể viết bài khi bạn đi từng mảnh thành tập cuối cùng trong khi sắp xếp các mẩu)

Các vấn đề liên quan