Looping through triplets for zigzag pattern - python

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

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

Break the solution into two parts:
Build a list of the differences between consecutive numbers (the zigs and zags).
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]

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.

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>

Related

how does any([a == b for a, b in zip(string, string[1:])]) work

I am learning python by doing online challanges.
The goal of this challenge is to analyze a string to check if it contains two of the same letter in a row. For example, the string "hello" has l twice in a row, while the string "nono" does not have two identical letters in a row.
Define a function named double_letters that takes a single parameter. The parameter is a string. Your function must return True if there are two identical letters in a row in the string, and False otherwise.
my solution:
def double_letters(word):
for i in range(len(word)):
if word[i] == word[i-1]:
return True
return False
apparently though,
def double_letters(string):
return any([a == b for a, b in zip(string, string[1:])])
is a better solution,
what is the logic behind it (I understand what a zip function does)
Note that your function has a bug - it believes that the first and last letters are next to each other, and double_letters('non') is True.
(Finding the bug left as an exercise. It's an "off by one" problem.)
zip(string, string[1:]) creates pairs of consecutive letters from the strings.
>>> s = "hello"
>>> list(zip(s, s[1:]))
[('h', 'e'), ('e', 'l'), ('l', 'l'), ('l', 'o')]
[a == b for a, b in zip(string, string[1:])] is a list comprehension that deconstructs each pair (for a, b in zip(string, string[1:])) and produces True elements when they are equal, False elements when they are not.
>>> [a == b for a, b in zip(s, s[1:])]
[False, False, True, False]
any is a function that takes an iterable and returns True if at least one of the elements is True.
>>> any([False, False])
False
>>> any([False, True, False])
True
Essentially what that code is doing is comparing the current character with the one next to it, simply by getting the shifted over version and comparing them.
It is a fancy and compact way of writing:
return any(a == b where b is the character after a)
What the zip is doing is something like this:
[1, 2, 3, 4, 5]
# a b
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, None]
... and where they are equal, it will return true.

compute matrix from list and two numbers that powers the elements

I'm trying to define a function. The function should compute a matrix from inserting a list of numbers and two additional numbers, which should be the range of what each element in the list is going to be powered to, in the command line.
For example if I insert powers([2,3,4],0,2) in the command line, the output should be a 3x3 matrix with the first row [2^0,2^1,2^2], the second [3^0,3^1,3^2] and third row [3^0,3^1,3^2].
It should look something like:
input: powers([2,3,4],0,2)
output: [[1, 2, 4],[1,3,9],[1,4,16]]
Does anyone know how to do something like that by not importing any additional package to python?
So far I have
def powers(C,a,b):
for c in C:
matrix=[]
for i in range(a,b):
c = c**i
matrix.append(c)
print(matrix)
But that only gives me one row of ones.
In your outer loop, you're emptying the matrix in each iteration. In your inner loop you're appending the powers directly to the matrix, when you should instead create a sub-list and append the numbers to it, then, append the sub-list to the matrix. All you need for this is a simple list comprehension:
def powers(C, a, b):
matrix = [[c ** i for i in range(a, b + 1)] for c in C]
return matrix
Test:
>>> powers([2, 3, 4], 0, 2)
[[1, 2, 4], [1, 3, 9], [1, 4, 16]]
The range is range(a, b + 1) because Python's range stops one step before the end (it doesn't include the end), so to include b use b + 1.

How to compare list of lists in Python?

I have 2 lists like the following:
a=[[1,0,1,0,1],[0,0,0,1,0],[1,1,0,0,0]]
b=[[1,0,0,0,1],[0,1,0,1,0],[1,1,0,1,0]]
I want to return true if all the sublists in b are present in a and vice versa.
That means a should be equal to b but indexes of the sublists can be different.
eg:
a=[[1,0,1,0,1],[0,0,0,1,0],[1,1,0,0,0]]
b=[[1,0,1,0,1],[1,1,0,0,0],[0,0,0,1,0]]
Above a and b are equal and comparison should return true. Also, the sublists will only contain a combination of 1s or 0s. How do I compare them?
I tried converting them to sets : set(a) but this is throwing an error.
Apart from that, when I tried the following code in a while loop, it gave an error
a=[[1,0,1,0,1],[0,0,0,1,0],[1,1,0,0,0]]
b=[[1,0,1,0,1],[1,1,0,0,0],[0,0,0,1,0]]
def sublists_equal(a, b):
return all(l for l in b if l in a)
print(sublists_equal(a, b))
The error was:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
I tried printing both the arrays to see what the problem was, they are printing like follows:
[[0 1 0 1 0]
[0 1 1 1 1]
[0 0 0 0 1]
[0 1 0 0 0]]
[array([0, 1, 0, 1, 0]), array([0, 0, 0, 0, 1]), array([0, 1, 1, 1, 1]), array([0, 1, 0, 0, 0])]
You could use the all() built-in function to check whether all sub-lists l of b can be found in a.
a=[[1,0,1,0,1],[0,0,0,1,0],[1,1,0,0,0]]
b=[[1,0,1,0,1],[1,1,0,0,0],[0,0,0,1,0]]
def sublists_equal(a, b):
return all(l for l in b if l in a)
print(sublists_equal(a, b))
Should you wanted to use sets, you would have to convert each sub-list to a tuple, which is a hashable type, and then compare their symmetric difference denoted by the ^ operator which returns the set of elements not found in both lists. If the symmetric difference is the empty set(), which is negated to True using the not operator, then the lists have equal sub-lists.
def sublist_equal2(a, b):
return not set([tuple(l) for l in a]) ^ set([tuple(l) for l in b])
print(sublist_equal2(a, b))
Output:
True
The easiest way is to sort the lists before comparing them:
def compare_lists(a, b):
return sorted(a) == sorted(b)
this handles cases where there are duplicate values.
If duplicates don't matter, and the sublists only contains 1 and 0, you could convert them to integers:
def sublists_to_integers(lst):
return [int(''.join(str(i) for i in item), 2) for item in lst]
def compare_lists(a, b):
return set(sublists_to_integers(a)) == set(sublists_to_integers(b))
which depending on your list size might be faster and/or use less memory.

Sorting tuples with dynamic values and length

I am trying to sort numerical tuples by two arguments:
first argument, lenght of the tuple: the smaller the tuple, the better;
second argument, the n-th value of the tuple: if two tuples have the same lenght, then they should be ordered by the first numerical value by which they differ
For example, let's say we have these four tuples:
a = (2, 5) # len(a) == 2
b = (2, 5, 3) # len(b) == 3
c = (2, 4, 3) # len(c) == 3
d = (1, 4, 4, 8) # len(d) == 4
The result I'm willing to obtain is a function that will help me sort these tuples so that:
a is the first tuple (the smallest one)
b and c follows (the middle ones)
d is the last tuple (the longest one)
since b and c both get the same lenght, they shall be ordered so that c comes before b, because while their first value is the same, c's second values is smaller than b's
Therefore, the four tuples above should be listed as [a, c, b, d].
Question is: how do I do it, knowing that the tuples have no fixed length, and they might differ at any value from the first to the last one?
You can sort tuples with... tuples:
res = sorted([a, b, c, d], key=lambda x: (len(x), x))
# [(2, 5), (2, 4, 3), (2, 5, 3), (1, 4, 4, 8)]
The key is the key argument, which utilises an anonymous (lambda) function. Python sorts tuples by element. So the first element len(x) gives priority to length of tuple. The second element x gives secondary importance to sorting by the tuple itself, which itself is performed element-wise.

Binary list from indices of ascending integer list

I have an ascending list of integers e that starts from 0 and I would like to have a binary list b whose i-th element is 1 if and only if i belongs to e.
For example, if e=[0,1,3,6], then this binary list should be [1,1,0,1,0,0,1],
where the first 1 is because 0 is in e, the second 1 is because 1 is in e, the
third 0 is because 2 is not in e, and so on.
You can find my code for that below.
My question is: is there something built-in in python for that? If not, is my
approach the most efficient?
def list2bin(e):
b=[1]
j=1
for i in range(1, e[-1]+1):
if i==e[j]:
b.append(1)
j+=1
else:
b.append(0)
return(b)
This can be done with a list comprehension, and in case e is huge then better convert it to a set first:
>>> e = [0, 1, 3, 6]
>>> [int(i in e) for i in xrange(0, e[-1]+1)]
[1, 1, 0, 1, 0, 0, 1]
The in operator returns True/False if an item is found in the list, you can convert that bool to an integer using int. Note that for lists the in is O(N) operation, so if e is large then converting it to a set will provide you much more efficiency.
I don't think there are a built-in way to do that. But you can use List Comprehensions:
a = [ 1 if i in e else 0 for i in range(1, e[-1]+1) ]
Get fun.

Categories

Resources