Как определить последовательности значений в булевом массиве?
У меня длинный булев массив:
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) th?
Ответы
Ответ 1
В качестве более эффективного подхода для больших наборов данных в python 3.X вы можете использовать accumulate
и groupby
из модуля itertools
.
>>> 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()
, который содержит пары ключей (которые мы избежали, используя строку, вместо переменной throw), и эти классифицированные итераторы.
>>> [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])
Ответ 2
Это скажет вам, где:
>>> import numpy as np
>>> np.argwhere(np.diff(bool_array)).squeeze()
array([ 4, 9, 12, 14, 18])
np.diff
вычисляет разницу между каждым элементом и следующим. Для булевых значений он по существу интерпретирует значения как целые числа (0: False, non-zero: True), поэтому различия отображаются как значения +1 или -1, которые затем преобразуются обратно в booleans (True, когда есть изменение).
Затем функция np.argwhere
сообщает вам, где значения True, которые теперь являются изменениями.
Ответ 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
дает вам последовательность индексов и значение итератора.
- Теперь мы завершим его в понимании списка. Если значения zipped не совпадают, мы возвращаем индекс
Другая процедура с одним шагом -
>>> [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]
никогда не присутствует в списке). Следовательно, мы получим эти индексы напрямую.
Ответ 4
Начиная с Python 3.8
и введением выражений присваивания (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
последовательные элементы с groupby
(последовательный True
будет сгруппирован вместе, то же самое относится к последовательному False
) - Для каждой серии сгруппированных логических значений это оба:
- увеличивает значение
total
на текущую длину серии логических значений (total := total + len(list(gp))
) с помощью выражения присваивания - и в то же время сопоставляет последовательный ряд с новым значением
total
Конечно, чтобы начать это с 0
, вы всегда можете вставить [0]
в начало списка.