I am reading Grokking Algorithms, which seems to be a highly recommended book. I am looking at the first algorithm for "binary search" and the guy uses two "ifs" instead of a "if" and "elif". Would using two "ifs" be better or faster?
def binary_search(list, item):
low = 0
high = len(list) - 1
while low <= high:
mid = (low + high)
guess = list[mid]
if guess == item:
return mid
if guess > item:
high = mid - 1
else:
low = mid + 1
return None
my_list = [1,3,5,7,9]
example1
>>> def foo():
a = 10
if a == 10:
print("condition1")
elif a == 10:
print("condition2")
else:
print(0)
>>> foo()
condition1
>>>
elif is guaranteed not to run when if is true.
example2
def foo():
a = 10
if a == 10:
print("condition1")
if a == 10:
print("condition2")
else:
print(0)
>>> foo()
condition1
condition2
>>>
example3
modify if statement of example2.
if a == 10:
print("condition1")
return a
output
>>> foo()
condition1
10
So, in your case adding a return in first if statement has similar operation like an if-elif block. the (return a) is preventing the second if statement to be executed in example3.
Multiple IFs
You use multiple ifs, when you want to accomplish different tasks that are independent of each other. And the execution of one of the tasks doesn't effect the execution of others.
Let's look at an example:
if primeMember:
makeDeliveryFree()
if validDiscountCoupon:
giveDiscount(couponCode)
if customersBirthday:
giveBirthdayDiscount()
So, in the above example we have different tasks that we want to perform under different conditions and the tasks are independent of each other. Making the delivery free doesn't effect the discount in any manner.
With multiple ifs, it could be possible that the statements within all the ifs get executed and on the other hand it could also be possible that none of the statements within the ifs get executed.
IF, ELIF, ELSE Chain
On the other hand we would use an if, elif, else chain, when we want to accomplish a particular task but we want to accomplish that differently under different conditions.
Let's look at an example:
if hasBalanceInWallet:
setPaymentMode("wallet")
elif hasCreditCardSaved:
setPaymentMode("credit-card")
else
showPaymentModeSelectorDialog()
So, in the above example the task that we're trying to accomplish is that of setting the mode of payment and we need to set it differently under different scenarios but we only want to set it once (i.e. we want only one of the branches to run).
As other answers mentioned, an elif is not necessary after an if that does a return. But they didn't cover the performance impact. I thought it would be interesting to test.
Turns out, it can be a tad faster to use if/elif/else instead of separate if's.
from timeit import timeit
import random
def count_items_ifelifelse(items, threshold):
below, on, above = 0, 0, 0
for item in items:
if item > threshold:
above += 1
elif item < threshold:
below += 1
else:
on += 1
return below, on, above
def count_items_ififif(items, threshold):
below, on, above = 0, 0, 0
for item in items:
if item > threshold:
above += 1
if item < threshold:
below += 1
if item == threshold:
on += 1
return below, on, above
def generate_items_and_threshold(count=100_000):
"""List of reproducible random numbers to test with. Set threshold at half"""
items = list(range(count))
random.Random(123).shuffle(items)
threshold = count // 2
return items, threshold
def run_test():
t1 = timeit(
"count_items_ifelifelse(i, t)",
setup="from __main__ import count_items_ifelifelse, generate_items_and_threshold; i, t = generate_items_and_threshold()",
number=1000,
)
print("count_items_ifelifelse: {:.2f}".format(t1))
t2 = timeit(
"count_items_ififif(i, t)",
setup="from __main__ import count_items_ififif, generate_items_and_threshold; i, t = generate_items_and_threshold()",
number=1000,
)
print("count_items_ififif: {:.2f}".format(t2))
if __name__ == "__main__":
run_test()
This outputs (Python 3.8.2, Windows):
count_items_ifelifelse: 6.69
count_items_ififif: 8.71
Roughly 20% faster, because of more unnecessary if evaluations each loop. The other side of it is that for if/elif/else the performance will vary based on how the data is sorted, and whether it checks the most occurring condition first.
Related
I have been part of a code review in which I have a condition such as this one (Python code):
my_list = <whatever list>
if len(my_list) <= 0:
do_something()
I have been pointed out that the condition should be == instead of <= as a list cannot have a negative length. I have no issues with it in this case, but I wanted to know if there is really any drawbacks on using <=.
Here is my rationale on why to use <=:
The actual condition that I want to check is not len(my_list) > 0 which is equivalent to len(my_list) <= 0, so that is correct for sure.
As explained in point 1, len(my_list) == 0 alone is not equivalent to the original condition. It is in this case, but it is not trivial why it is this way. len() returns the value returned by __len__() which may be an arbitrary. It only happens to be equivalent because the len() implementation performs some validations on the value. One could argue that len() implementation may change in the future, so the equivalence would not hold anymore.
I feel len(my_list) <= 0 would be safer in general than using len(my_list) == 0 plus an assumption that might not hold in some edge or unexpected cases.
So, are my arguments correct? Do you know any drawbacks of using len(my_list) <= 0?
Sorry if this seems like a non-relevant question, but this have been raised to me more than once on code reviews so wanted to understand if there is something I am missing.
So, after some more digging I found this in the python PEP 8 programming recommendations (https://peps.python.org/pep-0008/#programming-recommendations)
# Not recommended
my_list = []
if not len(my_list):
print('List is empty!')
# Recommended
my_list = []
if not my_list:
print('List is empty!')
so now you know the most pythonic way to check if a list is empty!
After testing a bit, I found this works even for custom classes! But my test might be incomplete.
class Test:
def __init__(self, len):
self.len = len
def __len__(self):
return self.len
t1 = Test(3)
if len(t1):
print("len1 > 0")
if t1:
print("len2 > 0")
t1 = Test(0)
if len(t1):
print("len3 > 0")
if t1:
print("len4 > 0")
t1 = Test(-1)
if len(t1):
print("len5 > 0")
prints
len1 > 0
len2 > 0
Exception has occurred: ValueError
__len__() should return >= 0
I am using Codelite to train myself as I just started learning Python. In the excercise "Smallest Positive Number", I used the code below and some test cases passed, but most of them not. How can I improve my code? (test cases like [1,2,3,4] and repetitive numbers like [5,6,2,4,6].
A = [2,1,-2,5,-6]
m = max(A)
temp = []
index = []
result = []
if m < 1:
print ("1")
elif len(A) == 1:
if A[0] == 1:
print("2")
else:
print("1")
else:
for i in range(len(A)):
if A[i] > 0:
temp.append(A[i])
temp.sort()
print(temp)
for j in range(len(temp)):
index.append(j+1)
if temp[j] != index[j]:
result.append(index[j])
print(min(result))
Thanks!
First you would want to store the smallest positive number. Start it at the first element of the list.
small_positive = A[0]
You would then want to iterate through the input
for number in A:
Then check if the number is positive. The easiest way is to see if it is greater then zero.
if number > 0:
After, check if the number is smaller then the current smallest. If so, overwrite it.
if number < small_positive:
small_positive = number
Lastly, you need to check if there were all negatives in the list. If small_positive is negative after the loop, there were no positives in the list.
if small_positive < 0:
return None
This is the easiest way to understand the solution, but there are many built in functions which are much simpler then this.
A = [2,-2,5,-6]
def small(a,b):
if a >= b: return b
else: return a
positive_list = [ele for ele in A if ele > 0]
res = min(positive_list)
if len(positive_list) == 0:
print("no positive number.")
else:
for i in range(len(positive_list)):
res = small(res, positive_list[i])
print(f"Smallest Positive number : {res}")
by this, yon can easily get a result.
For a start, this wouldn't work for the list [-42]:
m = max(A)
if m < 1:
print ("1")
There's no 1 anywhere in that given list so it should not be reported as the solution. Python would usually give you None in that scenario but the puzzle you've been set may have other ideas. It also wouldn't work with the list [] as max() will complain bitterly about the empty list.
You may think that one or both of those cases are unlikely but that's one of the things that makes the difference between a good coder and a not-so-good one. The former assume their users and testers are psychotic geniuses who know how to break code with near-zero effort :-)
To code it up by yourself, you can use the following pseudo-code(1) as a basis:
num_list = (some list of numbers)
min_pos = nothing
for num in num_list:
if num > 0 and (min_pos is nothing or num < min_pos): # >= ?
min_pos = num
print(min_pos)
Of course, if you're learning Python rather than learning programming, you should probably just use the facilities of the language, things will be much easier:
pos_list = [item for item in the_list if item > 0] # >= ?
min_pos = min(pos_list) if len(pos_list) > 0 else None
print(min_pos)
And, if you consider zero to be positive, make sure you use >= 0 instead of > 0, in both code blocks above where indicated.
Both of these snippets will successfully handle lists with no positive numbers, giving you a sentinel value (Python None or pseudo-code nothing).
(1) Yes, it looks a bit like Python but that's only because Python is the ultimate pseudo-code language :-)
I simply do not understand why this is not returning the value and stopping the recursion. I have tried everything but it seems to just keep on going no matter what I do. I am trying to get the program to get the loop to compare the first two values of the list if they are the same return that it was the first value. If they were not, add the first and second values of each list and compare, etc etc until it reaches the end of the list. If the sum of the values in each list never equal each other at any point then return 0.
It is supposed to take three inputs:
A single integer defining the length of the next two inputs
First set of input data
Second set of input data
Ex input
3
1 3 3
2 2 2
It should output a single number. In the case of the example data, it should output 2 because the sum of the lists equalled at the second value.
N = int(input())
s1 = input().split()
s2 = input().split()
count = 0
def func1(x,y):
if x == y:
return(count)
elif (N - 1) == count:
return(0)
else:
count + 1
return(func1(x + int(s1[count]), y + int(s2[count])))
days = func1(int(s1[0]),int(s2[0]))
print(days)
I am sorry in advance if I really messed up the formatting or made some dumb mistake, I am pretty new to programming and I have never posted on here before. Thanks in advance :)
The problem is that you never actually update the variable count. However, just writing:
count += 1
is not going to work either without declaring the variable global:
def func1(x, y):
global count
....
That said, global variables increase code complexity and break re-enterability, i.e. the same function can no longer be called twice, not to mention about concurrency. A much cleaner way is to make count a function argument, it will look like this (the code not tested and is here for illustration only):
N = int(input())
s1 = [int(c) for c in input().split()]
s2 = [int(c) for c in input().split()]
def func1(x, y, count=0):
if x == y:
return count
elif count == N - 1:
return 0
else:
return(func1(x + s1[count], y + s2[count]), count + 1)
days = func1(int(s1[0]),int(s2[0]))
print(days)
To answer "How would you go about solving this problem then" – If I understood the problem correctly, the aim is to find the index where the "running total" of the two lists is the same. If so,
def func1(s1, s2):
total_a = 0
total_b = 0
for i, (a, b) in enumerate(zip(s1, s2)):
total_a += a
total_b += b
if total_a == total_b:
return i
return 0
print(func1([1, 3, 3], [2, 2, 2]))
does the trick. (I've elided the input bits here – this function just works with two lists of integers.)
I need to write a script that generates random numbers between 1-257000 and stops when a certain number occurs telling me how many numbers it generated so far.
i manged to get this far but can't seem to get it to stop or count
x=1
while x < 257000:
import itertools
import random
def random_gen(low, high):
while True:
yield random.randrange(1, 257000)
gen = random_gen(1, 100)
items = list(itertools.islice(gen, 10))
print items
x = x+1
Thank you so much for your help
Huh. A few flaws (or at least unclear spots) in your code.
You run your loop max 257000 times. Even though the probability is low, there is a chance that you don't hit the number you seek in the loop.
Move your import statements out of your loop, no need to have python check loaded modules each round.
You use a generator for choices of a list (randrange) where you can simply use a randint() call.
You define a closed function within your loop which creates a new function at a new memory address each round.
You slice your results into lists of 10 elements each; is this for printing, or do you actually need your random integers grouped into such lists?
A very simple and straightforward implementation of your described problem could be:
import random
num = 0 # Our counter
certain_number = 123456 # The number we seek
while True: # Run until we break
# Increment for each new step
num += 1
# Generate a single number from the given range
random_number = random.randint(1, 257000)
if random_number == certain_number:
# Break if we hit it
break
print('Hit after {} tries.'.format(num))
>>> Hit after 382001 tries.
First, put your import statements and your function definitons outside your while-loop. That's being super redundant.
>>> def random_gen(low,high):
... while True:
... yield random.randrange(low,high)
...
>>> lucky = 7
>>> rg = random_gen()
>>> rg = random_gen(1,1000)
>>> next(itertools.dropwhile(lambda t: t[1] != lucky, enumerate(rg, 1)))
(811, 7)
>>>
Here's another run, just for fun:
>>> rg = random_gen(1,257000)
>>> n,L = next(itertools.dropwhile(lambda t: t[1] != lucky, enumerate(rg, 1)))
>>> n
22602
>>> L
7
>>>
How can I tell if a list (or iterable) of numbers all have the same sign?
Here's my first (naive) draft:
def all_same_sign(list):
negative_count = 0
for x in list:
if x < 0:
negative_count += 1
return negative_count == 0 or negative_count == len(list)
Is there a more pythonic and/or correct way of doing this? First thing that comes to mind is to stop iterating once you have opposite signs.
Update
I like the answers so far although I wonder about performance. I'm not a performance junkie but I think when dealing with lists it's reasonable to consider the performance. For my particular use-case I don't think it will be a big deal but for completeness of this question I think it's good to address it. My understanding is the min and max functions have O(n) performance. The two suggested answers so far have O(2n) performance whereas my above routine adding a short circuit to quit once an opposite sign is detected will have at worst O(n) performance. Thoughts?
You can make use of all function: -
>>> x = [1, 2, 3, 4, 5]
>>> all(item >= 0 for item in x) or all(item < 0 for item in x)
True
Don't know whether it's the most pythonic way.
How about:
same_sign = not min(l) < 0 < max(l)
Basically, this checks whether the smallest element of l and the largest element straddle zero.
This doesn't short-circuit, but does avoid Python loops. Only benchmarking can tell whether this is a good tradeoff for your data (and whether the performance of this piece even matters).
Instead of all you could use any, as it short-circuits on the first true item as well:
same = lambda s: any(i >= 0 for i in s) ^ any(i < 0 for i in s)
Similarly to using all, you can use any, which has the benefit of better performance, as it will break the loop on the first occurrence of different sign:
def all_same_sign(lst):
if lst[0] >= 0:
return not any(i < 0 for i in lst)
else:
return not any(i >= 0 for i in lst)
It would be a little tricky if you want to consider 0, as belonging to both groups:
def all_same_sign(lst):
first = 0
i = 0
while first == 0:
first = lst[i]
i += 1
if first > 0:
return not any(i < 0 for i in lst)
else:
return not any(i > 0 for i in lst)
In any case, you iterate the list once instead of twice as in other answers. Your code has the drawback of iterating the loop in Python, which is much less efficient than using built-in functions.