Looping through triplets for zigzag pattern

1.4k Views Asked by At

I am trying to see if groups of three with consecutive elements have a zigzag pattern. Add to an empty list "1" for a zig, "1" for a zag, or "0" for neither. It seems to satisfy my first "if" condition and my "else" statement but never the middle. I've tried it as two if statement, one if and one elif, and nested. The answer should be [1,1,0] but I can only get [1,0] or no output and sometimes " index out of range". input [1,2,1,3,4] output [1,1,0]

    def solution(numbers):
        arr = []
        for i in range(len(numbers)-2):
            if numbers[i+1] > numbers[i] and numbers[i+2]:
                arr.append(1)
            if numbers[i+1] < numbers[i] and numbers[i+2]:
                arr.append(1)
            else:
                arr.append(0)
                return arr
4

There are 4 best solutions below

1
Jack Deeth On

You've got a sliding window here.

You're getting IndexError because your code has for i in range(len(numbers)), and then you ask for numbers[i+2]. To prevent this, reduce the range:

for i in range(len(numbers) - 2):
    <do checks on numbers[i], numbers[i+1], numbers[i+2]>

But you may prefer to zip some slices together:

for a, b, c in zip(numbers, numbers[1:], numbers[2:]):
    <do checks on a, b, c>
2
Samwise On

Break the solution into two parts:

  1. Build a list of the differences between consecutive numbers (the zigs and zags).
  2. Return a list of the differences between consecutive zigs and zags (the zigzags).

You can use zip with slices to iterate over each pair of consecutive elements:

def zig_zags(numbers):
    zigzags = [(a - b) // abs(a - b) for a, b in zip(numbers, numbers[1:])]
    return [int(a and b and a != b) for a, b in zip(zigzags, zigzags[1:])]

print(zig_zags([1, 2, 1, 3, 4]))  # [1, 1, 0]

To break this down a little bit more, let's use the REPL to look at how zip with the slice works:

>>> numbers = [1, 2, 1, 3, 4]
>>> numbers[1:]
[2, 1, 3, 4]
>>> [(a, b) for a, b in zip(numbers, numbers[1:])]
[(1, 2), (2, 1), (1, 3), (3, 4)]

The slice [1:] takes the list starting with the second element, and zip takes two lists (the original list and the sliced version that's offset by one) and yields one element from each at a time -- so all together, we're getting pairs of consecutive elements.

Now let's take that same expression but subtract a and b rather than turning them into tuples:

>>> [a - b for a, b in zip(numbers, numbers[1:])]
[-1, 1, -2, -1]

Negative numbers show where the original list was decreasing (zigging) and positive numbers show where it was increasing (zagging). (If it was neither zigging nor zagging there'd be a zero.) It'll be easier to compare the zigs and zags if we normalize them:

>>> [(a - b) // abs(a - b) for a, b in zip(numbers, numbers[1:])]
[-1, 1, -1, -1]

Now each "zig" is -1 and each "zag" is +1. So now let's see where the zigs follow the zags:

>>> zigzags = [(a - b) // abs(a - b) for a, b in zip(numbers, numbers[1:])]
>>> [(a, b) for a, b in zip(zigzags, zigzags[1:])]
[(-1, 1), (1, -1), (-1, -1)]
>>> [a != b for a, b in zip(zigzags, zigzags[1:])]
[True, True, False]

Same exact technique with zip and [1:] as before, but now we're looking at the zigs and zags that we computed in the first step. Because we normalized them, all we need to do is look at whether they're equal to each other.

We should also specifically exclude cases where there was no zig or zag, i.e. where a or b is zero. That just looks like:

>>> [a and b and a != b for a, b in zip(zigzags, zigzags[1:])]
[True, True, False]

Finally, since we wanted our output to be in terms of 1 and 0 instead of True and False, we need to convert that. Conveniently, when you convert True to an int it becomes 1 and False becomes 0, so we can just wrap the whole thing in int:

>>> [int(a and b and a != b) for a, b in zip(zigzags, zigzags[1:])]
[1, 1, 0]
0
Alain T. On

You can use zip to combine elements 3 by 3 and compare each triplet to check if the middle element is either greater than both its neighbours or smaller than both of them:

numbers = [1,2,1,3,4]

result = [int((a-b)*(b-c)<0) for a,b,c in zip(numbers,numbers[1:],numbers[2:])]

print(result) # [1, 1, 0]

zip will combine items offset by 0, 1 and 2 yielding the following triplets:

numbers        [1,2,1,3,4]
numbers[1:]    [2,1,3,4]
numbers[2:]    [1,3,4] 
zip():          * * *   <-- 3 triplets (extras are ignored)

 a,b,c   a-b    b-c      (a-b)*(b-c)   int(...<0)
------- 
(1,2,1)  -1      1          -1            1
(2,1,3)   1     -2          -2            1 
(1,3,4)  -2     -1           2            0 
0
Nansamba Ssensalo On

Thanks everyone!

It was suggested I define my end range to avoid index out of range errors ( changed to len(numbers)-2) and that I change my formatting from "numbers[i+1] < numbers[i] and numbers[i+2]" to "numbers[i] > numbers[i+1] < numbers[i+2]". Also suggested I try the zip function which I will learn for next time.