NumPy:检测数组中连续的1

5

我希望能够检测numpy数组中连续的1的序列。实际上,我想首先确定数组中的元素是否在至少三个1的序列中。例如,我们有以下数组a:

    import numpy as np
    a = np.array([1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0])

然后,下面加粗的1是满足要求的元素。
[1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0]
接下来,如果两个由至多两个0分隔的1跨度,则这两个跨度组成更长的跨度。因此,以上数组可以表示为:
[1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0]
换句话说,对于原始输入数组,我希望输出如下:
    [True, True, True, True, True, True, True, False, False, False, False, False, True, True, True, True, True, True, True, True, True, True, False]

我一直在考虑一个算法来实现这个功能,但是我想到的所有算法似乎都太复杂了。所以我希望了解更好的实现方式——如果有人能帮助我,我将不胜感激。

更新:

很抱歉我的问题没有表述清楚。我想把数组中3个或更多连续的1识别为1的范围,并且将任何两个只有一个或两个0之间的1范围与分隔0一起识别并作为一个长范围处理。我的目标可以通过以下方式理解:如果1范围之间只有一个或两个0,则将这些0视为错误并应该改正为1。

@ritesht93提供了一个几乎给出我想要的答案。然而,当前答案没有识别出由0分隔的三个1范围,这些范围应该被识别为一个单独的范围。例如,对于以下数组:

    a2 = np.array([0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0])

我们应该接收输出。
    [False,  True,  True,  True,  True,  True,  True,  True,  True,
   True,  True,  True,  True,  True, False, False,  False, False,
   False,  True,  True,  True,  True,  True, False]

更新2:

我受到了极大的启发,并发现基于正则表达式的算法最易于实现和理解,尽管我不确定与其他方法相比效率如何。最终我使用了以下方法。

    lst = np.array([0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0])
    lst1 = re.sub(r'1{3,}', lambda x:'c'*len(x.group()), ''.join(map(str, lst)))
    print lst1

识别了一串1的跨度

    0ccc0ccc00cccc00100ccccc0

然后将连续的1之间建立连接

    lst2 = re.sub(r'c{1}0{1,2}c{1}', lambda x:'c'*len(x.group()), ''.join(map(str, lst1)))
    print lst2

这提供了

    0ccccccccccccc00100ccccc0

最终结果由以下代码给出:
    np.array(list(lst2)) == 'c'

    array([False,  True,  True,  True,  True,  True,  True,  True,  True,
    True,  True,  True,  True,  True, False, False, False, False,
   False,  True,  True,  True,  True,  True, False])

@hvwaldow 是的,你说得对。感谢指出。已经更正。 - user3821012
然而,当前的答案没有识别出当有三个由0分隔的1段时的情况... 嗯,我没有看到这个。我的答案似乎为您的第二个测试用例产生了正确的解决方案。 - hvwaldow
@hvwaldow 请参考更新后的问题描述。很抱歉没有清楚地描述问题并且对期望输出的错误感到抱歉... - user3821012
我做了。已通过第二个测试用例的验证。 - hvwaldow
@user3821012,或者说我们只需要识别出3个或更多的1,而少于3个1则为False吗? - riteshtch
显示剩余4条评论
4个回答

2
我们可以通过结合二元膨胀和侵蚀来解决这个问题,以跨越第一阶段,然后使用二元闭运算得到最终输出,如下所示 -
from scipy.ndimage.morphology import binary_erosion,binary_dilation,binary_closing

K = np.ones(3,dtype=int) # Kernel
b = binary_dilation(binary_erosion(a,K),K)
out = binary_closing(b,K) | b

样例运行

案例 #1 :

In [454]: a
Out[454]: array([1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0])

In [456]: out
Out[456]: 
array([ True,  True,  True,  True,  True,  True,  True, False, False,
       False, False, False,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True, False], dtype=bool)

案例 #2:

In [460]: a
Out[460]: 
array([0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0])

In [461]: out
Out[461]: 
array([False,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True, False, False, False, False,
       False,  True,  True,  True,  True,  True, False], dtype=bool)

1

与其采用传统的循环和计数方式解决问题,我们可以将所有的0和1转换为单个字符串,并用另一个字符(例如2)替换正则表达式匹配。完成后,我们再次拆分字符串并检查每个字符上的bool()

>>> import re
>>> lst=[1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0]
>>> list(map(bool, map(int, list(re.sub(r'1{3,}0{1,2}1{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst)))))))
[True, True, True, True, True, True, True, False, True, True, False, False, True, True, True, True, True, True, True, True, True, True, False]
>>> 

所有操作都发生在这里:

re.sub(r'1{3,}0{1,2}1{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst)))

它搜索连续出现的三个或更多1,后跟最多两个0,即1或2个0后跟3个或更多1,并用相同长度的2替换整个匹配字符串(使用2是因为bool(2)True)。此外,您可以使用NumPy中的tolist()方法将列表从NumPy数组中取出,如下所示:np.array([1,2, 3, 4, 5, 6]).tolist()
>>> lst=[1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0]
>>> import re
>>> list(map(lambda x:False if x == 0 or x ==1 else True, map(int, list(re.sub(r'1{3,}0{1,2}1{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst)))))))
[True, True, True, True, True, True, True, False, False, False, False, False, True, True, True, True, True, True, True, True, True, True, False]
>>> 

编辑2 最终答案:

>>> import re
>>> lst=[0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0]
>>> while re.subn(r'[12]{3,}0{1,2}[12]{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst)))[1]:
...     lst=re.subn(r'[12]{3,}0{1,2}[12]{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst)))[0]
... 
>>> lst
'0222222222222200100111110'
>>> lst=list(re.sub(r'1{3,}', lambda x:'2'*len(x.group()), ''.join(map(str, lst))))
>>> lst
['0', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '0', '0', '1', '0', '0', '2', '2', '2', '2', '2', '0']
>>> list(map(lambda x:False if x == 0 or x ==1 else True, map(int, lst)))
[False, True, True, True, True, True, True, True, True, True, True, True, True, True, False, False, False, False, False, True, True, True, True, True, False]
>>> 

这并没有返回所期望的结果,在原问题的更正后。不过应该很容易修复。 - hvwaldow
不,它不是这样工作的。每当你有像 0,1,1,0,1,1,1,0,1,1,0 这样的东西时,三个 1 将不会被捕获。 - hvwaldow
@hvwaldow 抱歉..我刚才离线了,刚看到更新的问题并相应地更新了我的答案。 - riteshtch
@hvwaldow,你的意思是三个1代表true吗?如果是的话,我们可以在正则表达式中添加另一个OR表达式,如1{3,},但我不认为OP提到了这一点。 - riteshtch
我可以问一下 x 的 .group() 是什么意思吗?这里的 x 应该是一个字符串吧? - user3821012
@user3821012 x 是匹配对象,它返回正则表达式查找的匹配部分,而对该匹配对象进行 group() 操作则会返回匹配的字符串。 - riteshtch

1

我知道这不是很符合Python的风格,但既然你谈到了算法,我决定尝试一下(抱歉我对Python不是很熟悉)

import numpy as np
a = np.array([1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0])
b = np.array([int])

#init 2nd array 
for x in range (0,(a.size-1)):
    b = np.append(b,0)

print (b)
#1st case
for x in range (2,(a.size)):
    if (a[x-2]==1 & a[x-1]==1 & a[x]==1): #1-1-1
        b[x] = 1
        b[x-1] = 1
        b[x-2] = 1

print (b)
#2nd case
for x in range (2,(b.size)):
    if (b[x-2]==1 & b[x]==1): #1-0-1
        if (b[x-1]==0): #sorry, i forget about logical op. in python
            b[x-1] = 1

print (b)
#3rd case
for x in range (3,(b.size)):
    if (b[x-3]==1 & b[x]==1): #1-0-0-1
        if (b[x-2]==0 & b[x]-1==0):
            b[x-1] = 1
            b[x-2] = 1

#4th case
for x in range (4,(b.size)):
    if (a[x-4]==1 & a[x-3]==1 & b[x]): #1-1-0-0-1
        if (a[x-2]==0 & a[x]-1==0):
            b[x-3] = 1
            b[x-4] = 1
print (b)

我不确定这是否完全符合您的预期结果,但这是它:
[1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0]


1
有很多方法可以做到这一点。我会将其分成分组、对组应用条件和展开操作。就像这样:
from itertools import groupby, starmap
import numpy as np

a = np.array([1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0])

def condition(groups, key, newkey, minlen):
    return [(newkey, l) if l < minlen and k == key else (k, l) for k, l in groups]

def flatten(groups):
    return [k for g in starmap(lambda k, l: l * [k], groups) for k in g]

def group(l):
    return [(k, len(list(v))) for k, v in groupby(l)]

res = group(flatten(condition(group(a), 1, 0, 3)))
# groups zeros at the beginning or the end never change to ones,
# no matter their length
res = flatten([res[0]] + condition(res[1:-1], 0, 1, 3) + [res[-1]])
print [bool(v) for v in res]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接