2010-10-02 54 views
28

Làm cách nào để kiểm tra xem danh sách có chứa danh sách khác hay không (nghĩa là đó là một chuỗi). Giả sử có một chức năng gọi là chứa:Kiểm tra xem danh sách có chứa danh sách khác với Python

contains([1,2], [-1, 0, 1, 2]) # Returns [2, 3] (contains returns [start, end]) 
contains([1,3], [-1, 0, 1, 2]) # Returns False 
contains([1, 2], [[1, 2], 3) # Returns False 
contains([[1, 2]], [[1, 2], 3]) # Returns [0, 0] 

Edit:

contains([2, 1], [-1, 0, 1, 2]) # Returns False 
contains([-1, 1, 2], [-1, 0, 1, 2]) # Returns False 
contains([0, 1, 2], [-1, 0, 1, 2]) # Returns [1, 3] 
+3

Đối với những gì nó có giá trị, trở về '[bắt đầu, kết thúc + 1]' là pythonic hơn vì nó trông giống như một lát - '(end + 1) -start' cho chiều dài của những gì được tìm thấy . –

+9

Điều này trông giống như một thiết kế xấu - đôi khi hàm trả về một bool, đôi khi nó trả về một danh sách. Điều đó làm cho nó rất khó sử dụng vì bạn phải kiểm tra kiểu trả về trước khi bạn có thể làm bất cứ điều gì với kết quả. IMHO một hàm có tên "contains" chỉ trả về True hoặc False. –

+1

Thật đáng buồn khi các danh sách không có chức năng cần thiết tích hợp sẵn, nhưng các chuỗi làm ('str.find'). –

Trả lời

13

Đây là phiên bản của tôi:

def contains(small, big): 
    for i in xrange(len(big)-len(small)+1): 
     for j in xrange(len(small)): 
      if big[i+j] != small[j]: 
       break 
     else: 
      return i, i+len(small) 
    return False 

Nó trả về một tuple của (bắt đầu, kết thúc + 1) kể từ khi tôi nghĩ rằng đó là pythonic hơn, như Andrew Jaffe chỉ ra trong bình luận của mình. Nó không cắt bất kỳ danh sách con nào nên có hiệu quả hợp lý.

Một điểm đáng quan tâm đối với người mới sử dụng là nó sử dụng else clause on the for statement - đây không phải là điều tôi thường sử dụng nhưng có thể vô giá trong các tình huống như thế này.

Điều này giống hệt với việc tìm kiếm các chất nền trong một chuỗi, vì vậy đối với các danh sách lớn, việc thực hiện một cái gì đó giống như Boyer-Moore algorithm có hiệu quả hơn.

+0

+1 cho lưu ý về thuật toán tìm kiếm chuỗi hiệu quả. Một bất lợi của bạn là việc bổ sung một vòng lặp bên trong giải thích (so sánh slice là, tôi tưởng tượng, nhanh hơn, mặc dù bản sao có thể bù đắp điều đó). Tôi sẽ thử so sánh hiệu suất. –

+1

Sau khi thử nghiệm thêm, máy của bạn * là * tốt nhất cho đến nay cho các chuỗi lớn. Tôi sẽ chọn điều này, mặc dù những bất lợi nhỏ là có trên các tập dữ liệu nhỏ hơn. –

+2

+1: Không biết về mệnh đề 'else' của' for'! Chỉ cần hôm nay tôi tạo ra một cấu trúc khó xử liên quan đến việc thiết lập một boolean để làm chính xác điều này. – mk12

17

Nếu tất cả các mục là duy nhất, bạn có thể sử dụng bộ.

>>> items = set([-1, 0, 1, 2]) 
>>> set([1, 2]).issubset(items) 
True 
>>> set([1, 3]).issubset(items) 
False 
+1

đó không phải là điều op đang tìm kiếm – SilentGhost

+0

Nó sẽ hoạt động cho các danh sách duy nhất. Trong thực tế nó sẽ làm việc cho các mục không độc đáo, nhưng nó sẽ không thể xác định giữa các mục riêng lẻ. (vì vậy bạn không thể so sánh giữa [1, 1, 2] và [1, 2].) –

+0

OK, đó là lý do tại sao tôi đã sử dụng vòng loại "nếu tất cả các mục là duy nhất". Tôi đã không nhận ra, như ví dụ của bạn đã không làm cho nó rõ ràng rằng bạn cần phân biệt giữa các mục giống hệt nhau. –

3

Sau khi chỉnh sửa OP của:

def contains(small, big): 
    for i in xrange(1 + len(big) - len(small)): 
     if small == big[i:i+len(small)]: 
      return i, i + len(small) - 1 
    return False 
+0

Nhưng nó không có chứa ([1,2], [-1, 0, 1, 1, 2]) mà trả về [2,4] thay vì những gì tôi giả định là dự kiến ​​[3,4] –

+0

Bây giờ nó hoạt động với tất cả các bài kiểm tra của OP. – eumiro

+2

Điều này sẽ có hiệu quả khủng khiếp đối với các danh sách lớn, vì nó liên tục tạo và phá hủy các danh sách tạm thời mỗi khi nó thực hiện 'big [i: i + len (nhỏ)]' –

1

này hoạt động và khá nhanh kể từ khi nó hiện tìm kiếm tuyến tính bằng cách sử dụng được xây dựng trong list.index() phương pháp và == điều hành:

def contains(sub, pri): 
    M, N = len(pri), len(sub) 
    i, LAST = 0, M-N+1 
    while True: 
     try: 
      found = pri.index(sub[0], i, LAST) # find first elem in sub 
     except ValueError: 
      return False 
     if pri[found:found+N] == sub: 
      return [found, found+N-1] 
     else: 
      i = found+1 
0

Tôi cố gắng để thực hiện điều này hiệu quả càng tốt.

Sử dụng trình tạo; những người không quen thuộc với những con thú này nên kiểm tra their documentation và của yield expressions.

Về cơ bản, nó tạo ra một trình tạo các giá trị từ chuỗi có thể được đặt lại bằng cách gửi nó một giá trị thực. Nếu máy phát lại được đặt lại, nó sẽ bắt đầu lại từ đầu sub.

Sau đó, nó chỉ so sánh các giá trị kế tiếp của sequence với năng suất máy phát, đặt lại trình tạo nếu chúng không khớp.

Khi máy phát hết giá trị, nghĩa là đến cuối sub mà không bị đặt lại, điều đó có nghĩa là chúng tôi đã tìm thấy kết quả phù hợp.

Vì nó hoạt động cho bất kỳ chuỗi nào, bạn thậm chí có thể sử dụng nó trên chuỗi, trong trường hợp nó hoạt động tương tự như str.find, ngoại trừ nó trả về False thay vì -1.

Lưu ý thêm: Tôi nghĩ rằng giá trị thứ hai của bộ trả về phải phù hợp với tiêu chuẩn Python, thường là cao hơn. tức là "string"[0:2] == "st". Nhưng spec nói khác, vì vậy đó là cách làm việc này.

Nó phụ thuộc vào việc này có phải là một thói quen có mục đích chung hay không hoặc nó có thực hiện một số mục tiêu cụ thể hay không; trong trường hợp sau nó có thể tốt hơn để thực hiện một thói quen có mục đích chung và sau đó bọc nó trong một chức năng mà twiddles giá trị trả lại cho phù hợp với spec.

def reiterator(sub): 
    """Yield elements of a sequence, resetting if sent ``True``.""" 
    it = iter(sub) 
    while True: 
     if (yield it.next()): 
      it = iter(sub) 

def find_in_sequence(sub, sequence): 
    """Find a subsequence in a sequence. 

    >>> find_in_sequence([2, 1], [-1, 0, 1, 2]) 
    False 
    >>> find_in_sequence([-1, 1, 2], [-1, 0, 1, 2]) 
    False 
    >>> find_in_sequence([0, 1, 2], [-1, 0, 1, 2]) 
    (1, 3) 
    >>> find_in_sequence("subsequence", 
    ...     "This sequence contains a subsequence.") 
    (25, 35) 
    >>> find_in_sequence("subsequence", "This one doesn't.") 
    False 

    """ 
    start = None 
    sub_items = reiterator(sub) 
    sub_item = sub_items.next() 
    for index, item in enumerate(sequence): 
     if item == sub_item: 
      if start is None: start = index 
     else: 
      start = None 
     try: 
      sub_item = sub_items.send(start is None) 
     except StopIteration: 
      # If the subsequence is depleted, we win! 
      return (start, index) 
    return False 
+0

Một nỗ lực dũng cảm, nhưng điều này có hiệu suất tồi tệ hơn so với các giải pháp của eumiro hoặc Dave Kirby. 8.2 trên điểm chuẩn tôi đã mô tả trong các nhận xét khác. –

+0

Thú vị khi thấy sự khác biệt về tốc độ đối với mã gốc. Dường như thuật toán này sẽ tương đối nhanh hơn đối với các chuỗi dài hơn .. bao lâu là/là (các) kết quả bạn đã sử dụng trong bài kiểm tra? – intuited

+0

Bạn nói đúng. Điều này thực hiện tốt hơn nhiều so với giải pháp của eumiro với các chuỗi lớn hơn, nhưng nó vẫn không hoạt động tốt như của Dave. –

1

Dưới đây là một thuật toán đơn giản có sử dụng phương pháp danh sách:

#!/usr/bin/env python 

def list_find(what, where): 
    """Find `what` list in the `where` list. 

    Return index in `where` where `what` starts 
    or -1 if no such index. 

    >>> f = list_find 
    >>> f([2, 1], [-1, 0, 1, 2]) 
    -1 
    >>> f([-1, 1, 2], [-1, 0, 1, 2]) 
    -1 
    >>> f([0, 1, 2], [-1, 0, 1, 2]) 
    1 
    >>> f([1,2], [-1, 0, 1, 2]) 
    2 
    >>> f([1,3], [-1, 0, 1, 2]) 
    -1 
    >>> f([1, 2], [[1, 2], 3]) 
    -1 
    >>> f([[1, 2]], [[1, 2], 3]) 
    0 
    """ 
    if not what: # empty list is always found 
     return 0 
    try: 
     index = 0 
     while True: 
      index = where.index(what[0], index) 
      if where[index:index+len(what)] == what: 
       return index # found 
      index += 1 # try next position 
    except ValueError: 
     return -1 # not found 

def contains(what, where): 
    """Return [start, end+1] if found else empty list.""" 
    i = list_find(what, where) 
    return [i, i + len(what)] if i >= 0 else [] #NOTE: bool([]) == False 

if __name__=="__main__": 
    import doctest; doctest.testmod() 
0

Tôi nghĩ rằng đây là một trong nhanh ...

def issublist(subList, myList, start=0): 
    if not subList: return 0 
    lenList, lensubList = len(myList), len(subList) 
    try: 
     while lenList - start >= lensubList: 
      start = myList.index(subList[0], start) 
      for i in xrange(lensubList): 
       if myList[start+i] != subList[i]: 
        break 
      else: 
       return start, start + lensubList - 1 
      start += 1 
     return False 
    except: 
     return False 
3

Có thể tôi một cách khiêm nhường đề nghị Rabin-Karp algorithm nếu danh sách big là thực sự lớn. Liên kết thậm chí còn chứa mã gần như có thể sử dụng trong hầu hết Python.

1

Nếu chúng ta tinh chỉnh các vấn đề nói về việc kiểm tra xem một danh sách chứa một danh sách với như là một chuỗi, câu trả lời có thể là sau một liner:

def contains(subseq, inseq): 
    return any(inseq[pos:pos + len(subseq)] == subseq for pos in range(0, len(inseq) - len(subseq) + 1)) 

đây kiểm tra đơn vị tôi đã sử dụng để điều chỉnh lên một này -liner:

https://gist.github.com/anonymous/6910a85b4978daee137f

+0

Tôi nghĩ đây là câu trả lời cho tôi! tốt đẹp một lót, trông đúng. – hwjp

+1

tình cờ mặc dù, tôi nghĩ rằng 'return True' có thể vượt qua tất cả các bài kiểm tra đơn vị. – hwjp

0

đang nhỏ nhất:

def contains(a,b): 
    str(a)[1:-1].find(str(b)[1:-1])>=0 
0

Đây là giải pháp của tôi. Chức năng này sẽ giúp bạn tìm hiểu xem B là một danh sách phụ của A. Độ phức tạp thời gian là O (n).

def does_A_contain_B(A, B): #remember now A is the larger list b_size = len(B) for a_index in range(0, len(A)): if A[a_index : a_index+b_size]==B: return True else: return False

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