Reputation: 3299
The objective is to create a boolean array, state
, from another boolean array, initial
, according to the rules described below.
If initial
starts with False
, the first and subsequent elements of state
will be False
. Upon reaching True
in initial
, state
will switch to True
.
Example:
initial: [False, False, False, True]
state: [False, False, False, True]
If, on the other hand, initial
starts with True
, the first and subsequent elements of state
will also be True
. Upon reaching True
in initial
, state will switch to False
.
Example:
initial: [True, False, False, True]
state: [True, True, True, False]
If True
is encountered in the middle of initial
, the new value of state
will depend on the previous state. For example, if the previous value of state
was False
, then state
will switch to True
:
initial: [False, False, False, True] --Take note of the last boolean
state: [False, False, False, True] --Take note of the last state i.e., False
On the other hand, if the previous value of state
was True
, then state
will switch to False
:
initial: [False, False, False, True] --Take note of the last boolean
state: [True, True, True, False] --Take note of the last state i.e., False
Based on these requirements and two simple samples of initial
, I've written the following code:
import numpy as np
sample_1 = [False, False, False, False, False, True, False, False, False, True, False, False, False]
# sample_2 = [True, False, False, False, False, True, False, False, False, True, False, False, False]
expected_output = []
counter = 0
for x in np.array(sample_1):
if x == False and counter == 0:
idx = False
elif x == True and counter == 0:
idx = True
counter = 2
elif x == False and counter == 1:
idx = True
elif x == True and counter == 1:
idx = True
counter = 0
elif x == True and counter == 2:
idx = False
counter = 0
expected_output.append(idx)
The expected output, respectively for sample1
and sample2
is:
For sample_1
:
[False, False, False, False, False, True, True, True, True, False, False, False, False]
For sample_2
:
[True, True, True, True, True, True, False, False, False, True, True, True, True]
I am curios whether there is more compact notation or a build-in module that can be used instead of the naive if-else approach above.
Upvotes: 1
Views: 74
Reputation: 4268
IIUC, this gives what you want
import numpy as np
def compute_states(initial: np.array):
return (initial.cumsum() % 2).astype(bool)
Test:
print(compute_states(np.array([False, False, False, False, False, True, False, False, False, True, False, False, False])))
print(compute_states(np.array([True, False, False, False, False, True, False, False, False, True, False, False, False])))
Output:
[False False False False False True True True True False False False False]
[ True True True True True False False False False True True True True]
Upvotes: 2
Reputation: 114468
To begin with, your if/else
can be greatly simplified. Notice that any time you get a True
element, the value of future elements in the output is toggled.
You can therefore safely always start your loop with the assumption that prior elements were False
:
state = False
for x in sample_1:
if x:
state = not state
expected_output.append(state)
A similar approach with a counter:
counter = 0
for x in sample_1:
if x:
counter += 1
expected_output.append(bool(counter % 2))
You can if course do this with numpy functions. A term that I've heard for this type of operation is "smearing a mask". The general idea is that you can build a mask with runs of elements by taking the cumulative sum of an array containing 1
, 0
and -1
elements. 1
turns on a run, and -1
turns it off.
To convert your input data into a more useful format, use np.flatnonzero
:
indices = np.flatnonzero(sample_1)
Now make an intermediate array that will contain your +/-1
values. I'm going to suggest using np.int8
for that. First, it takes up less space, and second, you can do the entire operation in-place if you're clever:
triggers = np.zeros(len(sample_1), dtype=np.int8)
Place ones at every other index location:
triggers[indices[::2]] = 1
Place negative ones at every other location:
triggers[indices[1::2]] = -1
The output mask is just the cumulative sum of the triggers:
expected_output = np.cumsum(triggers).astype(bool)
You can do the same operation without allocating a single temporary array in the last step, because np.bool_
and np.int8
have the same element size:
expected_output = np.cumsum(triggers, out=triggers).view(np.bool_)
Upvotes: 2