2009-05-09 35 views
23

Tôi có một danh sách các dữ liệu có thể có, ví dụ: ['mèo', 'cá', 'chó']. Trong thực tế, danh sách chứa hàng trăm mục nhập.Cách hiệu quả nhất để tìm một trong số một số chất nền trong Python là gì?

Tôi đang xử lý một chuỗi và những gì tôi đang tìm kiếm là tìm chỉ mục xuất hiện đầu tiên của bất kỳ chất nền nào trong số này.

Để làm rõ, cho '012cat' kết quả là 3, và cho '0123dog789cat' kết quả là 4.

Tôi cũng cần phải biết rằng những chuỗi con được tìm thấy (ví dụ như chỉ số của nó trong danh sách chuỗi hoặc văn bản chính nó), hoặc ít nhất là chiều dài của chuỗi con phù hợp.

Có những cách rõ ràng về sức mạnh để đạt được điều này, tôi tự hỏi liệu có bất kỳ giải pháp Python/Regex trang nhã nào cho điều này không.

Cảm ơn, Rax

+1

Danh sách các bản chất có liên tục không? Tôi yêu cầu vì sử dụng các giải pháp loại Regex thường liên quan đến một số precomputations của biểu thức chính quy (rsp. Danh sách các chất nền trong trường hợp của bạn). Liệu rằng precomputation được khấu hao trên nhiều tìm kiếm? – Accipitridae

Trả lời

31

tôi sẽ giả định một regex là tốt hơn so với kiểm tra cho mỗi substring riêng vì khái niệm biểu thức chính quy được mô phỏng như một DFA, và như vậy là đầu vào được tiêu thụ tất cả các trận đấu đang được thử nghiệm cùng một lúc (dẫn đến một lần quét chuỗi đầu vào).

Vì vậy, đây là một ví dụ:

import re 

def work(): 
    to_find = re.compile("cat|fish|dog") 
    search_str = "blah fish cat dog haha" 
    match_obj = to_find.search(search_str) 
    the_index = match_obj.start() # produces 5, the index of fish 
    which_word_matched = match_obj.group() # "fish" 
    # Note, if no match, match_obj is None 

UPDATE: Một số dịch vụ chăm sóc cần được thực hiện khi kết hợp các từ trong một mô hình duy nhất của từ thay thế. Các mã sau đây được xây dựng một regex, nhưng escapes any regex special characters và sắp xếp các từ để từ còn có cơ hội để phù hợp trước khi bất kỳ tiền tố ngắn hơn của cùng một từ:

def wordlist_to_regex(words): 
    escaped = map(re.escape, words) 
    combined = '|'.join(sorted(escaped, key=len, reverse=True)) 
    return re.compile(combined) 

>>> r.search('smash atomic particles').span() 
(6, 10) 
>>> r.search('visit usenet:comp.lang.python today').span() 
(13, 29) 
>>> r.search('a north\south division').span() 
(2, 13) 
>>> r.search('012cat').span() 
(3, 6) 
>>> r.search('0123dog789cat').span() 
(4, 7) 

END CẬP NHẬT

Nó nên lưu ý rằng bạn sẽ muốn tạo thành regex (tức là - gọi lại đến re.compile()) càng ít càng tốt. Trường hợp tốt nhất sẽ là bạn biết trước thời gian tìm kiếm của bạn là gì (hoặc bạn tính toán chúng một lần/không thường xuyên) và sau đó lưu kết quả của re.compile ở đâu đó. Ví dụ của tôi chỉ là một hàm vô nghĩa đơn giản để bạn có thể thấy việc sử dụng regex. Có một số tài liệu regex thêm ở đây:

http://docs.python.org/library/re.html

Hope this helps.

UPDATE: Tôi không chắc chắn về cách python thực hiện biểu thức thông thường, nhưng để trả lời câu hỏi Rax của về việc có hay không có những hạn chế của re.compile() (ví dụ, có bao nhiêu từ bạn có thể cố gắng " | "cùng nhau để phù hợp cùng một lúc), và số lượng thời gian để chạy biên dịch: không phải trong số này có vẻ là một vấn đề. Tôi đã thử mã này, đủ tốt để thuyết phục tôi. (Tôi có thể làm điều này tốt hơn bằng cách thêm thời gian và kết quả báo cáo, cũng như ném danh sách các từ vào một tập hợp để đảm bảo không có bản sao ... nhưng cả hai cải tiến này dường như quá mức cần thiết). Mã này chạy cơ bản ngay lập tức, và thuyết phục tôi rằng tôi có thể tìm kiếm 2000 từ (kích thước 10), và điều đó và của chúng sẽ phù hợp một cách thích hợp.Đây là mã:

import random 
import re 
import string 
import sys 

def main(args): 
    words = [] 
    letters_and_digits = "%s%s" % (string.letters, string.digits) 
    for i in range(2000): 
     chars = [] 
     for j in range(10): 
      chars.append(random.choice(letters_and_digits)) 
     words.append(("%s"*10) % tuple(chars)) 
    search_for = re.compile("|".join(words)) 
    first, middle, last = words[0], words[len(words)/2], words[-1] 
    search_string = "%s, %s, %s" % (last, middle, first) 
    match_obj = search_for.search(search_string) 
    if match_obj is None: 
     print "Ahhhg" 
     return 
    index = match_obj.start() 
    which = match_obj.group() 
    if index != 0: 
     print "ahhhg" 
     return 
    if words[-1] != which: 
     print "ahhg" 
     return 

    print "success!!! Generated 2000 random words, compiled re, and was able to perform matches." 

if __name__ == "__main__": 
    main(sys.argv) 

UPDATE: Cần lưu ý rằng thứ tự của điều ORed với nhau trong regex vấn đề. Hãy xem xét bài kiểm tra sau đây lấy cảm hứng từ TZOTZIOY:

>>> search_str = "01catdog" 
>>> test1 = re.compile("cat|catdog") 
>>> match1 = test1.search(search_str) 
>>> match1.group() 
'cat' 
>>> match1.start() 
2 
>>> test2 = re.compile("catdog|cat") # reverse order 
>>> match2 = test2.search(search_str) 
>>> match2.group() 
'catdog' 
>>> match2.start() 
2 

Điều này cho thấy thứ tự quan trọng: - /. Tôi không chắc điều này có nghĩa gì với ứng dụng của Rax, nhưng ít nhất là hành vi được biết đến.

UPDATE: tôi đăng this questions about the implementation of regular expressions in Python mà hy vọng sẽ cung cấp cho chúng tôi một số cái nhìn sâu sắc vào vấn đề tìm thấy với câu hỏi này.

+0

Điều này chắc chắn hoạt động, nhưng tôi có một câu hỏi - không phải là có một giới hạn về kích thước của định nghĩa regex? Nếu tôi có 1000 chất nền, nó sẽ vẫn hoạt động?Có bất kỳ sự giảm sút hiệu suất đáng kể nào liên quan đến số lượng từ (nghĩa là nó lớn hơn tuyến tính trong kích thước của danh sách) không? Về các giải thích khác của bạn, danh sách các bản chất của tôi đang được cập nhật chỉ một lần một ngày hoặc lâu hơn, tôi nghĩ không có vấn đề gì khi tạo định nghĩa regex và gọi "biên dịch" ở tần số này. Rất cám ơn –

+0

@ rax bạn có thấy giải pháp mới của tôi không? Tôi về cơ bản đã sửa mọi thứ về nó và gửi nó 20 giây sau cái này. – Unknown

+0

@rax: Hy vọng mã ví dụ tôi đã thêm giúp thuyết phục bạn mô-đun sẽ trở lại :-). – Tom

4
subs = ['cat', 'fish', 'dog'] 
sentences = ['0123dog789cat'] 

import re 

subs = re.compile("|".join(subs)) 
def search(): 
    for sentence in sentences: 
     result = subs.search(sentence) 
     if result != None: 
      return (result.group(), result.span()[0]) 

# ('dog', 4) 
+0

Tôi nghĩ rằng anh ta chỉ có 1 "câu" –

+0

Cảm ơn, nhưng đây không phải là những gì tôi đang tìm kiếm. Thứ nhất, nó không tìm thấy sự xuất hiện đầu tiên (trong câu thứ hai nó sẽ trả về sự xuất hiện của "con mèo", tức là 10, thay vì "con chó", tức là 4). Có những giải pháp rõ ràng nhưng nó rất rất mạnh bạo lực (lặp cho đến khi chuỗi cuối cùng và liên tục duy trì sự xuất hiện đầu tiên). Tôi có ấn tượng rằng Python phải có một số chức năng thư viện cho việc này ... –

+0

Tôi không thích khi nào câu trả lời của tôi bị "bắn tỉa" ... nhưng tôi không có ý đánh cắp sấm của bạn. 1 vì giải pháp của bạn về mặt kỹ thuật là chính xác. Hai bình luận: nó không bàn về những lo ngại về khả năng mở rộng mà Rax có, và tôi không thích câu trả lời "return", vì nó sẽ sớm thoát ra nếu bạn có nhiều câu trong câu. Khác hơn thế, nó ngắn và đến mức, và đảm bảo một số danh tiếng. – Tom

2

Đây là một câu trả lời mơ hồ, lý thuyết không có mã được cung cấp, nhưng tôi hy vọng nó có thể hướng bạn đi đúng hướng.

Trước tiên, bạn sẽ cần tìm kiếm hiệu quả hơn cho danh sách chuỗi con của mình. Tôi muốn giới thiệu một số loại cấu trúc cây. Bắt đầu với một gốc, sau đó thêm một nút 'a' nếu bất kỳ chất nền nào bắt đầu bằng 'a', thêm nút 'b' nếu bất kỳ đế nào bắt đầu bằng 'b', v.v. Đối với mỗi nút này, hãy tiếp tục thêm các nút con. Ví dụ, nếu bạn có chuỗi con có từ "kiến", bạn nên có nút gốc, nút con 'a', nút cháu 'n' và nút cháu tuyệt vời 't'.

Các nút phải dễ thực hiện.

class Node(object): 
    children = [] 

    def __init__(self, name): 
     self.name = name 

trong đó name là ký tự.

Lặp lại qua thư dây của bạn bằng chữ cái. Theo dõi bạn đang sử dụng lá thư nào. Tại mỗi chữ cái, hãy thử sử dụng vài chữ cái tiếp theo để đi qua cây. Nếu bạn thành công, số thư của bạn sẽ là vị trí của chuỗi con và thứ tự truyền tải của bạn sẽ cho biết chuỗi con đã được tìm thấy.

Làm rõ chỉnh sửa: DFA phải nhanh hơn nhiều so với phương pháp này và vì vậy tôi phải xác nhận Tom's answer. Tôi chỉ giữ câu trả lời này trong trường hợp danh sách chuỗi con của bạn thay đổi thường xuyên, trong trường hợp này sử dụng cây có thể sẽ nhanh hơn.

+0

Cảm ơn, tôi hoàn toàn hiểu được lý thuyết và thực hành về lập chỉ mục chuỗi và tìm kiếm, và có thể tự thực hiện nó, nhưng tôi hy vọng Python sẽ có một chiếc xe cho điều chính xác này. Tôi hiểu không có gì? –

+0

Tôi không biết chức năng như vậy được xây dựng trong Python, vì vậy tôi không thể nói nó có tồn tại hay không. Như vậy, tôi sợ câu trả lời này không giúp bạn ít nhất. Câu trả lời gần nhất tôi thấy ở đây là của Tom. – Wesley

0

Trước hết, tôi khuyên bạn nên sắp xếp danh sách ban đầu theo thứ tự tăng dần. Vì quét cho chuỗi con ngắn hơn nhanh hơn khi quét chuỗi con dài hơn.

+0

Bạn có chắc chắn điều này tạo nên sự khác biệt? Nếu tôi đã thực hiện regex bản thân mình (như là một DFA), chiều dài sẽ không quan trọng. Mỗi chuỗi con sẽ được tìm kiếm cùng một lúc. Tôi bây giờ tò mò như thế nào python thực hiện regexes ... – Tom

0

Làm thế nào về điều này.

>>> substrings = ['cat', 'fish', 'dog'] 
>>> _string = '0123dog789cat' 
>>> found = map(lambda x: (_string.index(x), x), filter(lambda x: x in _string, substrings)) 
[(10, 'cat'), (4, 'dog')] 
>>> if found: 
>>>  min(found, key=lambda x: x[0]) 
(4, 'dog') 

Rõ ràng, bạn có thể trả lại một thứ khác không phải một bộ.

này hoạt động bằng cách:

  • Lọc danh sách các chuỗi con xuống để những người đang trong chuỗi
  • Xây dựng một danh sách các hàng có chứa các chỉ số của chuỗi, và chuỗi
  • Nếu một đã tìm thấy chuỗi con, tìm giá trị tối thiểu dựa trên chỉ mục
+0

Điều này có vẻ là một câu trả lời không hiệu quả khủng khiếp. Nó chắc chắn sẽ quét chuỗi nhiều lần. Ngay cả một phương pháp tiếp cận vũ lực nơi bạn sử dụng phương thức string index() theo cách thủ công cho mỗi chuỗi bạn đang tìm kiếm (theo dõi mức tối thiểu khi đang bay) là tốt hơn điều này. map() có thể là một hàm mạnh, nhưng đây không phải là ví dụ về một trường hợp như vậy. – Tom

3

Tôi chỉ muốn chỉ ra sự khác biệt về thời gian giữa câu trả lời của DisplacedAussie và câu trả lời của Tom. Cả hai đều nhanh chóng khi sử dụng một lần, vì vậy bạn không nên có bất kỳ chờ đợi đáng chú ý cho cả hai, nhưng khi bạn có thời gian cho họ:

import random 
import re 
import string 

words = [] 
letters_and_digits = "%s%s" % (string.letters, string.digits) 
for i in range(2000): 
    chars = [] 
    for j in range(10): 
     chars.append(random.choice(letters_and_digits)) 
    words.append(("%s"*10) % tuple(chars)) 
search_for = re.compile("|".join(words)) 
first, middle, last = words[0], words[len(words)/2], words[-1] 
search_string = "%s, %s, %s" % (last, middle, first) 

def _search(): 
    match_obj = search_for.search(search_string) 
    # Note, if no match, match_obj is None 
    if match_obj is not None: 
     return (match_obj.start(), match_obj.group()) 

def _map(): 
    search_for = search_for.pattern.split("|") 
    found = map(lambda x: (search_string.index(x), x), filter(lambda x: x in search_string, search_for)) 
    if found: 
     return min(found, key=lambda x: x[0]) 


if __name__ == '__main__': 
    from timeit import Timer 


    t = Timer("_search(search_for, search_string)", "from __main__ import _search, search_for, search_string") 
    print _search(search_for, search_string) 
    print t.timeit() 

    t = Timer("_map(search_for, search_string)", "from __main__ import _map, search_for, search_string") 
    print _map(search_for, search_string) 
    print t.timeit() 

Đầu ra:

(0, '841EzpjttV') 
14.3660159111 
(0, '841EzpjttV') 
# I couldn't wait this long 

tôi sẽ đi với câu trả lời của Tom, cho cả hai khả năng đọc và tốc độ.

+0

Cảm ơn Nick!Trong sự công bằng để DisplacedAussie, bạn có thể giúp anh ta ra (một chút) bằng cách loại bỏ các cuộc gọi để chia ("|") và chỉ cần cung cấp cho anh ta một danh sách để bắt đầu. Để toàn diện hơn, bạn nên thêm phương pháp tiếp cận vũ lực. cho từ trong search_for :, index = search_string.index (word), nếu chỉ mục Tom

+0

+1 để thực sự làm điểm chuẩn trong câu hỏi về hiệu quả! – dbr

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