If and else inside a one-line python loop - python

Sorry if being so simple; as I searched elsewhere but nobody had pointed out to this specific problem. I'd like to learn python in a way that makes my code compact! So, to this end, I'm trying to make use of one-line (i.e., short) loops instead of multi-line loops, specifically, for loops. The problem arises when I try to use one-line if and else inside the one-line loops. It just doesn't seem to be working. Consider the following, for example:
numbers = ... # an arbitrary array of integer numbers
over_30 = [number if number > 30 for number in numbers]
This is problematic since one-line if does need else following it. Even though, when I add else to the above script (after if): over_30 = [number if number > 30 else continue for number in numbers], it turns into just another pythonic error.
I know that the problem is actually with one-line if and else, because python needs to identify a value that should be assigned to the lefthand operator. But, is there a work-around for the specific use-case of this schema as above?

They are different syntaxes. The one you are looking for is:
over_30 = [number for number in numbers if number > 30]
This is a conditional list comprehension. The else clause is actually a non-conditional list comprehension, combined with a ternary expression:
over_30 = [number if number > 30 else 0 for number in numbers]
Here you are computing the ternary expression (number if number > 30 else 0) for each number in the numbers iterable.

continue won't work since this is ternary expression, in which you need to return something.
val1 if condition else val2
You can try this way:
over_30 = [number for number in numbers if number > 30]

Related

I want to exclude all numbers that aren't divisible by 7 and that are not multiples of 5 in a range of numbers from (0,300)

basically I have to get a list of numbers that are divisible by 7 but not multiples of 5. but for some reason when i put the conditions it tells me i have error.
for i in [x for x in xrange(0,100) if x%7 ==0 and if x%5 !=0 ]:
print i
I know you posted something along the lines of a list comprehension but it's a bit hard to read. So a few things...
I would try writing this as a multi-line for loop before condensing it down to a list comprehension.
I'm not sure why you have an 'x' in here, and 'xrange' doesn't make sense.
Edit: Just realized why I don't recognize xrange and it's because I never worked with Python 2.x
So thinking through this, you are basically looking for any number from 0-300 that is divisible by 7 but is not a multiple of 5.
Means we have a few things...
range(0,301): Since range is not inclusive of our last value we want n+1
Our number, let's say "i" is both... "i%7==0" and "i%5!=0"
So let's look line-by-line
for i in range(0,301):
Okay cool, now you don't need a nested for loop list comprehension like you did in your example. Now, you need to know "if" i is ____... So we need an if statement.
if i%7==0 and i%5!=0:
See the logic? And of course that if statement is inside of our for loop to loop over all the values in our range.
Finally, if our "i" meets our criteria, then we can print all the values.
print(i)
So, our final code looks like...
for i in range(0,301):
if (i % 7 == 0) and (i % 5 != 0):
print(i)
Of course, there are ways you can make this more elegant, but this is the general idea.
List Comprehension:
party = [i for i in range(0,301) if i%7==0 and i%5!=0]
print(party)
That stores them all in a list so you can access them whenever. Or you can print it without assigning it of course.
Edit: The title and then what you say in the body are kind of conflicting. After reading over my own answer, I'm not completely sure if that's what you're looking for, but that is how it came across to me. Hope it helps!
Your list comprehension is incorrect. It should be something similar to:
[x for x in xrange(100) if x%5 and not x%7]
Even better (more efficient) will be something similar to
[x for x in xrange (7, 100, 7) if x%5]
Even better will be ... Nah, we'll just stop here for now.

Should I use a for statement here or not?

I have this question and I want your expert answers about it, because I want to get better in programming.
"""
The parameter s_str is a string. The parameter n is an int > 0.
The function x() should return the last n characters of s_str if
s_str has a length >= n, or the empty string if s_str has a length < n
Example:
x('abcdef', 3) == 'def'
"""
So, I could build the exact code with or without the for statement and it would give me (print) the same values, but I don't know what is the more common way to do it. If I'd go for a for statement, I'd do this:
for i in s_str:
if len(s_str) >= n:
return a_str[-n:]
elif len(s_str) < n:
return ''
Is the idea of using a for statement wrong if you know in advance that you are not going to use i, in this case? I could easily remove the for statement and still get the right answer, so is that enough reason not to use it?
There are cases in which a for loop is justified even if you do not intend to use the loop index (e.g when you want to preform a certain task n times). Having said that, this problem can be solved in a more elegant way, as you have shown.
Also please note that your code iterates over the string len(str) times, except it returns in the first iteration, so the for loop in this case is redundant.
"so is that enough reason not to use it?"
Yes. Simple is better than complex.
You dont actually need a for loop
if len(a_str) >= n:
return a_str[-n:]
it is better and simple too.

Why doesn't str(a) == reversed(str(a)) work as a palindrome test in Python?

I have been trying to find the answer to problem #4 in Project Euler in Python but I really can´t seem to find the problem in my code. Here is the question:
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
And here is my code:
nums = list(range(10000, 998001))
pals = []
def palindrome(a):
if str(a) == reversed(str(a)):
pals.append(a)
for every in nums:
palindrome(every)
For a start, try printing out the string and its supposed reversal - you'll find they're not as you expect. A more sensible way of getting the string reversal of s is with s[::-1].
Then you need to realise that you're checking every number in that range of yours, 10000..998000 (noticing I've left out 998001 there since Python ranges are exclusive at the end). Not all of those numbers will be a product of two 3-digit numbers. Of course, it may be that was going to be your next step once you'd got all the palindromes, in which case feel free to ignore this paragraph (other than fixing the range, of course).
As an aside, I probably wouldn't wrap that range in a list. If you're using Python 2, it's already a list and, for Python 3, it's probably better to leave it as a lazy iterator so as not to waste memory.
And, as a final aside, I probably wouldn't do it this way since I prefer readable code but those who demand "Pythonic" solutions may like to look at something like:
print(max((i*j
for i in range(100,1000)
for j in range(100,1000)
if str(i*j) == str(i*j)[::-1]
)))
though of course true Python aficionados will want that on a single line :-) I've just split it for readability.
Python's reversed returns an iterator object. So, no one string could be equal to the iterator.
Because reversed returns an iterator object, you should replace str(a) == reversed(str(a)) with str(a) == str(a)[::-1]. The [::-1] qualifier is a string splicing that will reverse the collection.
Let me start with what everyone else pointed out: reversed(s) is not the same as s[::-1].
Everyone else wanted you to use s[::-1] instead of reversed(). Sure, that suggestion is scrutable and idiomatic. But why limit yourself! This is Python, after all.
from itertools import combinations, ifilter, imap, starmap
from operator import eq, mul
def is_palindrome(integer):
"""Returns a True if an input integer's digits are palindromic, False otherwise."""
string = str(integer)
return all(imap(eq, iter(string), reversed(string)))
def largest_palindrome_product(n_digits=3):
"""Solves Project Euler #4"""
all_n_digit_numbers = xrange(10**(n_digits)-1, 10**(n_digits-1), -1)
palindromes = ifilter(is_palindrome,
(starmap(mul, combinations(all_n_digit_numbers, 2)))
)
return max(palindromes)
largest_palindrome_product()
This solution has the valuable feature of retaining the use of reversed()! For extra inscrutability I tried to use as many functions from itertools as I could.

Condensing a long if statement to a short one automatically

Lets say we have an if statement in python of form:
if a == 1 or a == 2 or a == 3 ... a == 100000 for a large number of comparisons, all connected with an or.
What would a good algorithm be to compress that into a smaller if statement?
eg for the above
if a >= 1 and a <= 100000
Sometimes there will be a pattern in the numbers and sometimes they will be completely random so the algorithm must deal well with both cases.
Can anyone suggest a decent algorithm that will efficiently condense an if statement of this form?
Edit: The goal is to have the resulting if statement be as short as possible. The efficiency of evaluating the if statement is secondary to length.
If there is no pattern in your numbers, you can use a tuple and use membership test
if a in (1,2,3,... 100000)
You sort your "compare list", then traverse it to extract runs of consecutive integers as separate intervals. For intervals of length 1 (i.e. single numbers) you perform an == test and for larger intervals you can perform the chained =</>= comparisons.
Maintain a sorted array of numbers to compare and perform binary search on it whenever you want to check for a . If a exists in array then the statement is true else false. It will be O(logn) for each if query
a quick and easy way is:
if a in set(pattern in the numbers and sometimes they will be completely random):
whaterver
You can also given
x = random.sample(xrange(100), 30)
you could build up a list of ranges:
for i in x:
if ranges and i-1 in ranges[-1]:
ranges[-1].append(i)
else:
if ranges: ranges[-1] = ranges[-1][0], ranges[-1][-1]
ranges.append([i])
tidy the ranges up and then then loop through it
for range in ranges:
if range[0] <= a <= range[0]:
whatever
What works in the least space for the if statement is going to be a test for membership in a set of values. (Don't use a tuple or a list for this - this is what sets are for):
if a in set_of_numbers:
blah ...
If you know from your wider problem that when expressed as ranges of integers, that you will usually have sufficiently less ranges to compare, then you can use code from the Rosetta Code Range extraction task to create the ranges, with a different print routine printi to format the output as a for statement:
def printi(ranges):
print( 'if %s:' %
' or '.join( (('%i<=a<=%i' % r) if len(r) == 2 else 'a==%i' % r)
for r in ranges ) )
For the Rosetta code example numbers it will produce the following:
if 0<=a<=2 or a==4 or 6<=a<=8 or a==11 or a==12 or 14<=a<=25 or 27<=a<=33 or 35<=a<=39:
Tradeoffs
For a few but large ranges then a large set would have to be created in your source against a more compact creation of the if statement comparing explicit ranges.
For many scattered ranges the if statement becomes very long - the set solution would be easier to maintain - still long but probably easier to scan.
I don't know your full problem, but probably the best way to handle this is if the integers come in a file that is easy to parse then make your program parse this file and create an appropriate set or list of ranges on the fly for your if statement.

Python function to sum digits

I want function to take the last digit of each number in the list and sum them up all together. So forexample, the function below would return "10".
def getSumOfLastDigits(numList):
x = sum(int(num[-1:]) for num in numList)
print x
getSumOfLastDigits([1, 23, 456])
>>>10
Here is what i receive instead of the expected out "10"
def getSumOfLastDigits(numList):
x = sum(int(num[-1:]) for num in numList)
print x
getSumOfLastDigits([1, 23, 456])
Too much work.
def getSumOfLastDigits(numList):
return sum(x % 10 for x in numList)
x = sum(num%10 for num in numList)
You can't index into a number; a number isn't a sequence of digits (internally, it isn't represented in base 10). You can obtain the last digit of a number using mathematical manipulation instead: take the remainder when dividing by 10. We do this with the % operator.
Also:
Don't print the value in your function, return it. Let the calling code decide what to do with the value. Calculation and output are separate tasks, and should be kept separate.
Avoid indicating data types in variable names - yes, even in Python. It's not a good idea to build in assumptions that aren't actually necessary. You could use any kind of sequence here, for example. The simplest way to indicate that you have more than one number is to use the plural, numbers. That also means you use a full word, and people don't have to think about what 'num' is short for.
There is no need to assign the result of an expression to a temporary variable, if you are just going to use it once, and right away. The name x doesn't tell us anything, so cut it out.
get is considered an ugly prefix for function names by most Pythonistas. It should already be obvious that the function calculates and returns a value. Use noun-type names for those functions, and verb-type names for functions that are primarily intended to manipulate some existing data.
Thus:
def sum_of_last_digits(numbers):
return sum(number % 10 for number in numbers)
def getSumOfLastDigits(numList):
total=0
for item in numList:
newItem=str(item)
length=newItem[len(newItem)-1]
total+=int(length)
return total

Categories