2012-03-17 37 views
8

Tôi đang tìm quốc gia theo dải ip cho hàng chục triệu hàng. Tôi đang tìm cách tìm kiếm nhanh hơn.Cách nhanh hơn để tra cứu giá trị trong danh sách bộ dữ liệu là gì?

Tôi có 180K bản ghi trong mẫu đơn này:

>>> data = ((0, 16777215, 'ZZ'), 
...   (1000013824, 1000079359, 'CN'), 
...   (1000079360, 1000210431, 'JP'), 
...   (1000210432, 1000341503, 'JP'), 
...   (1000341504, 1000603647, 'IN')) 

(Các số nguyên được địa chỉ ip chuyển đổi thành số đơn giản.)

này không được công việc đúng, nhưng chỉ mất quá lâu:

>>> ip_to_lookup = 999 
>>> country_result = [country 
...     for (from, to, country) in data 
...     if (ip_to_lookup >= from) and 
...      (ip_to_lookup <= to)][0] 
>>> print country_result 
ZZ 

Có ai có thể chỉ cho tôi đúng hướng để có cách nhanh hơn để thực hiện tra cứu này không? Sử dụng phương pháp trên, 100 lần tra cứu mất 3 giây. Có nghĩa là, tôi nghĩ, 10 triệu hàng sẽ mất vài ngày.

+1

Đầu tiên rõ ràng vi tối ưu hóa: 'country_result = tiếp theo (nước cho từ, đến, nước trong dữ liệu nếu ip_to_lookup> = từ và ip_to_lookup <= để)' – agf

+0

Thứ hai: Sắp xếp các 'dữ liệu' vì vậy bạn chỉ cần để kiểm tra giới hạn dưới - ngay sau khi bạn vượt qua nó, bạn đang ở trong phạm vi bên phải. – agf

+0

'từ' là từ khóa và không thể được sử dụng làm tên biến. –

Trả lời

8

Bạn có thể sử dụng các mô-đun bisect để thực hiện tìm kiếm nhị phân sau khi bạn sắp xếp các dữ liệu:

from operator import itemgetter 
import bisect 

data = ((0, 16777215, 'ZZ'), (1000013824, 1000079359, 'CN'), (1000079360, 1000210431, 'JP'), (1000210432, 1000341503, 'JP'), (1000341504, 1000603647, 'IN')) 
sorted_data = sorted(data, key=itemgetter(0)) 
lower_bounds = [lower for lower,_,_ in data] 

def lookup_ip(ip): 
    index = bisect.bisect(lower_bounds, ip) - 1 
    if index < 0: 
     return None 
    _, upper, country = sorted_data[index] 
    return country if ip <= upper else None 

print(lookup_ip(-1))   # => None 
print(lookup_ip(999))   # => 'ZZ' 
print(lookup_ip(16777216)) # => None 
print(lookup_ip(1000013824)) # => 'CN' 
print(lookup_ip(1000400000)) # => 'IN' 

Sự phức tạp thuật toán của việc tra cứu là O(log n) đây, thay vì O(n) cho một danh sách đi bộ hoàn chỉnh.

+0

Tôi đã làm việc trên một số hàm bao cho loại điều này, để tạo ra sự trừu tượng của ánh xạ với (có thể nửa vô hạn ở một trong hai đầu) phạm vi không trùng lặp cho các khóa thay vì các giá trị riêng lẻ. –

+0

Cảm ơn bạn Niklas, điều đó thực sự hữu ích. Nút cổ chai không còn là tra cứu nữa. Tôi có một nút cổ chai mới: tải 17million hàng vào bộ nhớ để thực hiện tra cứu. Tôi đang chia nó thành các khối 8.000 hàng, thực hiện tra cứu và tạo thành một câu lệnh chèn lớn. Chèn nhanh. Nhưng việc nhận các hàng để xử lý vào bộ nhớ là chậm. Nó sẽ nhận được công việc làm vào buổi sáng. Nhưng tôi tò mò nếu tôi làm điều gì sai. Mất 15-20 giây để tải trong 8000 hàng. – exzackley

+0

@ user567784: Đây là một vấn đề hoàn toàn không liên quan. Vui lòng xem xét chấp nhận một trong các câu hỏi ở đây và tạo một câu hỏi mới cho vấn đề mới của bạn. –

1

Giả sử tình huống của bạn đáp ứng một số yêu cầu, có một cách để tăng độ phức tạp của thời gian chạy lên O(1), nhưng độ phức tạp của không gian bị ảnh hưởng.

  1. Dữ liệu phải tĩnh; tất cả dữ liệu phải được xử lý trước bất kỳ lần tra cứu nào.
  2. Với IP tùy ý, phải có cách xác định significant octets.
  3. Phải có đủ không gian để thêm khóa cho mọi giá trị quan trọng cho mỗi quốc gia.

Dưới đây là triển khai rất ngây thơ. Nó chọn hai octet đầu tiên của IP là quan trọng không có vấn đề gì, sau đó ghép các octet quan trọng thành các số nguyên và gia tăng thêm một khóa cho mỗi giá trị giữa mininum và maximum. Như bạn có thể nói, có nhiều chỗ để cải thiện.

from socket import inet_ntoa 
from struct import pack 

data = ((0,    16777215, 'ZZ'), 
     (1000013824, 1000079359, 'CN'), 
     (1000079360, 1000210431, 'JP'), 
     (1000210432, 1000341503, 'JP'), 
     (1000341504, 1000603647, 'IN')) 

def makedict(minip, maxip, country): 
    d = {} 
    for i in xrange(key(minip), key(maxip)+1): 
     d[i] = country 
    return d 

def key(ip): 
    octets = inet_ntoa(pack('>L', ip)).split('.') 
    return int("".join(octets[0:2])); 

lookup = {} 
for lo, hi, cnt in data: 
    lookup.update(makedict(lo, hi, cnt)) 

print lookup[key(999)]   # ZZ 
print lookup[key(1000215555)] # JP 
+0

Điều này có thể được thực hiện chung hơn một chút bằng cách giữ một danh sách các quốc gia có thể cùng với các phạm vi liên quan của chúng bên trong dict, thay vì một mã quốc gia duy nhất. Nếu một chìa khóa được tra cứu, người ta có thể chỉ cần đi bộ (hy vọng không quá dài) danh sách các quốc gia và tìm thấy một chính xác. Ngoài ra, chức năng 'key' của bạn có thể được cải thiện thành' (ip & 0xffff0000) >> 16', mà không cần phải giải nén cấu trúc. –

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