【问题标题】:Find Consecutive Repeats of Specific Length in NumPy在 NumPy 中查找特定长度的连续重复
【发布时间】:2020-04-26 23:38:25
【问题描述】:

假设我有一个 NumPy 数组:

a = np.array([0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 9, 10, 11, 12, 13, 13, 13, 14, 15])

我有一个用户指定的长度m = 2,以便查看时间序列中是否有该长度的任何重复。在这种情况下,长度为m = 2 的重复是:

[2, 2]
[5, 5]
[9, 9]
[9, 9]
[13, 13]

用户可以将其更改为m = 3,长度为m = 3的重复为:

[9, 9, 9]
[13, 13, 13]

我需要一个函数来返回找到重复的索引或None。因此,对于 m = 3,该函数将返回以下 NumPy 起始索引数组:

[11, 17]

对于m = 4,该函数将返回None。完成此任务的最干净、最快的方法是什么?

更新 请注意,数组不必排序,我们对排序后的结果感兴趣。我们只想要未排序数组的结果。 m = 2 的结果应该与此数组相同:

b = np.array([0, 11, 2, 2, 3, 40, 5, 5, 16, 7, 80, 9, 9, 9, 1, 11, 12, 13, 13, 13, 4, 5])

【问题讨论】:

  • a 是否总是排序?
  • 那么,(2, 6, 11, 12, 17, 18)m=2 吗?
  • 好问题。 a 永远不会排序,是的,这是 m = 2 的返回数组
  • 我们可以先排序吗?
  • 还是无法对数组进行排序?

标签: python numpy


【解决方案1】:

方法#1

我们可以利用 1D convolution 来实现矢量化解决方案 -

def consec_repeat_starts(a, n):
    N = n-1
    m = a[:-1]==a[1:]
    return np.flatnonzero(np.convolve(m,np.ones(N, dtype=int))==N)-N+1

示例运行 -

In [286]: a
Out[286]: 
array([ 0,  1,  2,  2,  3,  4,  5,  5,  6,  7,  8,  9,  9,  9, 10, 11, 12,
       13, 13, 13, 14, 15])

In [287]: consec_repeat_starts(a, 2)
Out[287]: array([ 2,  6, 11, 12, 17, 18])

In [288]: consec_repeat_starts(a, 3)
Out[288]: array([11, 17])

In [289]: consec_repeat_starts(a, 4)
Out[289]: array([], dtype=int64)

方法 #2

我们也可以使用binary-erosion -

from scipy.ndimage.morphology import binary_erosion

def consec_repeat_starts_v2(a, n):
    N = n-1
    m = a[:-1]==a[1:]
    return np.flatnonzero(binary_erosion(m,[1]*N))-(N//2)

【讨论】:

  • 当,@Divakar!同样,您的解决方案简单、优雅,而且我只用几行代码就学到了很多东西。谢谢!
  • 如果输入数组是np.float64,答案是否重要?
  • @slaw 不应该。它会寻找精确的重复项,因此如果您接近浮点数学,则需要在比较时引入容差。
  • 我可能应该在另一个问题中问这个问题,但是在您获得索引后,对于每个重复序列(从每个索引开始),是否有一种快速方法可以找到该重复序列的所有匹配项和返回那些匹配的索引?所以,如果数组是[0, 1, 2, 2, 3, 4, 2, 2, 5, 5, 6, 5, 5, 2, 2]consec_repeat_starts 将返回[2, 6, 8, 11, 13]。然后,对于每种唯一类型的重复序列(即[2, 2][5, 5]),我想返回类似于重复的内容,然后是重复所在位置的索引([2, 2], [2, 6, 13]), ([5, 5], [8, 11])
  • @slaw 是的,这将涉及更多工作。我认为最好提出一个新问题。
【解决方案2】:

我想出了这个解决方案,也许它不是最干净的。

我使用常规数组而不是 numpy 数组,但它应该以相同的方式工作。

a = [0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 9, 10, 11, 12, 13, 13, 13, 14, 15]
m = 2


def index_repeated_length( list, length ) :
    previous = None
    count = 1
    for i, element in enumerate(list) :
        if element == previous :
            count += 1
        else :
            previous = element
            count = 1

        if count >= length :
            yield i - length + 1

print( list( index_repeated_length(a, m) ) )

输出:

[2, 6, 11, 12, 17, 18]

由于它从来没有排序,你必须遍历它,所以复杂度应该是线性 O(n)

【讨论】:

    【解决方案3】:

    我找到了这个,与发布的另一个非常相似,但它应该可以工作。仅当数组已排序时才有效

    def find(a,m):
        index=[]
        prev=0
        n =1
        for i in range(1,len(a)):
            if a[prev] == a[i]:
                n+=1
                if(m==n):
                    index.append(i-m+1)
                    n=1
            else:
                n=1
            prev=i
        return index if len(index)>0 else None
    find(a,3)
    

    【讨论】:

      【解决方案4】:

      如果您只想检查未排序数组中的连续相似值,此方法仍然有效:

      import numpy as np
      
      nums = np.array([11, 0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 9, 10, 11, 12, 13, 13, 13, 14, 15])
      
      m = 3
      
      equals = []
      
      for ii in range(len(nums)):
          if ii >= m:
              sliced = nums[ii-m:ii]
              if np.unique(sliced).size == 1:
                   equals.append(sliced)
      
      print(np.array(equals))
      

      这将给出 m=2 的输出:

      [[ 2  2]
      [ 5  5]
      [ 9  9]
      [ 9  9]
      [13 13]
      [13 13]]
      

      这将给出 m=3 的输出:

      [[ 9  9  9]
      [13 13 13]]
      

      【讨论】:

        【解决方案5】:

        我们也可以使用递归函数:

        def recursive_repeat(arr, k):
            if k == 1:
                return np.flatnonzero(arr)
            else:
                new_arr = np.zeros(arr.size - 1)
                mask = arr[1:] == arr[:-1]
                new_arr[mask] = arr[:-1][mask]
                return recursive_repeat(new_arr, k-1)
        
        
        recursive_repeat(a, 2)
        Out[]: array([ 2,  6, 11, 12, 17, 18], dtype=int64)
        
        recursive_repeat(a, 3)
        Out[]: array([11, 17], dtype=int64)
        

        【讨论】:

          【解决方案6】:

          numbanjit'ed 函数:

          import numpy as np
          import numba as nb
          
          @nb.njit
          def find_repeats(arr, n):
              indices = []
              current = arr[0]
              count = 0
              for idx, item in enumerate(arr[1:]):
                  if item == current:
                      count += 1
                      if count >= n-1:
                          indices.append(idx)
                  else:
                      count = 0
                      current = item
              return indices if indices else None
          
          b = np.array([0, 11, 2, 2, 3, 40, 5, 5, 16, 7, 80, 9, 9, 9, 1, 11, 12, 13, 13, 13, 4, 5])
          
          print(find_repeats(b, 2))
          # [2, 6, 11, 12, 17, 18]
          
          print(find_repeats(b, 3))
          # [12, 18]
          

          不太优雅但速度很快,尤其是对于较小的数组大小 - simple_benchmarkDivakar's functions 的比较:

          【讨论】:

            猜你喜欢
            • 2017-06-02
            • 1970-01-01
            • 1970-01-01
            • 2013-05-19
            • 1970-01-01
            • 2020-10-03
            • 1970-01-01
            • 2014-09-13
            • 1970-01-01
            相关资源
            最近更新 更多