2011-08-17 88 views
15

在Python或NumPy中,找出子數組的第一次出現的最佳方法是什麼?Python/NumPy首次出現的子數組

例如,我有

a = [1, 2, 3, 4, 5, 6] 
b = [2, 3, 4] 

什麼是最快的方法(運行時明智),找出其中b的發生?我對字符串的理解非常簡單,但對於列表或numpy的ndarray呢?

非常感謝!

[編輯]我更喜歡numpy解決方案,因爲從我的經驗來看,numpy向量化比Python列表理解要快得多。與此同時,大數組是巨大的,所以我不想將它轉換成字符串;那會太長。

+0

你可以將列表轉換爲字符串進行比較嗎? 'x =''。join(str(x)for x in a)'然後使用find方法和結果字符串?還是他們必須保持清單? – danem

回答

14

我假設你正在尋找一個numpy特定的解決方案,而不是一個簡單的列表理解或for循環。一種方法可能是使用rolling window技術來搜索適當大小的窗口。這裏的rolling_window功能:

>>> def rolling_window(a, size): 
...  shape = a.shape[:-1] + (a.shape[-1] - size + 1, size) 
...  strides = a.strides + (a. strides[-1],) 
...  return numpy.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) 
... 

然後,你可以做這樣的事情

>>> a = numpy.arange(10) 
>>> numpy.random.shuffle(a) 
>>> a 
array([7, 3, 6, 8, 4, 0, 9, 2, 1, 5]) 
>>> rolling_window(a, 3) == [8, 4, 0] 
array([[False, False, False], 
     [False, False, False], 
     [False, False, False], 
     [ True, True, True], 
     [False, False, False], 
     [False, False, False], 
     [False, False, False], 
     [False, False, False]], dtype=bool) 

爲了使這真的很有用,你必須使用all,以減少它沿軸1:

>>> numpy.all(rolling_window(a, 3) == [8, 4, 0], axis=1) 
array([False, False, False, True, False, False, False, False], dtype=bool) 

然後你可以使用,但是你會使用一個布爾數組。一個簡單的方法來獲得索引列:

>>> bool_indices = numpy.all(rolling_window(a, 3) == [8, 4, 0], axis=1) 
>>> numpy.mgrid[0:len(bool_indices)][bool_indices] 
array([3]) 

對於列表,你可以調整這些rolling window迭代器之一來使用類似的方法。

對於非常大陣列和子陣,你可以節省內存這樣的:

>>> windows = rolling_window(a, 3) 
>>> sub = [8, 4, 0] 
>>> hits = numpy.ones((len(a) - len(sub) + 1,), dtype=bool) 
>>> for i, x in enumerate(sub): 
...  hits &= numpy.in1d(windows[:,i], [x]) 
... 
>>> hits 
array([False, False, False, True, False, False, False, False], dtype=bool) 
>>> hits.nonzero() 
(array([3]),) 

在另一方面,這將可能會比較慢。如果沒有測試,速度慢多少?請參閱Jamie的另一個節省內存選項的答案,該選項必須檢查誤報。我想象這兩種解決方案之間的速度差異將在很大程度上取決於輸入的性質。

+0

這種方法的問題是,雖然'rolling_window'的返回不需要任何新的內存,並且重用了原始數組的內存,當執行'=='操作時,您會實例化一個新的布爾數組乘以原始數組的全長。如果陣列足夠大,這可能會大大縮短性能。 – Jaime

+0

的確如此。實際上,我使用滾動窗口函數的主要目的不是爲了節省內存,而是爲了快速生成所需結構的數組。但我添加了自己的記憶保存解決方案;你的看起來很有希望。我沒有動機測試他們對方! – senderle

2

另一種嘗試,但我敢肯定有更Python & efficent辦法做到這一點......

 
def array_match(a, b): 
    for i in xrange(0, len(a)-len(b)+1): 
     if a[i:i+len(b)] == b: 
      return i 
    return None 
 
a = [1, 2, 3, 4, 5, 6] 
b = [2, 3, 4] 

print array_match(a,b) 
1 

(這第一個答案是不是在這個問題的範圍,作爲cdhowie提到)

set(a) & set(b) == set(b) 
+0

兩個問題:這也會匹配'[1,3,2,4,5,6]'(集合沒有排序;數組是),並且它不報告匹配的位置(它應該是索引1 )。 – cdhowie

+0

是的,我的不好,回答太快: -/ –

+0

你可以通過用'return i'替換'first_occurence = i','用return'返回first_occurence'來簡化你的代碼。 – Nayuki

10

我的第一個答案,但我認爲這應該工作....

[x for x in xrange(len(a)) if a[x:x+len(b)] == b] 

返回模式開始的索引。

+1

這可能不是最快的解決方案,但最簡單的答案是+1。這可能適合許多用戶的需求,尤其是在numpy不可用的情況下。 – David

+0

在Python 3中,使用'range'而不是'xrange'。 – Samoth

6

您可以調用tostring()方法將數組轉換爲字符串,然後您可以使用快速字符串搜索。當你有許多子數組需要檢查時,這種方法可能會更快。

import numpy as np 

a = np.array([1,2,3,4,5,6]) 
b = np.array([2,3,4]) 
print a.tostring().index(b.tostring())//a.itemsize 
13

基於卷積的辦法,應該是更多的內存比stride_tricks基礎的方法有效:

def find_subsequence(seq, subseq): 
    target = np.dot(subseq, subseq) 
    candidates = np.where(np.correlate(seq, 
             subseq, mode='valid') == target)[0] 
    # some of the candidates entries may be false positives, double check 
    check = candidates[:, np.newaxis] + np.arange(len(subseq)) 
    mask = np.all((np.take(seq, check) == subseq), axis=-1) 
    return candidates[mask] 

有了非常大的陣列,它可能無法使用stride_tricks的辦法,但是這一次還是工作原理:

haystack = np.random.randint(1000, size=(1e6)) 
needle = np.random.randint(1000, size=(100,)) 
# Hide 10 needles in the haystack 
place = np.random.randint(1e6 - 100 + 1, size=10) 
for idx in place: 
    haystack[idx:idx+100] = needle 

In [3]: find_subsequence(haystack, needle) 
Out[3]: 
array([253824, 321497, 414169, 456777, 635055, 879149, 884282, 954848, 
     961100, 973481], dtype=int64) 

In [4]: np.all(np.sort(place) == find_subsequence(haystack, needle)) 
Out[4]: True 

In [5]: %timeit find_subsequence(haystack, needle) 
10 loops, best of 3: 79.2 ms per loop 
+0

雖然我非常喜歡這種方法,但我應該注意到,一般來說,通過l2規範找到候選人並不比從針中找到特定符號更好。但通過計算與針長度相同的隨機圖案點產品的小修改後,此方法將非常棒。 – Alleo

2

我知道這是一個很老的問題,但我最近有一個快速和有效的方式和最快的方法(尤其是長AR解決這個我發現是,我想我留在這裏作爲參考:

data = np.array([1, 2, 3, 4, 5, 6]) 
sequence = np.array([3, 4, 5]) 
data.tostring().index(sequence.tostring())//data.itemize 

你必須要小心,數組和序列都有相同的dtype。

1

這裏有一個直接的選項:

def first_subarray(full_array, sub_array): 
    n = len(full_array) 
    k = len(sub_array) 
    matches = np.argwhere([np.all(full_array[start_ix:start_ix+k] == sub_array) 
        for start_ix in range(0, n-k+1)]) 
    return matches[0] 

然後使用原來的A,B的載體,我們得到:

a = [1, 2, 3, 4, 5, 6] 
b = [2, 3, 4] 
first_subarray(a, b) 
Out[44]: 
array([1], dtype=int64) 
+0

你可能會添加一些邏輯來處理沒有匹配的情況。 –

0

創建一個數組(或轉換)這樣

>>> ar = numpy.array([1,2,3,4,5,1,2,8,9,1,2,3,4,6], dtype=str) 
>>> ar.tostring() 
'12345128912346' 
>>> ss.count('123') 
2 
>>> ss.index('123') 
0