1

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.

10
  • I don't understand how your initial examples explain how your expected output at all Commented Jan 19, 2021 at 15:06
  • Sorry for the confusion @Mad Physicist, I have change the explanation for the second point. Thing should be much more clearer now Commented Jan 19, 2021 at 15:11
  • Thanks. That makes sense now Commented Jan 19, 2021 at 16:12
  • 1
    @MarkRansom. I agree that that particular example can be done without numpy, but there are lots of (good) questions in which the OP recognizes that numpy would benefit them, just don't know how to use it. Commented Jan 19, 2021 at 18:03
  • 1
    Shouldn't the 6th element of the output of sample_2 be False according to your computation rules? Commented Jan 19, 2021 at 18:57

2 Answers 2

2

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]
Sign up to request clarification or add additional context in comments.

Comments

2

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_)

2 Comments

Really appreciate for the suggestion and especially introduction of the memory-efficient dtype
@balandongiv. No prob. You can do the selected answer in-place too using a similar trick.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.