4
myList = [True, True, False, False, True, False, True, False, False]

I want to find if True appears 3 times in a row.

I can find it by doing:

for x0, x1, x2 in zip(myList, myList[1:], myList[2:]):
    if x0 == True and x1 == True and x2 == True:
        print True

Is there a better way?

user2333196
  • 5,406
  • 7
  • 31
  • 35
  • 1
    As a general note, python treats ```if bool == True``` and ```if bool``` as the same – wnnmaw Oct 16 '14 at 15:40
  • Honestly, what you have isn't bad . . . you could shortcut it a little for your case: `for elems in zip(...): if all(elems): print True`... `itertools.islicing` the may not be a horrible idea either if you want to be able to short-circuit efficiently. – mgilson Oct 16 '14 at 15:49

6 Answers6

7

Use itertools.groupby() to group elements, then count each group. Using the any() function lets you exit the loop early if a match was found:

from itertools import groupby, islice

print any(sum(1 for _ in islice(g, 3)) == 3 for k, g in groupby(myList) if k)

The if k filters the groups to only count the groups of True values.

The itertools.islice() function ensures we only look at the first 3 elements of a group, and ignore the rest of that group. This way you avoid having to count the next however many True values just to determine that you found at least 3.

Demo:

>>> from itertools import groupby, islice
>>> myList = [True, True, False, False, True, False, True, False, False]
>>> [sum(1 for _ in islice(g, 3)) for k, g in groupby(myList) if k]
[2, 1, 1]
>>> any(sum(1 for _ in islice(g, 3)) == 3 for k, g in groupby(myList) if k)
False
>>> myList = [True, True, False, False, True, True, True, True, False, True, False, False]
>>> [sum(1 for _ in islice(g, 3)) for k, g in groupby(myList) if k]
[2, 3, 1]
>>> any(sum(1 for _ in islice(g, 3)) == 3 for k, g in groupby(myList) if k)
True

I used a list comprehension to show the group sizes (counting only True groups) to show why the any() call returns False first, then True; the second example has a group of 4 consecutive True values.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Is `itertools` built in? Also, is the `print any(sum(1 for _ in islice(g, 3)) == 3 for k, g in groupby(myList) if k)` a boolean? – WarpPrime Aug 06 '20 at 14:34
3

I like the brevity of the groupby, but I find the following slightly more readable so I thought I'd add an alternative;

needle = 3 * [True]
any(1 for i in range(len(myList)) if myList[i:i+len(needle)] == needle)
Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
  • Nice. You can also easily switch `any` to `sum` if you want to count the (possibly overlapping) matches. (It looks like you have a typo, though, with the `++`.) – John Y Oct 16 '14 at 16:07
  • Python is quite savy when dealing with out-of-bond indices in slices, but my FORTRANesque nature would have bent me to write `any(1 for i in range(len(myList)-len(needle)+1) ... ` ;-) +1 for brevity .AND. readability – gboffi Oct 16 '14 at 16:38
  • 1
    This gets slower when looking for long runs of the same element, because it looks at each element _k_ times. That said, a variation of the same approach would be `needle in (myList[i:i+len(needle)] for i in range(len(myList)))`, eliminating the use of `any()`. – Sven Marnach Oct 16 '14 at 17:11
1

Here it is my solution

% cat hsol.py
import itertools

myList = [True, True, False, False, True, False, True, False, False]

def test_sequentiality(l, item, n):

    if n>len(l): return False
    s = 0
    for i in l:
        if i != item:
            s = 0
        else:
            s = s+1
            if s == n: return True

    return False

print test_sequentiality(myList, True, 3)
print test_sequentiality(myList, True, 2)
% python2 hsol.py
False
True
% 
gboffi
  • 22,939
  • 8
  • 54
  • 85
0

One way would be to reduce the if statements etc in your question, and directly print the boolean value.

for x0, x1, x2 in zip(myList, myList[1:], myList[2:]):
    print x0 == x1 == x2 == True

Using any statement, we can short circuit the same

any(x0 == x1 == x2 == True for (x0, x1, x2) in zip(myList, myList[1:], myList[2:]))
Anshul Goyal
  • 73,278
  • 37
  • 149
  • 186
0
print reduce(
             lambda acc, x: (acc[0]+1 if x else 0, max(acc[0], acc[1])),
             myList+[None],
             (0, 0)
             )[1]

It will accumulate the sequential counts of True with maximal count of sequential Trues +[None] is needed in case the longest sequence ends with last element.

Andrey
  • 59,039
  • 12
  • 119
  • 163
  • This is just an obfuscated version of the for loop in gboffi's answer. – Sven Marnach Oct 16 '14 at 15:56
  • Why the `+[None]`, by the way? – Sven Marnach Oct 16 '14 at 15:58
  • @SvenMarnach it is functional approach to the problem. Honestly I would write loop in my code, it is much more sane. +[None] the maximal sequential count is increased on next element, so it last element is True then last max will yield previous count and not really maximal. – Andrey Oct 16 '14 at 15:59
  • @SvenMarnach if you have TTFTTT the reduce will yield internally (1, 0), (1, 1), (0, 2), (0, 2), (1, 2), (2, 2), (3, 2) and the result will be (3, 2). So you need to flush it to become (0, 3). – Andrey Oct 16 '14 at 16:01
  • Ah, I see. You could also use `max(reduce(...))` to take the bigger of the two values after the last iteration. – Sven Marnach Oct 16 '14 at 16:02
0

A straightforward and (in my opinion) very easy-to-understand way:

myList = [True, True, False, False, True, False, True, False, False]
v = True  # search for this value
n = 3  # number of consecutive appearances of v
vList = [v] * n

for i in range(len(myList) - n):
    if myList[i:i + n] == vList:
        print True
        break
else:
    print False

It's kind of C-style (or Pascal or Fortran, etc.), but simple. The above code assumes you just want a single True or False at the end. You can easily adapt it if you want to print the index of each found occurrence.

John Y
  • 14,123
  • 2
  • 48
  • 72