【问题标题】:Find consecutive sequences based on Boolean array基于布尔数组查找连续序列
【发布时间】:2019-03-26 07:16:40
【问题描述】:

我正在尝试从数组 b 中提取序列,其中布尔数组 a 用作索引(len(a) >= len(b),但 (a==True).sum() == len(b),即有a 中为真的值仅与 b 中的元素一样多)。序列应在结果中表示为 a 的开始和结束索引,其中a[i] 为真,并且存在连续值。

例如,对于以下 ab

的数组
a = np.asarray([True, True, False, False, False, True, True, True, False])
b = [1, 2, 3, 4, 5]

结果应该是[((0, 1), [1, 2]), ((5, 7), [3, 4, 5])],因此数组中的元素与真实序列一样多。每个真正的序列都应该包含 a 的开始和结束索引以及这些与 b 相关的值。

所以对于上面的:

[
 ((0, 1), [1, 2]),   # first true sequence: starting at index=0 (in a), ending at index=1, mapping to the values [1, 2] in b

 ((5, 7), [3, 4, 5]) # second true sequence: starting at index=5, ending at index=7, with values in b=[3, 4, 5]
]

如何在 numpy 中有效地做到这一点?

【问题讨论】:

  • 你能举个例子len(a)>len(b)。我不知道在这种情况下您将如何选择索引,除非 a 中不可索引的 b 中的所有元素都是 False?即b=[0,1,2,3]a=[True, True, True, False, False, False, False]
  • 谢谢,你是对的。这没有意义。 len(b) 实际上只存在于 a 中存在真正元素的情况下。我会更正我的例子和文字。
  • 好的,我明白了,奇怪的问题。我想它相对容易使用 for 循环,但您正在寻找更有效的实现。
  • 是的,创建一个有效的解决方案就是问题所在。抱歉,要求又变了。我意识到我需要来自 a 的索引以及 b 中的值。
  • @orange 是 numpy 一个要求还是一个选项?

标签: python numpy


【解决方案1】:

这是一个基于 NumPy 的,灵感来自 this post -

def func1(a,b):
    # "Enclose" mask with sentients to catch shifts later on
    mask = np.r_[False,a,False]

    # Get the shifting indices
    idx = np.flatnonzero(mask[1:] != mask[:-1])

    s0,s1 = idx[::2], idx[1::2]
    idx_b = np.r_[0,(s1-s0).cumsum()]
    out = []
    for (i,j,k,l) in zip(s0,s1-1,idx_b[:-1],idx_b[1:]):
        out.append(((i, j), b[k:l]))
    return out

示例运行 -

In [104]: a
Out[104]: array([ True,  True, False, False, False,  True,  True,  True, False])

In [105]: b
Out[105]: [1, 2, 3, 4, 5]

In [106]: func1(a,b)
Out[106]: [((0, 1), [1, 2]), ((5, 7), [3, 4, 5])]

时间安排 -

In [156]: # Using given sample data and tiling it 1000x
     ...: a = np.asarray([True, True, False, False, False, True, True, True, False])
     ...: b = [1, 2, 3, 4, 5]
     ...: a = np.tile(a,1000)
     ...: b = np.tile(b,1000)

# @Chris's soln
In [157]: %%timeit
     ...: res = []
     ...: gen = (i for i in b)
     ...: for k, g in itertools.groupby(enumerate(a), lambda x:x[1]):
     ...:     if k:
     ...:         ind, bools = list(zip(*g))
     ...:         res.append((ind[0::len(ind)-1], list(itertools.islice(gen, len(bools)))))
100 loops, best of 3: 13.8 ms per loop

In [158]: %timeit func1(a,b)
1000 loops, best of 3: 1.29 ms per loop

【讨论】:

    【解决方案2】:

    使用itertools.groupbyitertools.islice

    import itertools
    
    res = []
    gen = (i for i in b)
    for k, g in itertools.groupby(enumerate(a), lambda x:x[1]):
        if k:
            ind, bools = list(zip(*g))
            res.append((ind[0::len(ind)-1], list(itertools.islice(gen, len(bools)))))
    

    输出

    [((0, 1), [1, 2]), ((5, 7), [3, 4, 5])]
    

    见解:

    • itertools.groupby 返回 Trues 和 Falses 的分组对象。
    • list[0::len(list)-1] 返回list 的第一个和最后一个元素。
    • 由于b 始终具有相同数量的Trues,因此将b 设为generator 并获取与Trues 一样多的元素。

    所用时间:

    def itertool_version():
        res = []
        gen = (i for i in b)
        for k, g in itertools.groupby(enumerate(a), lambda x:x[1]):
            if k:
                ind, bools = list(zip(*g))
                res.append((ind[0::len(ind)-1], list(itertools.islice(gen, len(bools)))))
        return res
    
    %timeit itertool()
    7.11 µs ± 313 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    【讨论】:

      【解决方案3】:

      我不知道使用 numpy 的解决方案,但也许以下 for-loop 解决方案将帮助您(或其他人)找到不同的、更有效的解决方案:

      import numpy as np
      
      a = np.asarray([True, True, False, False, False, True, True, True, False])
      b = []
      temp_list = []
      count = 0
      for val in a:
          if (val):
              count += 1
              temp_list.append(count) if len(temp_list) == 0 else None  # Only add the first 'True' value in a sequence
          # Code only reached if val is not true > append b if temp_list has more than 1 entry
          elif (len(temp_list) > 0):
              temp_list.append(count)  # Add the last true value in a sequence
              b.append(temp_list)
              temp_list = []
      print(b)
      
      >>> [[1, 2], [3, 5]]
      

      【讨论】:

        【解决方案4】:

        这是我的两分钱。希望能帮助到你。 [编辑]

        # Get Data
        a = np.asarray([True, True, False, False, False, True, True, True, False])
        b = [1, 2, 3, 4, 5]
        
        # Assign Index names
        ac = ac.astype(float)
        ac[ac==1] = b
        
        
        # Select edges
        ac[(np.roll(ac, 1) != 0) & (np.roll(ac, -1) != 0)] = 0 # Clear out intermediates
        indices = ac[ac != 0] # Select only edges
        indices.reshape(2, int(indices.shape[0]/2)) # group in pairs
        

        输出

        >> [[1, 2], [3, 5]]
        

        【讨论】:

          【解决方案5】:

          解决方案使用 numpy 中的 where() 方法:

          result = []
          f = np.where(a)[0]
          m = 1
          for j in list(create(f)):
              lo = j[1]-j[0]+1
              result.append((j, [*range(m, m + lo)]))
              m += lo
          
          print(result)
          #OUTPUT: [((0, 1), [1, 2]), ((5, 7), [3, 4, 5])]
          

          还有一种方法可以拆分数组[0 1 5 6 7] --> [(0, 1), (5, 7)]:

          def create(k):
              le = len(k)
              i = 0
          
              while i < le:
                  left = k[i]
                  while i < le - 1 and k[i] + 1 == k[i + 1]:
                      i += 1
                  right = k[i]
                  if right - left >= 1:
                      yield (left, right)
                  elif right - left == 1:
                      yield (left, )
                      yield (right, )
                  else:
                      yield (left, )
                  i += 1
          

          【讨论】:

            猜你喜欢
            • 2020-04-01
            • 1970-01-01
            • 2021-02-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-23
            • 2015-11-05
            • 2018-01-31
            相关资源
            最近更新 更多