In Python: How to deal with zero at first position in condition - python-3.x

I found some strange behavior in python. Possibly my logic is not correct.
1 and 2 and 3 in range(5)
Expected: True
Outcome: True
2 and 1 and 99 in range(5)
Expected: False
Outcome False
2 and 1 and 0 in range(5)
Expected: True
Outcome: True
Now the tricky one:
0 and 1 and 2 in range(5)
Expected: True
Outcome: 0
I am sure there is someone who makes me find my logical error.

In each expression, only the last number is checked against the range. The previous ones are evaluated "as is". In python, the expression if i: evaluates to True if i is not 0.
The value returned from the expressions (boolean or int) depends on what the conditions are. If you leave just 1 and 2 for example, the result will be the last int. However, since you have the v in range(n) expression, which returns True or False, the result is cast into a boolean value.
Now, due to short-circuit evaluation, in the last case, only the zero gets evaluated. So the result is not cast into a boolean and 0 is returned.
Edit: After reading the comments, it becomes clear that you want to check if k number exist in range(n). For that, you cannot use the simple expressions you've shown. You need to check if every individual value exists in the range. One - inefficient - approach would be this
if all([v in range(n) for v in values]):
print("All values exist in the range")
Edit 2 (by #Pranav Hosangadi)
Side note:
Since the all() function takes generator expressions, you can avoid the list-comprehension altogether. When you do this, the generator expression will only calculate as many items as needed for the all() to short-circuit. On the other hand, the list-comprehension approach will calculate all elements in the list, and then run all() on that list. Here's a simple example:
l = [100] * 10000
l[-1] = 0
def f1(): # Using generator
return all(li == 0 for li in l)
def f2(): # Using list comp
return all([li == 0 for li in l])
Now for the generator-expression approach, all() needs to calculate only the first element to know that it will short-circuit to False. However, the list-comprehension approach calculates all elements of the list first. All but the last element of this list are False. Then, all() takes this list and short-circuits at the first element. Running some timing on these functions:
import timeit
timeit.timeit('f1()', setup='from __main__ import f1, l', number=10000)
# Out: 0.006381300001521595
timeit.timeit('f2()', setup='from __main__ import f2, l', number=10000)
# Out: 5.257489699986763
f1() is significantly faster than f2().

Related

How to convert a conditional statement to a simple expression? Is compounding a return this way Acceptable practice?

The exercise I am doing was given by a book that takes a dictionary argument and asks for me to give a return value of True or False. I am new to Python 3 and as a personal exercise for learning I want to convert all the conditions of a "valid dictionary as a chessboard into" a single return value. I haven't actually tested this code for errors as it isn't finished, but I did run it through an online validator I found https://extendsclass.com/python-tester.html.
I want to know how I can convert the following 2 code blocks into simple expressions to be used in the return statement in my function below, You can see below that I've converted most expressions into the return value already with "and" because "ALL expressions must == True"
for pieces in dictionary.values():
if all(pieces.startswith('b')) or \
all(pieces.startswith('w')):
return True
else:
return False
The above code block loops through the dictionary keys passed to function as "pieces",
and compares each key individually to determine if it starts with a value of 'b' or 'w'. So if any key does not start with 'b' or 'w' the dictionary "chessboard" is false as it contains an improper piece. Ok I think I see an error in this I'm going to look into it and try to figure it out. Ok I noticed some errors in the above code that need to be addressed I am currently researching how to properly execute the above code.
for square in dictionary:
try:
if int(square[:0]) <= 8 and \
square[-1] <= 'h':
return True
else:
return False
except ValueError:
return False
I worked on the above code block a very long time and am still not sure that's the "best" implementation of what I want it to do. But I am still new and did my best.
Anyway it slices the dictionary key and compares the first char in the key to make sure it isn't over 8 which is the maximum "valid range" if over valid range it returns false and anything not int is obviously automatically False and returned as such by the "exception".
Then it slices the dictionary key to get the last char of the dictionary key and compares it to <= 'h' as that is the valid range and anything over 'h' or not a valid type value will return as False.
And then it compares the results of True/False "and" True/False with "and" because both conditions must be True.
Here is the function as it currently is with a test dictionary at the end:
def cBV(dic): # (c)hess(B)oard(V)alidator
Err = 'Error: Invalid Board -'
if not isinstance(dic, type({})):
raise TypeError('Object passed to cBV is not of type <class dict>')
chess_pieces = {'bk pieces': 0, 'wh pieces': 0,
'bk pawns': 0, 'wh pawns': 0}
# converts dictionary values into keys and assigns those keys "counn of values"
for squares, pieces in dic.items:
if pieces.startswith('b'): # alt if pieces[:0] == 'b':
if pieces.startswith('bpawn'): # alt if pieces == 'bpawn':
chess_pieces['bk pawns'] += 1
chess_pieces['bk pieces'] += 1
elif pieces.startswith('w'):
if pieces.startswith('wpawn'):
chess_pieces['wh pawns'] += 1
chess_pieces['wh pieces'] += 1
return 'wking' in dic.values() and \
'bking' in dic.values() and \
chess_pieces['bk pieces'] <= 16 and \
chess_pieces['wh pieces'] <= 16 and \
chess_pieces['bk pawns'] <= 8 and \
chess_pieces['wh pawns'] <= 8 and \
dict = {'8h': 'wking', '2c': 'bking', '3a': 'wpawn', '3b': 'wpawn', '3c': 'wpawn',
'3d': 'wpawn', '3e': 'wpawn', '3f': 'wpawn', '3g': 'wpawn', '3h': 'wpawn', '4b': 'wpawn'}
test = cBV(dict)
print('Is this a valid chessboard? ' + str(test))
What you have now is good, and you should feel proud - there are more fancy techniques for making things more concise, and you'll get more used to them as you wrap your head around how the various data structures work.
for pieces in dictionary.values()
if pieces.startswith('b') or \
pieces.startswith('w'):
return True
else:
return False
can be converted to the one-liner
return all(
piece.startswith('b') or piece.startswith('w')
for piece in dictionary.values()
)
which does a few things.
The all() function takes any iterable object, and returns True if all of the values in that iterable are truthy. If even one of them is not, then it 'short-circuits' and returns False instead.
As our argument to all(), we give a "comprehension". A comprehension is essentially a one-line for loop, of the form f(element) for element in iterable: it performs whatever f(element) is, for every element in the iterable.
In our case, the iterable is dictionary.values(), which returns all of the values (but not the keys) in dictionary (which, here, is 'wking', 'wpawn', ...). In this case, these are strings.
piece is what we assign each element of, for each 'iteration' of the comprehension. It'll run for piece = 'wking', then for piece = 'wpawn', etc.
piece.startswith('b') or piece.startswith('w') is the function that we perform for each piece. This outputs either True or False, depending on whether the conditions are met.
You can wrap a comprehension in square-brackets [] to have it output as a regular list. However, if you give a comprehension as an argument to a function like all(), which is what we're doing here, then it will end up as a "generator", a slightly more efficient object that only calculates one object at a time. For our purposes, this isn't important.
The comprehension, overall, produces a series containing either True or False, that all() will consume.
Similarly with your second code snippet. You have the basics down, but can be more concise. Your code:
def allSquaresAreInRange(dictionary):
for square in dictionary:
try:
if int(square[:0]) <= 8 and \
pieces[-1] <= 'h':
return True
else:
return False
except ValueError:
return False
can be turned into
def allSquaresAreInRange(dictionary):
try:
return all(
(1 <= int(row) <= 8) and ('a' <= col <= 'h')
for (row, col) in dictionary
)
except ValueError:
return False
Here we make use of a few things:
As before, we use all(), and as before, we use a comprehension. But this time, we iterate through dictionary directly
Iterating through a dict is functionally identical to iterating through dict.keys(). So, we're iterating through the keys '8h', '2c', ...
Each key is a two-character string. Strings are iterable, just like lists are, and most iterables have an interesting property called "multiple assignment": if we assign exactly as many variables as the iterable has elements, then those elements get split up.
(row, col) = '8h', for example, is functionally identical to row = '8h'[0] and col = '8h'[1]. In both cases, we're left with row = '8' and col = 'h'.
This produces a ValueError if the number of elements on either side is mismatched - for example, if the key has only one character, or only three characters. A byproduct of this is that row and col are guaranteed to be exactly one-character long strings, if that error doesn't happen.
Our condition checks if the row is between 1 and 8, and whether the col is between A and H, using greater than/less than signs. This returns True or False, once again.
As you seemed to discover, using int() on something that doesn't represent an integer will also throw a ValueError.
This new snippet keeps the try/except blocks you came up with in yours, because they work just fine.
Python has a bit of a culture surrounding it that prides 'efficiently-written' code. Which is to say, code that looks as fancy as possible, and follows Functional Programming paradigms. Comprehensions, as well as all() and any(), are a big part of that, and so they're probably the 'correct' solution for any problem which they are a solution for (if they can be written concisely).
Similarly, the snippet
if condition:
return True
else:
return False
can almost always be condensed to
return condition
(or return bool(condition), in the case that condition deals with a value that has truthiness but isn't a boolean, such as None or an empty list or string). If this is applicable, it's good practice (but again, it's not always applicable).
The most important thing, though, is that your code works the way you want it to, and that it's clear enough for you to come back to it a few months down the line and figure out what you were doing, and why you were doing it that way. There are some cases where things can be written as comprehensions but that makes them extremely complicated and unreadable - and in those cases, it's sometimes a good idea to not write them as comprehensions, and do it the more verbose way. Just keep that in mind as you're continuing to develop, and you'll do fine.

Is there a way to return true for an empty list in a sort invariant?

So I am supposed to write a function that returns true if the said sequence is arranged from smallest to largest. I am able to understand strings and num lists but for an empty list, I am unable to understand why it won't return as true.
This is for understanding and possibly help me understand how to manipulate my loops better.
def is_sorted(seq):
for i in range(0, len(seq)):
if seq[i]<seq[i+1]:
return True
else:
return False
print(is_sorted([])) # expected to be true but returns none
The obvious problem is that with an empty list you are returning None which evaluates to false. The other problem is that you are using return inside of a loop, which means that you aren't evaluating every sequence in the iterable.
#hiro protagonist's answer is one solution to this problem. I offer my alternative using all and a generator expression.
def is_sorted(seq):
return all(seq[i] < seq[i + 1] for i in range(len(seq) - 1))
# All evaluate True
print(is_sorted(['a', 'b', 'c']))
print(is_sorted([1, 2, 3]))
print(is_sorted([]))
# Both evaluate False
print(is_sorted(['a', 'c', 'b']))
print(is_sorted([0, 1, -1]))
Edit with explanation
As best I understand it, all works by stepping through an iterable and returns False if any value in it evaluates to False, otherwise returning True.
As the comments may show you, I don't have a good understanding of Python generators. A generator is an iterable object that calculates the next value and yields it back each time it is referenced.
The generator defined above, each time that all references it, calculates seq[i] < seq[i + 1] and gives that value back. If this is False at any time then all will return False.
I hope this helps. I'm sure one of the good people in the comments will correct any flawed understanding that I have.
Your implementation is wrong. It will return True for [1, 3, 2] since it only compares the first 2 elements (return returns after the first iteration).
It can be fixed by checking for the opposite condition, then return True after the loop.
You should also iterate until len(seq) - 1 otherwise the last iteration will cause an IndexError.
def is_sorted(seq):
for i in range(0, len(seq) - 1):
if seq[i] > seq[i + 1]:
return False
return True
print(is_sorted([1, 2, 3]))
# True
print(is_sorted([1, 3, 2]))
# False
print(is_sorted([]))
# True
And of course there is the trivial, naive solution,
def is_sorted(seq):
return seq == sorted(seq)
this is a variant that also works for empty lists and lists of length 1:
from itertools import islice
def is_sorted(seq):
return all(i <= j for i, j in zip(seq, islice(seq, 1, None)))
it iterates over seq[k] and seq[k+1] using zip and islice. and only if all elements satisfy the requirement True will be returned.
Its because for an empty list the code inside the for is not reached. So neither return statement isnt reached. Also you should take into account that a list with only one element should also return True. Solution:
def is_sorted(seq):
for i in range(0, len(seq)-1):
if seq[i]>=seq[i+1]:
return False
return True

First 20 values that are true

for n in range(561,1000):
if n is not isprime2(n) and isprimelike(n):
print(n,isprimelike(n))
I want my function to print the first 20 values that satisfy this condition instead of printing the whole range. Like once it sees 20 values that make this condition true, stop.
First, I'm assuming that this is a typo: if n is not isprime2(n) and isprimelike(n) makes no sense. You want if not isprime2(n) and isprimelike(n)
To solve this, I would create a generator comprehension with a condition out of your loop:
(n for n in range(561,1000) if not isprime2(n) and isprimelike(n))
(you don't need to print isprimelike since it is True in your case, we filtered False values out)
then intergrate it to loop 20 times and get the 20 first iterations, in one line:
[next(n for n in range(561,1000) if not isprime2(n) and isprimelike(n)) for _ in range(20)]
or (courtesy to Jon), using itertools.islice which is better if there aren't enough values (solution above throws StopIteration in that case)
list(itertools.islice((n for n in range(561,1000) if not isprime2(n) and isprimelike(n)),20))
Of course there is a simpler method of completing this task, an easy-on-the-eye way to do this is to simply create a variable that stores the amount of prints.
I.E
AoPrints = 0 # Amount of Prints
for n in range(561,1000):
if n is not isprime2(n) and isprimelike(n): # NOTE: the first condition here does not look accurate
print(n,isprimelike(n))
AoPrints = AoPrints+1
if AoPrints > 20:
break # Exit the for-loop

Python- How to fix failed testcase on a has repeat function?

I've built a function that checks for repeats of a specific number in a list named xs. V is the number to check for repeats of. It needs to return True if there are more than one occurrences of the number and if there are none, it needs to return False.
I'm failing one test case which is input xs=[1,2,1] v=1, this function needs to return True, but my code is making it False. Can you see where I went wrong?
Here is my current code:
def has_repeat(xs, v):
count=0
for num in range(len(xs)):
if num == v:
count+=1
if count>1:
return True
else:
return False
You're actually iterating over the range of the length of the list, not the items in the list.
The range function returns a list of numbers from 0 (by default) to the number you provide, in this case 3 (not inclusive). See Python documentation.
As an example if you try:
l = [1, 2, 3]
print(range(len(l)))
It will print out [0, 1, 2]
What you should do is instead of
for num in range(len(xs))
do
for num in xs:
You can try it out on PyFiddle here
As an added tasty bonus, you could change this to use the .count method on your list of items to check how many occurrences of that number are in the list, removing the need to iterate the list at all, like so:
count = xs.count(v)

recursion not stopping with 'if'

I am trying to write a code which prints True if given string has at max 2 consecutive c, and at max 1 b. I am using recursion to reduce the string and check that at max 'c' is present in the same index twice.But my recursion is not stopping till it empties the whole list. Can you please suggest what's wrong with my code. Thanks!
def stringcond(N,count=0,k=0):
N=list(N)
if(N.count('b')>1):
return False
if len(N)<2:
return True
else:
for i,j in enumerate(N):
if(j=='c'):
del N[i]
count+=1
if(k==i and count>2):
return False
stringcond(N,count=count,k=i)
return True
You have several mistakes. First, why are you splitting the characters into a list? There is a perfectly good count method for strings.
Your recursion fails because you ignore the return value. You would want something like
if not stringcond(N,count=count,k=i):
return False
# I make no claim that this logic is correct.
In any case, there is no need to recur. Use count to check the quantity of "b" and many-'c' substrings:
def stringcond(s, c_max=0):
return s.count('b') <= 1 and \
s.count("c" * (c_max+1)) == 0
You have to use the result of the stringcond call. Now your function will only return whatever was determined on the top level call.

Resources