Good evening everyone,
I am comparing nested structures of two arrays of the same length (i.e.: [ 1, [ 1, 1 ] ], [ [ 2, 2 ], 2 ] ) by checking for the type and length equality as follows:
def array_structure(array1, array2):
for i in range(len(array1)):
if type(array1[i]) == type(array2[i]):
if isinstance(array1[i], int) == False:
if len(array1[i]) == len(array2[i]):
return True
else:
return False
elif len(str(array1[i])) == len(str(array2[i])):
return True
else:
return False
else:
return False
The function should return True or False whether the nested structures of the arrays are equal or not.
My attemp works for some arrays, but I'm feeling like I'm missing the point so I would appreciate any help to understand and find a more pythonic way to code this and improve the logical approach.
You can use itertools.zip_longest and all:
from itertools import zip_longest
def compare(a, b):
if not isinstance(a, list) or not isinstance(b, list):
return a == b
return all(compare(j, k) for j, k in zip_longest(a, b))
print(compare([[1, [2, [3, 4]]]], [[1, [2, [3, 4]]]]))
print(compare([3, [4], 5], [3, [6], 5]))
Output:
True
False
Note that you are manipulating lists, not arrays.
The exact meaning of "structure" is a bit vague, I am guessing that you mean that there are either integers or nested lists of the same length, at the same positions.
If that is so, here are some problems:
As soon as you find two identical things, you return and exit. This means that if the first elements are identical, you never compare the second element !
>>> array_structure([1,2],[4,[5]])
True
Finding the "same structure" at one position isn't enough to deduce that it is true for all indices. You need to keep checking. However, you are right that as soon as you find "different structure", you can safely return, because it is enough to make the structures different overall.
When you have the same type for both elements, if you have an integer, then you convert them to strings, then compare lengths, otherwise, you compare the lengths. This looks strange to me: Why would you say that [1] and [11] have different structures ?
>>> array_structure([1],[11])
False
What about nested arrays ? maybe array_structure([1,[2]],[1,[2,[3,4,5,6]]]) should be false, and array_structure([4,[5,[8,3,0,1]]],[1,[2,[3,4,5,6]]]) should be true ? If you have seen the notion of recursion, this might be a good place to use it.
A good strategy for your problem would be to go over each element, make some tests to check if you have "different structures", and return False if that is the case. Otherwise, don't return, so you can go over all elements. This means that if anything is different, you will have exited during iteration, so you will only reach the end of the loop if everything was identical. At that point, you can return True. The bulk of the function should look like:
for i in range(<length>):
if <different structure>:
return False
return True
More tips for later:
Testing for types in general is not very pythonic. Unless the exercise tells you that you will only ever get integers or lists, you could have other things in these arrays that are not integers and don't have a length. Rather than testing whether the things are ints to deduce if they have a length, you could test for hasattr(array1[i], 'len'). This will be True if the element is of a type that has a length, and False otherwise.
Each time you have something that looks like if <condition>: return True else return False you can be sure it is identical to return <condition>. This helps in simplifying code, but in your example, it's not a great idea to change it right away, because you should not be returning when you find True to your conditions.
Each time you have something that looks like <condition> == False, it is better and more readable to write not <condition>. It is also possible to switch the logic over so that you have a positive condition first, which is often more readable.
Related
I'm trying to see if lst2 is the reverse of lst1.
For the following code, why does return True have to be outside the if statement. When I put else: return False with the if statement, both of the prints return True (which is incorrect). Thank you!
def reversed_list(lst1, lst2):
for index1 in range(len(lst1)):
if lst1[index1] != lst2[(-1 - index1)]:
return False
return True
print(reversed_list([1, 2, 3], [3, 2, 1]))
print(reversed_list([1, 5, 3], [3, 2, 1]))
If you put it into the if, i.e. into the same conditionality as the return False, but after it, then it will never be executed, because the function will always have been left with the first return. Or it will always be executed inside the if, before, leaving it and thereby unintendedly overriding the intended False. This seems is what you are observing.
If you put it into the loop (but outside the if) it will be executed during the first iteration of the loop, i.e. much too early.
If you put it into the loop, but with an else, it will still be executed too early. at the first case of not False. This is still not what you want, because you only want a True when there is no False anywhere in the loop, not already at the first case of not False.
You only want to return a true boolean if the loop gets completly through without ever triggering the False. You want that because otherwise you might miss cases of False.
This is why the position you describe and use in the shown code, outside of both, the if and the loop, is the only correct way.
This approach is a lighter method to a brute force approach.
With brute force, the second list is reversed and then all the elements of both lists are compared. That's a lot of wasted resources, especially if the lists are massive in length.
The approach provided in your Q utilizes a pointer, which essentially allows you to "stop early." Instead of sorting the second list, iterate over the elements and compare them. If they match, move on to the next elements. If they do not match, escape early and return False. With this approach, you cannot return True until all the elements in the lists are compared. (Hence, return True is outside the for loop.)
The return statement immediately stops the execution of a function or method.
In this way, your function stops at the first difference and returns False. If all tested elements are equal, then the loop finishes without that return False statement and continues with the next statement which is return True .
I am not sure if this is exactly what you need, but this is a way of checking if l1 is the same as l2 reversed.
l1 = [1, 2, 3]
l2 = [3, 2, 1]
l2.reverse()
if l1 == l2:
print("Yes")
else:
print("No")
I am learning python with a book: The exercise is to make a program that print True if all numbers in a list all odds.
I get it whit this approach
if all(x % 2 == 1 for x in list):
But the 'if all' approach is not yet explained. They only use while, if,for and booleans in the examples. Furthermore, seems to be a reflexive exercise about it is possible to do it, maybe not. It is possible to do it using the basic tools of above?
If you look at the docs: https://docs.python.org/3/library/functions.html#all
all(iterable) .
Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to:
def all(iterable):
for element in iterable:
if not element:
return False
return True
So if all(x % 2 == 1 for x in li): roughly translates to
def are_all_odds(num_list):
#Flag to keep track if we encounter an even number
flag = True
for num in num_list:
#Once we encounter a even number, we break the for loop
if num % 2 != 1:
flag = False
break
#Return the flag
return flag
We can test this function by doing
print(are_all_odds([1, 2, 3, 4]))
#False
print(are_all_odds([1, 3, 5]))
#True
Also just a suggestion, list is a python builtin keyword, so don't use it in variables :)
Yes, it is possible.
The Python code you wrote is very idiomatic, keep up that good work.
To see how to do it differently, you can look at programming languages that are less advanced, such as C. That is a very basic programming language which lacks the features for this if all statement. Searching for "c all elements array true" should give you the code you are looking for. For such a simple piece of code, it's easy to translate the code back into Python.
I don´t understand why it returns False, if sequence[0] is bigger than sequence[1]
sequence=[10, 1, 2, 3, 4, 5]
a=any(q for q in range(len(sequence)-1) if sequence[q]>=sequence[q+1])
print(a)
It works for the indexes bigger than 0
Your problem is that, for this list, (q for q in range(len(sequence)-1) if sequence[q]>=sequence[q+1]) is (0), and 0 is falsey.
Putting the actual indices into any kind of iterable is a red herring here - and you probably don't realise you're actually doing it. What you want to do is merely check if the predicate sequence[q]>=sequence[q+1] is true for any q. So do this instead:
any(sequence[q]>=sequence[q+1] for q in range(len(sequence)-1))
This gives an iterable of booleans, and checks if any are True or not.
First remove the any() to see what your comprehension actually gives you:
[q for q in range(len(sequence)-1) if sequence[q]>=sequence[q+1]]
>>> [0]
That is, there is one pair of numbers where the condition is true, and it is at index 0 in the original list.
any([0]) is then False because 0 is false. any() checks each item to see whether it's truthy.
Robin's solution is the usual way to do it, by using the comparison result as the yielded value. But it can be quite a lot faster to not yield false values, which you might notice if your sequence is long enough, so you could use this form:
any(True for q in range(len(sequence)-1) if sequence[q]>=sequence[q+1])
I feel very confused about some code like this[not written by me]:
version = any(func1(), func2()) # wrong, should be any([func1(), func2()])
def func1():
if something:
return 1
else:
return None
def func2():
if something:
return 2
else:
return 3
version must be a num. when [func1(), func2()] is [1, None], should return 1, when is [None, 2], should return 2, when [1, 2], should return 1.
so I think it's wrong to use any() in this code, because any() just return True or False. If I rewirte this logic using another way, I can not find a graceful way as a pythoner.
I want to know whether any() can achieve the logic, if not, how to achieve it gracefully?
You can simply use or here.
version = func1() or func2()
Make sure the functions are defined before trying to call them.
This works because or returns the first True-like value or the last value (if no value is True-like) . And 'None' is considered False-like in Boolean context.
#AnandSKumar's answer is optimal. But just to give you some information on any if you are interested:
Take this example:
>>> def foo():
... return 2
...
>>> def boo():
... return 3
...
>>> def doo():
... return 4
...
>>> f = [foo, boo, doo]
>>> any(i() < 3 for i in f)
True
Ultimately what is happening inside the any is, iterate over the array of methods j, and indicate whether each item is less than 3, what the "any" will do in this case is return "ANY" condition that matches that. So even if you find one that is False, it will still return True.
There is another similar method to this called "all", that will ensure that ALL conditions are met based on your condition you are checking. Here is the example:
>>> all(i() < 3 for i in f)
False
So, as you can see, because one condition failed, it will return False.
For the arbitrary length case (where explicitly chaining or doesn't make sense), you can make a version of any that returns the first truthy value or a given value when all results are falsy with:
# If on Py2, you'll want to do this to get shortcircuiting behavior
from future_builtins import filter
result = next(filter(None, iterable_to_check), False) # False can be replaced with a chosen default
The filter only produces "truthy" values, and the two arg next gets the first "truthy" value, or the default value if filter finds no "truthy" values.
If the iterable_to_check is a non-empty Sequence (rather than merely Iterable), you can exactly match the behavior of chained ors (the "falsy" result is the last value, not a specific value like False or None) with:
result = next(filter(None, sequence_to_check), sequence_to_check[-1])
to use the final element ("truthy" or "falsy") as the result when all elements were "falsy".
To be clear, if the set of things to test is fixed and smallish, explicit or per Anand's answer is the better way to go.
This question already has answers here:
Python "all" function with conditional generator expression returning True. Why?
(2 answers)
Closed 9 years ago.
have a question on all() operator in Python.
say
array = ["one","one","one"]
all( x=="one" for x in array ) <<--- i want to check for all "one" in array
The above seem to work. however, if i have
array = []
all( x=="one" for x in array ) <<--- this still return true to me.
The behaviour is that i want it return false if all items are not "one". How to do it? thanks
You can read all() as if it means:
It returns False if any of the items evaluates to False. True otherwise.
So an empty set will return True, because there is none that will make it false.
Generally speaking, in an empty set, all the elements fullfill any requirement you can imagine. That's a principle of logic, not of Python, BTW.
all's implementation is equivalent to this
def all(iterable):
for element in iterable:
if not element:
return False
return True
So, it returns True till any of the elements in the iterable is Falsy. In your case that didnt happen. Thats why it returns True
all always returns True for an empty list/tuple/etc. This is because, technically, every item in an empty collection fulfills any and every condition there is.
To fix the problem, you need to add some additional code to test whether your list is empty or not. Fortunately, empty lists evaluate to False in Python, so you can just do this:
>>> array = []
>>> bool(array and all(x=="one" for x in array))
False
>>> if array and all(x=="one" for x in array):
... print True
... else:
... print False
...
False
>>>
How to do it?
array and all(x=="one" for x in array)
Empty lists are false, so the result is false and it doesn't matter that the all part is true.
If you want to deal with iterables other than containers like list then it's a bit harder. I suppose you need something like this:
set(x=="one" for x in iterable) == { True }
Although if you care about speed, the following should be faster on the whole, since the version above doesn't short-circuit like all does:
def nonempty_all(iterable):
iterator = iter(iterable)
try:
if not next(iterator):
return False
except StopIteration:
return False
return all(iterator)