Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python list keep value only if equal to n predecessors

Tags:

python

I have a list of signals (representing consecutive mesures) :

signals = [0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0]

I consider a signal valid only if equal to the previous n mesures.

Eg. if we consider only 2 mesures for validation (n=2), the first time signals turns from 0 to 1 we consider it still 0 but the next mesure, if it's 1 again then we consider it is still valid and make it 1. Then we would need 2 mesures of 0 to turn it to 0 again, etc... Here signals are 0 and 1 for simplification but in my application they can be other integers.

Desired output :

# For n = 2:
valid_s = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]

# For n = 3:
valid_s = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0]

# For n = 4:
valid_s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0]

I was looking of a pythonesque one-liner way of doing it but can't seem to find the desired output. I tried something along the lines :

S = signals

# For n = 2
[S[i] if S[i] == S[i-1] else S[i-2] for i, _ in enumerate(S)]
# gives [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]

# For n = 3
[S[i] if S[i] == S[i-1] == S[i-2] else S[i-3] for i, _ in enumerate(S)]
# gives [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]

Edit : I'm open to numpy if it makes it easier as it's already imported.

like image 429
Fredovsky Avatar asked Dec 12 '17 14:12

Fredovsky


2 Answers

I don't think there is a good way to make this a one-liner / list comprehension. While you could use all with a slice of the list to see whether the value is the same as the n values before, I don't see a good way to determine which should be the last valid value in case it is not.

Instead, you could use a good-old "many-lines" for loop:

signals = [0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0]
n = 3

last_valid = 0
current = None
repeated = 0
res = []
for s in signals:
    if s == current:
        repeated += 1
    else:
        repeated = 1
        current = s
    if repeated >= n:
        last_valid = s
    res.append(last_valid)

Afterwards, res is [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0]


Alternatively, a bit shorter, using itertools.groupby; result is the same:

last_valid = 0
res = []
for k, g in itertools.groupby(signals):
    m = len(list(g))
    if m >= n:
        res.extend([last_valid] * (n-1) + [k] * (m-n+1))
        last_valid = k
    else:
        res.extend([last_valid] * m)
like image 128
tobias_k Avatar answered Nov 03 '22 07:11

tobias_k


signal = [0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0]

def each(signal, n):
  p = 0
  ring = [ signal[0] ] * (n-1)
  v = None
  for x in signal:
    if v is None or all(q == x for q in ring):
      v = x
    yield v
    ring[p] = x
    p = (p+1) % (n-1)

list(each(signal, 2))
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]

But I reconsidered this and think that a pure iterative (less Pythonic) approach is probably more efficient. After completing my new approach I now think that it is the same idea as @tobias_k already implemented in his answer:

def each(signal, n):
  current = signal[0]
  next = None
  for v in signal:
    if v != current:
      if v == next:
        next_count += 1
      else:
        next_count = 1
        next = v
      if next_count >= n:
        current = v
    yield current
like image 30
Alfe Avatar answered Nov 03 '22 07:11

Alfe