如何识别布尔数组中的值序列?
Posted
技术标签:
【中文标题】如何识别布尔数组中的值序列?【英文标题】:How do I identify sequences of values in a boolean array? 【发布时间】:2016-08-22 01:06:40 【问题描述】:我有一个长布尔数组:
bool_array = [ True, True, True, True, True, False, False, False, False, False, True, True, True, False, False, True, True, True, True, False, False, False, False, False, False, False ]
我需要找出值翻转的位置,即True
和False
序列开始的地址。在这种特殊情况下,我想得到
index = [0, 5, 10, 13, 15, 19, 26]
有没有一种简单的方法可以不用手动循环检查每个第 i 个元素和第 (i+1) 个元素?
【问题讨论】:
【参考方案1】:作为一种更有效的处理大型数据集的方法,在 python 3.X 中,您可以使用来自itertools
模块的accumulate
和groupby
函数。
>>> from itertools import accumulate, groupby
>>> [0] + list(accumulate(sum(1 for _ in g) for _,g in groupby(bool_array)))
[0, 5, 10, 13, 15, 19, 26]
代码背后的逻辑:
此代码使用groupby()
函数对连续重复项进行分类,然后循环遍历由groupby()
返回的迭代器,该迭代器包含成对的键(我们使用下划线而不是丢弃变量对其进行转义)和这些分类迭代器。
>>> [list(g) for _, g in groupby(bool_array)]
[[True, True, True, True, True], [False, False, False, False, False], [True, True, True], [False, False], [True, True, True, True], [False, False, False, False, False, False, False]]
所以我们只需要计算这些迭代器的长度,并将每个长度与其之前的长度相加,以获得第一项的索引,该索引正是该项更改的位置,这正是 accumulate()
函数是为了。
在 Numpy 中,您可以使用以下方法:
In [19]: np.where(arr[1:] - arr[:-1])[0] + 1
Out[19]: array([ 5, 10, 13, 15, 19])
# With leading and trailing indices
In [22]: np.concatenate(([0], np.where(arr[1:] - arr[:-1])[0] + 1, [arr.size]))
Out[22]: array([ 0, 5, 10, 13, 15, 19, 26])
【讨论】:
不幸的是,我想说这段代码的意图是相当不透明的,即使对于相当熟练的人也是如此。itertools
很棒,但它几乎是一种独立的语言。【参考方案2】:
这会告诉你在哪里:
>>> import numpy as np
>>> np.argwhere(np.diff(bool_array)).squeeze()
array([ 4, 9, 12, 14, 18])
np.diff
计算每个元素与下一个元素之间的差异。对于布尔值,它本质上将值解释为整数(0:假,非零:真),因此差异显示为 +1 或 -1 值,然后映射回布尔值(发生变化时为真)。
np.argwhere
函数随后会告诉您值在哪里是 True --- 现在是变化。
【讨论】:
酷。这就是我要找的。但是,我将accumulate
的答案标记为正确,因为它对于大型数组来说更快。【参考方案3】:
使用zip
和enumerate
你可以做到
>>> [i for i,(m,n) in enumerate(zip(bool_array[:-1],bool_array[1:])) if m!=n]
[4, 9, 12, 14, 18]
现在你有了[4, 9, 12, 14, 18]
,你可以
>>> [0]+[i+1 for i in [4, 9, 12, 14, 18]]+[len(bool_array)]
[0, 5, 10, 13, 15, 19, 26]
实现你的输出。
代码背后的逻辑:
zip
接受两个迭代器并返回两个元素的序列。我们为从第一个元素开始的迭代器和从第二个元素开始的迭代器传递相同的列表。因此我们得到一个相邻数字的列表
enumerate
为您提供一系列索引和迭代器的值。
现在我们将它包装在一个列表推导中。如果压缩后的值不一样,我们返回索引
另一个单步过程是
>>> [i for i,(m,n) in enumerate(zip([2]+bool_array,bool_array+[2])) if m!=n]
[0, 5, 10, 13, 15, 19, 26]
这里我们故意将[2]
引入列表中,这是因为第一个和最后一个值总是不同的(因为[2]
永远不会出现在列表中)。因此,我们将直接获取这些索引。
【讨论】:
对于较小的列表快速。answer++
:我很高兴看到一个不依赖于itertools
和/或numpy
的答案。
如果在前面添加一个值可以跳过第二步,而不是删除一个。
@alexis 是的,[i for i,(m,n) in enumerate(zip([2]+bool_array,bool_array+[2])) if m!=n]
会更好。我会将其添加到我的答案中。
哦,对不起,我现在看到了你的评论。我认为2
是第一个值。继续。【参考方案4】:
从Python 3.8
开始,并引入assignment expressions (PEP 572)(:=
运算符),我们可以在列表推导式中使用和递增变量。再加上groupby
:
from itertools import groupby
# bool_array = [True, True, True, True, True, False, False, False, False, False, True, True, True, False, False, True, True, True, True, False, False, False, False, False, False, False]
total = 0
[total := total + len(list(gp)) for _, gp in groupby(bool_array)]
# [5, 10, 13, 15, 19, 26]
这个:
将变量total
初始化为0
,表示累计和
将连续的项目与groupby
组合在一起(连续的True
将被组合在一起,连续的False
也是如此)
对于每个分组的布尔值系列,这两个:
通过赋值表达式将total
增加一系列布尔值 (total := total + len(list(gp))
) 的当前长度
同时,将连续序列映射到新值total
当然要以0
开头,您可以随时将[0]
插入列表的前面。
【讨论】:
【参考方案5】:使用 pandas shift 来查找 value != 下一个的位置。
import pandas as pd
bool_array = [ True, True, True, True, True, False, False, False, False, False, True, True, True, False, False, True, True, True, True, False, False, False, False, False, False, False ]
bools = pd.Series(bool_array)
flips = bools != bools.shift(1)
flips[flips].index
Int64Index([0, 5, 10, 13, 15, 19], dtype='int64')
或作为列表
list(flips[flips].index)
[0, 5, 10, 13, 15, 19]
【讨论】:
以上是关于如何识别布尔数组中的值序列?的主要内容,如果未能解决你的问题,请参考以下文章