What is the pythonic way of writing a long if-else statement? - python

I am quite new to Python though I have had experience in Java before.
I have the following code to convert a score into an interval delta (as part of a Spaced Repetition System (SRS) program I am implementing). This code looks ugly and not very readable. Typically I would have used a switch statement in Java. But in python I could not find an equivalent. Hence I have written this code which produces the result. But I want to take this opportunity to learn different options for achieving the same output using more pythonic ways. I would appreciate any help, even if they are pointers to resources that I can read to improve this code.
# Convert score into an interval delta
if score == 0: # Perfect page
intervalDelta = +3
elif score == 1: # 1 Word Mistake
intervalDelta = +2
elif score <= 3: # 3 Word Mistakes
intervalDelta = +1
elif score == 4: # 1 Line Mistake
intervalDelta = 0
elif score <= 8: # 2 Line Mistakes
intervalDelta = -1
elif score <= 12: # 3 Line Mistakes
intervalDelta = -2
elif score <= 20: # 5 Line Mistakes
intervalDelta = -3
elif score <= 30: # 7.5 Line Mistakes - Half a page
intervalDelta = -5
else: # More than half a page
intervalDelta = -7

I don't find the long if statement that unreadable, although I'd change to using <= for consistency (since the == cases are already single possibilities using <=). If I were implementing it from scratch, I'd probably write something like the following simply to have the associations more visible:
from math import inf
SCORE_DELTAS = (
(0, +3),
(1, +2),
(3, +1),
(4, +0),
(8, -1),
(12, -2),
(20, -3),
(30, -5),
(inf, -7),
)
def score_to_delta(score):
for bound, delta in SCORE_DELTAS:
if score <= bound:
return delta

The official position on switch statements is that switch statements are easily done with if statements, so writing long if statements is the Python way. PEP 275 and PEP3103 document the reasons why switch statements were omitted. In practice though there are several alternatives to the switch statement.. The most common is the dictionary approach to switches.
def case_a():
print('Executing case A')
def case_b():
print('Executing case B')
def case_else()
print('Executing default case')
def dict_switch(argument):
switch = {
'a': case_a
'b': case_b
}
return switch.get(argument, case_else)()
dict_switch(x)
Another approach is to keep all of the conditional logic in a class, but I find this is only necessary when there is a significant amount of conditional logic.
class Switcher:
def numbers_to_methods_to_strings(self, argument):
"""Dispatch method"""
# prefix the method_name with 'number_' because method names
# cannot begin with an integer.
method_name = 'number_' + str(argument)
# Get the method from 'self'. Default to a lambda.
method = getattr(self, method_name, lambda: "nothing")
# Call the method as we return it
return method()
def number_0(self):
return "zero"
def number_1(self):
return "one"
def number_2(self):
return "two"
In your case, however, I think #asthasr approach is the cleanest.

Related

Fastest way to check for multiple conditions

I am looking to check for 3 conditions, any of which triggers a continue.
The 2 ways I am looking at are
1) if with multiple conditions
2) if and elif
def conditions_1(a,b,c):
numbers = []
min_no = min(a,b,c)
max_no = max(a,b,c)
for no in range(min_no,max_no+1):
if no == 0 :
continue
elif no + min_no == 0:
continue
elif math.gcd(min_no, no)> 1:
continue
else:
numbers.append(no)
return(numbers)
def conditions_2(a,b,c):
numbers = []
min_no = min(a,b,c)
max_no = max(a,b,c)
for no in range(min_no,max_no+1):
if no == 0 or no + min_no == 0 or math.gcd(min_no, no)> 1:
continue
else:
numbers.append(no)
return(numbers)
for _ in range(10):
t0 = time.time()
conditions_1(-5000, 10000, 4)
t1 = time.time()
conditions_2(-5000, 10000, 4)
t2 = time.time()
if t2-t1 > t1-t0:
print('2nd')
else:
print('1st')
May I know if there is a difference in both ways?
Thanks to the fact that or has short-circuit evaluation (i.e., it evaluates the list of conditions left to right and stops at the first True), the execution pattern is the same between your two variants (minus the fact that in the if/elif case you may have multiple jumps when testing each condition).
Coding-style-wise, the second is of course a lot better (no repetition of continue, clearer intent of the if/else block) and should be the way you structure your code.
Side note: Remember that if an expression gets too long, you can put it in parentheses and break it over several lines:
if (some_very_lengthy_condition_1 == my_very_lengthy_name_1 or
some_very_lengthy_condition_2 == my_very_lengthy_name_2 or
some_very_lengthy_condition_3 == my_very_lengthy_name_3 ):
pass # do something
As Gábor noted in the comments, in python you also have the any and all operators, which apply to iterables. any(iterable) is equivalent to oring all values in the iterable, while all(iterable) is equivalent to anding them. Short-circuit logic applies here as well, so that only the minimal number of values in iterable are evaluated when computing the expression result.

Finding multiples using recursion

Given 1 to 100 numbers, for multiples of 3 it should print "he" ,for multiples of 5 it should print "llo" ,for both multiples of 3 and 5 it should print "hello".
This is what I have:
for i in range (1,100):
if(i%3==0):
print("he")
elif(i%5==0):
print("llo")
elif(i%3==0 and i%5==0):
print("hello")
How would I do this recursively?
How about the code below?
def find_multiples(current, last_num=100):
# Base Case
if current > last_num:
return
result = ""
if current % 3 == 0:
result += "he"
if current % 5 == 0:
result += "llo"
if result:
print(f"{current}: {result}")
find_multiples(current+1, last_num)
find_multiples(1)
Base case is if current reaches last_num or the maximum number you'd like to check.
Here is a general outline for doing simple recursive things in python:
BASE_CASE = 1 #TODO
def f(current_case):
if current_case == BASE_CASE:
return #TODO: program logic here
new_case = current_case - 2 #TODO: program logic here ("decrement" the current_case somehow)
#TODO: even more program logic here
return f(new_case) + 1 #TODO: program logic here
Of course, this doesn't handle all possible recursive programs. However, it fits your case, and many others. You would call f(100), 100 would be current_value, you check to see if you've gotten to the bottom yet, and if so, return the appropriate value up the call stack. If not, you create a new case, which, in your case, is the "decrement" logic normally handled by the "loop" construct. You then do things for the current case, and then call the function again on the new case. This repeated function calling is what makes it "recursive". If you don't have an "if then" at the beginning of the function to handle the base case, and somewhere in the function recall the function on a "smaller" value, you're probably going to have a bad time with recursion.
This recursive function prints multiples of a number! hope it helps
def multi(n,x):
if x == 12:
print(n*x)
else :
print(n*x,end =",")
multi(n,x+1)
print(multi(4,1));

Python 3 Passing a Function into Another Function

I am somewhat new to Python, and this is a homework question, so I would appreciate no answers, just advice to help me understand. I am writing a game program that plays two different strategies against each other - a greedy strategy and a zoom-in strategy, which I have written as function. I have a game function that needs to pass in my greedy and zoom-in functions, as well as a game board. I need to be able to have either strategy function go first. So far I can only get it to where my greedy strategy goes first.
def game(P1,P2,board):
P1 = 0
P2 = 0
for x in range(len(board)):
if x%2 == 0:
move = greedy(board)
P1 += board[move]
board.remove(board[move])
else:
move = zoomin(board)
P2 += board[move]
board.remove(board[move])
if P1 > P2:
return 1
elif P1 == P2:
return 0.5
else:
return 0
This strategy always assumes that P1 is the greedy function, but I need to be able to play either first. I thought I could pass in the functions, so my call would be
game(greedy,zoomin,board)
but I am not sure how to actually implement it so that it can recognize who is playing first.
Thank you in advance for your help!
EDIT:
Here are my greedy and zoomin functions:
def greedy(board):
if board[0] > board[len(board)-1]:
#returns position of first item
return 0
elif board[len(board)-1] > board[0]:
#returns position of last item
return len(board)-1
else:
#if board[-1] == board[0]
return 0
def zoomin(board):
if len(board)%2 == 0:
evens = 0
odds = 0
for x in range(len(board)):
if x%2 ==0:
evens += board[x]
else:
odds += board[x]
if evens > odds:
return 0
else:
return len(board)-1
else:
#choose the larger value (greedy)
if board[0] < board[len(board)-1]:
return len(board)-1
else:
return 0
This is not a direct answer to your question (since senshin already answered it), but I wanted to point out that you can decrease your code duplication by using arrays instead. For instance, like this:
def game(players, board):
scores = [0] * len(players)
while i in range(len(board))
p = i % len(players)
move = players[p](board)
scores[p] += board[move]
del board[move] # <-- This is also a faster and more fail-safe version of your "board.remove(board[move])"
return scores
You can then call this function as game([greedy, zoomin], board). Also note how it extends to an arbitrary number of players, although that may not actually be useful for you. :)
You will want to rewrite your game function slightly. Notice that your game function accepts P1 and P2, but you don't do anything with them - you immediately assign 0 to both of them.
The correct way to approach this is to have your game function accept two strategies, which can be greedy or zoomin, or whatever else you might come up with later.
def game(strategy1, strategy2, board):
You will also need to replace the explicit calls to greedy and zoomin in the function body (e.g. move = greedy(board)) with calls to the strategies passed into your function instead - something like move = strategy1(board).
Then, in order to have greedy play first and zoomin play second, you could call:
game(greedy, zoomin, board)
Or if you wanted zoomin first and greedy second, you could call:
game(zoomin, greedy, board)
As you can see, the order of play is determined by the order in which you pass the two strategies into your function. Let me know if this needs clarification.

Basic Python Function replacing values

I am attempting to create a function with the following characteristics;
function name: new_scores,
parameters: (p1_score,p2_score,current_score,current_player)
p1_score: user inputed player 1 score,
p2_score: user inputed player 2 score,
current_score: the current score of set player after any actions performed
current_player: either 'PLAYER_ONE' or 'PLAYER_TWO'
the problem with my code is that it doesnt work in all cases, for example try replacing values in PLAYER_ONE, it just spits back out the p1_score, and p2_score that i inputed. Thanks for the help
def new_scores(p1_score,p2_score,current_score,current_player):
if current_player == 'PLAYER_ONE':
p1_score = current_score
return (p1_score,p2_score)
elif current_player == 'PLAYER_TWO':
p2_score = current_score
return (p1_score,p2_score)
Your code is needlessly complex, which is quite the feat for so few lines. :) There's no point in the assignment, just return the intended data directly:
def new_scores(p1_score, p2_score, current_score, current_player):
if current_player == 'PLAYER_ONE':
return (current_score, p2_score)
elif current_player == 'PLAYER_TWO':
return (p1_score, current_score)
Still, your code looks correct. Verify that the parameters are correct, using strings for semantics like this is a bit error-prone.
Make sure you are using the returned value and not expecting the parameters you passed in to be altered as a side effect.
>>> new_scores(3, 4, 5, "PLAYER_ONE")
(5, 4)
>>> new_scores(3, 4, 7, "PLAYER_TWO")
(3, 7)
Running your function as above shows it works as expected

How can I use recursion to find palindromes using Python?

I've just started exploring the wonders of programming. I'm trying to write a code to identify numeric palindromes. Just looking at numbers and not texts. I'm trying to learn to use recursion here. But I'm just not getting anywhere and I can't figure out what's wrong with it.
My idea was to check first string vs the last, then delete these two if they match, and repeat. Eventually there'll be nothing left (implying it is a palindrome) or there will be a couple that doesn't match (implying the reverse).
I know there are better codes to finding palindromes in but I just wanted to try my hand at recursion.
So what's wrong?
def f(n):
global li
li=list(str(n))
if (len(li)==(1 or 0)):
return True
elif li[len(li)-1]==li[0]:
del li[0]
del li[len(li)-1]
if len(li)==0:
return True
if len(li)>0:
global x
x=''.join(li)
str(x)
f(x)
else:
return False
Thanks in advance!
A few comments
Why are x and li globals? In recursion, all variables should be local.
Why are you converting back and forth between str and list? You can subscript both of them
You need to return the result of your recursive call: return f(x)
Try these suggestions, and see how it works out.
Before looking into it too much, if (len(li)==(1 or 0)): doesn't do what you're expecting it to do. (1 or 0) will always evaluate to 1.
You probably want:
if len(li) in (1, 0):
There are a couple of problems with your solution. Let me analyse them line by line.
You don't need global statements if you don't intend to change variables outside of function scope. Thus, I removed two lines with global from your code.
li=list(str(n)): casting a string to a list is unnecessary, as a string in Python has a similar interface to an immutable list. So a simple li = str(n) will suffice.
if (len(li)==(1 or 0)):: although it looks OK, it is in fact an incorrect way to compare a value to a few other values. The or operator returns the first "true" value from its left or right operand, so in this case it always returns 1. Instead, you can use the in operator, which checks whether the left operand is an element of a right operand. If we make the right operand a tuple (1, 0), all will be well. Furthermore, you don't need parentheses around the if statement. You should write: if len(li) in (1, 0):
elif li[len(li)-1]==li[0]: is fine, but we can write this shorter in Python, because it supports negative list indexing: elif li[-1] == li[0]:
Because we don't use lists (mutable sequences) because of point 2., we can't do del li[0] on them. And anyway, removing the first element of a list is very inefficient in Python (the whole list must be copied). From the very same reason, we can't do del li[len(li)-1]. Instead, we can use the "splicing" operator to extract a substring from the string: li = li[1:-1]
if len(li)==0: is unnecessary long. In Python, empty strings and lists resolve to False if tested by an if. So you can write if not li:
if len(li)>0:: You don't have to check again if li is not empty -- you checked it in point 6. So a simple else: would suffice. Or even better, remove this line completely and unindent the rest of the function, because the body of the if in 6. contains a return. So if we didn't enter the if, we are in the else without writing it at all.
x=''.join(li): We don't need to convert our string to a string, because of the decision made in 2. Remove this line.
str(x): This line didn't do anything useful in your code, because str() doesn't modify its argument in place, but returns a new value (so x = str(x) would have more sense). You can also remove it.
f(x): This is a valid way to call a recursive function in Python, but you have to do something with its value. Return it perhaps? We'll change it to: return f(li) (as we don't have an x variable any more).
We end up with the following code:
def f(n):
li = str(n)
if len(li) in (1, 0):
return True
elif li[-1] == li[0]:
li = li[1:-1]
if not li:
return True
return f(li)
else:
return False
It's almost what we need, but still a little refinement can be made. If you look at the lines if not li: return True, you'll see that they are not necessary. If we remove them, then f will be called with an empty string as the argument, len(li) will equal 0 and True will be returned anyway. So we'll go ahead and remove these lines:
def f(n):
li = str(n)
if len(li) in (1, 0):
return True
elif li[-1] == li[0]:
li = li[1:-1]
return f(li)
else:
return False
And that's it! Good luck on your way to becoming a successful programmer!
Split the whole show out into a list, then just:
def fun(yourList):
if yourList.pop(0) == yourList.pop(-1):
if len(yourList) < 2:
return True # We're a palindrome
else:
return fun(yourList)
else:
return False # We're not a palindrome
print "1234321"
print fun(list("1234321")) # True
print "6234321"
print fun(list("6234321")) # False
def palindrome(n):
return n == n[::-1]
It's hard to tell what you intend to do from your code, but I wrote a simpler (also recursive) example that might make it easier for you to understand:
def is_palindrome(num):
s = str(num)
if s[0] != s[-1]:
return False
elif not s[1:-1]:
return True
else:
return is_palindrome(int(s[1:-1]))
number = int(raw_input("Enter a number: "))
rev = 0
neg = number
original = number
if (number < 0):
number = number * -1
else:
number = number
while ( number > 0 ):
k = number % 10
number = number / 10
rev = k + ( rev * 10 )
if (number < 1):
break
if ( neg < 0 ):
rev = ( rev * -1)
else:
rev = (rev)
if ( rev == original):
print "The number you entered is a palindrome number"
else:
print "The number you entered is not a palindrome number"
This code even works for the negative numbers i am new to programming in case of any errors
dont mind.

Categories

Resources