Convert nested loops and conditions to a list comprehension - python

Is there a way for list comprehending this condition set.
clamp_sel = list()
for i in range(0, len(clamp_normalized)):
for j in range(0, len(clamp_normalized[i])):
if clamp_normalized[i][j][0] < int(max_min_band[index_selec]):
clamp_sel.append(int(clamp_normalized[i][j][0]))
If it is single dimension list, I could set the condition in this way.
norm_sel = [i for i in normalize_data if i[0] > max_min_band[index_selec]]
Thanks

If clamp_normalized is a list of lists, you can iterate without using range, unless you need the index.
clamp_sel = [j[0]
for i in clamp_normalized
for j in i
if j[0] < int(max_min_band[index_selec])]

This should translate directly into a list-comprehension:
clamp_sel = [int(clamp_normalized[i][j][0])
for i in range(0, len(clamp_normalized))
for j in range(0, len(clamp_normalized[i]))
if clamp_normalized[i][j][0] < int(max_min_band[index_selec])]
The general rule is (see the manual) that you should write the list-comprehension in exactly the same order as you would do with a series of nested for loops and if-statements. The only thing you change is to replace the final xx.append(yy) with just yy at the front of the list comprehension. Also note that this is essentially one long expression that you could write on an extremely long line. Because of the enclosing [], you can divide this expression over multiple lines, with arbitrary indentation.
If the list-comprehension is more pythonic than the original is a question of taste. In this case, the nesting is straightforward, so I would personally go for the list-comprehension. If it gets more complex, stick to the for-loops.
As thefourtheye shows, this example can be further simplified by replacing the use of range() with a direct iteration over your lists.

Related

How to elegantly "min" and then perform an operation with the reference in python

Suppose you have two lists nums1 and nums2, and you want to check which of the two lists first elements is smaller, remove it and then move that into an accumulator list which you return.
You might accomplish it using the following code:
def stackoverflowmethod(nums1, nums2):
accumulator = []
# determine if nums1[0] or nums2[0] is smaller and then remove it
if nums1[0] <= nums2[0]:
accumulator.append(nums1.pop(0))
else:
accumulator.append(nums2.pop(0))
return accumulator
However this might look very unsightly in that you would rather have written something like
def idealMethod(nums1, nums2):
accumulator.append(RemoveFromTheFrontOfOYourList(min(nums1[0], nums2[0])))
return accumulator
The only thing that makes this hard is the mysterious method RemoveFromTheFrontOfOYourList needs to be able to figure out which list an item came from after the min operation was conducted on it,
I.E. if we had a slightly less forgetful min function then something elegant could be done here.
If stylistically what I'm asking for makes sense, how does one accomplish it in python 3?
Use min on the lists, with operator.itemgetter(0) as the key function.
import operator
smaller_0 = min(nums1, nums2, key=operator.itemgetter(0))
accumulator.append(smaller_0.pop(0))
operator.itemgetter(0) is the same as lambda x: x[0] for this purpose.
Or use a ternary that gets the list with a smaller item 0, then pop from it
smaller_0 = nums1 if nums1[0] <= nums2[0] else nums2
accumulator.append(smaller_0.pop(0))
This is like Michael's answer, but a bit DRYer
Use a ternary statement:
acc.append(nums1.pop(0)) if nums1[0] < nums2[0] else acc.append(nums2.pop(0))

Python list comprehension with function returning a list

I am trying to call a function for a range of values. That function returns a list. The goal is to combine all the returned lists into a list.
Here is a test function that returns a list:
def f(i):
return [chr(ord('a') + i), chr(ord('b') + i), chr(ord('c') + i)]
Here is a list comprehension that does what I need that I came up with after some experimentation and a lot of StackOverflow reading:
y = [a for x in (f(i) for i in range(5)) for a in x]
However, I do not understand why and how it works when a simple loop that solves this problem looks like this:
y = []
for x in (f(i) for i in range(5)):
for a in x:
y.append(a)
Can someone explain?
Thanks!
This may be a better illustration, following Bendik Knapstad's answer:
[
a # element added to the list
for x in (f(i) for i in range(5)) # outer loop
for a in x # inner loop that assigns to element to be added to the list
]
Answering to this:
However, I do not understand why and how it works (list comprehensions) when a simple loop that solves this problem looks like this (for loops)
Yes, they both can work but there are some differences.
First, with list comprehensions, you are able to generate a list (because that's the output) after assigning it to a variable. Whereas in a for loop you must have the list created (regardless if it's empty or not) if you wish to use append later on perform any updating/deleting/re-indexing operation.
Second, simplicity. While for loops might be used in complex tasks where you need to apply a wide variety of functions, and maybe use RNGs, list comprehensions are always preferrable when it comes to dealing with lists and performing rather 'basic' operations (of course you can start nesting them and turn them into something more complex).
Third and finally, speed. List comprehensions tend to perform baster when compared to for loops for simple tasks.
More in-depth information regarding listcomp and for loops can be read in python's official tutorial. https://docs.python.org/3/tutorial/datastructures.html
Nested list comprehensions are hard to read.
But if you look at the two expressions you'll se that they contain the same logic.
In the list comprehension the first a is the part you want to keep in the list. It's equal to the y.append(a) in the for loop.
The for x in (f(i) for i in range(5)) is the same as in your for loop
The same goes for the next line for a in x
So for x in (f(i) for i in range(5)) creates a list x
So if we had the list x already we could write
y= [a for a in x]

Using comprehensions instead of a for loop

The following is a simplified example of my code.
>>> def action(num):
print "Number is", num
>>> items = [1, 3, 6]
>>> for i in [j for j in items if j > 4]:
action(i)
Number is 6
My question is the following: is it bad practice (for reasons such as code clarity) to simply replace the for loop with a comprehension which will still call the action function? That is:
>>> (action(j) for j in items if j > 2)
Number is 6
This shouldn't use a generator or comprehension at all.
def action(num):
print "Number is", num
items = [1, 3, 6]
for j in items:
if j > 4:
action(i)
Generators evaluate lazily. The expression (action(j) for j in items if j > 2) will merely return a generator expression to the caller. Nothing will happen in it unless you explicitly exhaust it. List comprehensions evaluate eagerly, but, in this particular case, you are left with a list with no purpose. Just use a regular loop.
This is bad practice. Firstly, your code fragment does not produce the desired output. You would instead get something like: <generator object <genexpr> at 0x03D826F0>.
Secondly, a list comprehension is for creating sequences, and generators a for creating streams of objects. Typically, they do not have side effects. Your action function is a prime example of a side effect -- it prints its input and returns nothing. Rather, a generator should for each item it generates, take an input and compute some output. eg.
doubled_odds = [x*2 for x in range(10) if x % 2 != 0]
By using a generator you are obfuscating the purpose of your code, which is to mutate global state (printing something), and not to create a stream of objects.
Whereas, just using a for loop makes the code slightly longer (basically just more whitespace), but immediately you can see that the purpose is to apply function to a selection of items (as opposed to creating a new stream/list of items).
for i in items:
if i < 4:
action(i)
Remember that generators are still looping constructs and that the underlying bytecode is more or less the same (if anything, generators are marginally less efficient), and you lose clarity. Generators and list comprehensions are great, but this is not the right situation for them.
While I personally favour Tigerhawk's solution, there might be a middle ground between his and willywonkadailyblah's solution (now deleted).
One of willywonkadailyblah's points was:
Why create a new list instead of just using the old one? You already have the condition to filter out the correct elements, so why put them away in memory and come back for them?
One way to avoid this problem is to use lazy evaluation of the filtering i.e. have the filtering done only when iterating using the for loop by making the filtering part of a generator expression rather than a list comprehension:
for i in (j for j in items if j > 4):
action(i)
Output
Number is 6
In all honesty, I think Tigerhawk's solution is the best for this, though. This is just one possible alternative.
The reason that I proposed this is that it reminds me a lot of LINQ queries in C#, where you define a lazy way to extract, filter and project elements from a sequence in one statement (the LINQ expression) and can then use a separate for each loop with that query to perform some action on each element.

Index of first differing element without loop

I have two lists say
A = [1,3]
B = [1,3,5,6]
I want to know the index of the first differing element between these lists (2 in this case).
Is there a simple way to do this, or do I need to write a loop?
You can use following generator expression within next() function using enumerate() and zip() function:
>>> next(ind for ind,(i,j) in enumerate(zip(A,B)) if i != j)
2
Perhaps the loop you mentioned is the most obvious way, not necessarily the most pretty. Still every O(n) complexity solution is fine by me.
lesser_length = min(len(A), len(B))
answer = lesser_length # If one of the lists is shorter and a sublist,
# this will be the answer, because the if condition
# will never be satisfied.
for i in xrange(lesser_length):
if A[i] != B[i]:
answer = i
break
range instead of xrange in Python3. A generator would be the best way given that you don't know when the difference between lists will occur.(In Python2, xrange is generator. In Python3, xrange became the regular range() function.)
A list comprehension is also viable. I find this to be more readable.

python list generation/saving bug

I am trying to make program that prints all the possible combinations for a to zzz. I tried to add a save state feature, and it works fine but there is this bug.
Let's say I interrupted the program when it printed something like e. When I execute the program again, it works fine until z but after z instead of printing aa it prints ba and continues from ba. This happens right after it prints zz too. it prints baa instead of aaa. How can I fix this?
Here is what I did so far:
import pickle,os,time
alphabet="abcdefghijklmnopqrstuvwxyz"
try:
if os.path.isfile("save.pickle")==True:
with open("save.pickle","rb") as f:
tryn=pickle.load(f)
for i in range(3):
a=[x for x in alphabet]
for j in range(i):
a=[x+i for x in alphabet for i in a]
b=a[tryn:]
for k in b:
print(k)
time.sleep(0.01)
tryn+=1
else:
tryn=0
for i in range(3):
a=[x for x in alphabet]
for j in range(i):
a=[x+i for x in alphabet for i in a]
for k in a:
print(k)
tryn+=1
time.sleep(0.01)
except KeyboardInterrupt:
with open("save.pickle","wb") as f:
pickle.dump(tryn,f)
If you're using python2, or python3 as the tag suggests, this exists in the standard library already. See itertools, product py2, and product py3, for a simple way to solve this problem.
for i in range(3):
a=[x for x in alphabet]
for j in range(i):
a=[x+i for x in alphabet for i in a]
b=a[tryn:]
Here's your bug. You skip the first tryn strings of every length, rather than just the first tryn strings. This would be easier to recognize in the output if it weren't for the following:
for k in b:
print(k)
time.sleep(0.01)
tryn+=1
You modify tryn, the number of things you're skipping. When you print out length-2 strings, you skip a number of them equal to the number of length-1 strings. When you print out length-3 strings, you skip a number of them equal to the number of length-2 strings. If tryn were bigger than the number of length-1 strings, you would skip even more.
your problem is almost certainly here:
a=[x for x in alphabet]
for j in range(i):
a=[x+i for x in alphabet for i in a]
Perhaps you shouldn't assign the in-loop value to a, but instead use a different name? Otherwise, you are changing what you use every time through the loop....
Edit: More detail. So, technically user2357112's answer is more correct, but I'm amending mine. The initial answer was just from a quick reading, so the other answer is close to the original intent. But, the original version is inefficient (for more reasons than not using product :), since you are generating the inner loops more than once. So let's walk through why this is a bad idea, as an educational exercise:
Initial algorithm:
for i in range(n):
assign a to alphabet
for j in range(i):
i times, we rewrite a to be all combinations of the current set against the alphabet.
Note that for this algorithm, to generate the length(n) product, we have to generate all previous products length(n-1), length(n-2), ..., length(1). But you aren't saving those.
You'd be better off doing something like this:
sum_list = alphabet[:]
#get a copy
product_list = alphabet[:]
#Are we starting at 0, or 1? In any case, skip the first, since we preloaded it
for i in range(1, n):
# Your existing list comprehension was equivalent here, and could still be used
# it MIGHT be faster to do '%s%s'%(x,y) instead of x+y... but maybe not
# with these short strings
# This comprehension takes the result of the last iteration, and makes the next iteration
product_list = [x+y for x,y in product(product_list, alphabet)]
# So product list is JUST the list for range (n) - i.e. if we are on loop 2, this
# is aaa...zzz. But you want all lengths together. So, as you go, add these
# sublists to a main list.
sum_list.extend(product_list)
Overall, you are doing a lot less work.
Couple other things:
You're using i as a loop variable, then re-using it in the loop comprehension. This is conflicting, and probably not working the way you'd expect.
If this is to learn how to write save/restore type apps... it's not a good one. Note that the restore function is re-calculating every value to be able to get back where it left off - if you could rewrite this algorithm to write more information out to the file (such as the current value of product_list) and make it more generator-like, then it will actually work more like a real-world example.
Here is how I would suggest solving this problem in Python. I didn't implement the save state feature; this sequence is not a really long one and your computer should be able to produce this sequence pretty fast, so I don't think it is worth the effort to try to make it cleanly interruptable.
import itertools as it
def seq(alphabet, length):
for c in range(1, length+1):
for p in it.product(alphabet, repeat=c):
yield ''.join(p)
alphabet="abcdefghijklmnopqrstuvwxyz"
for x in seq(alphabet, 3):
print(x)
If you really wanted to, you could make a one-liner using itertools. I think this is too hard to read and understand; I prefer the above version. But this does work and will be somewhat faster, due to the use of itertools.chain and itertools.imap() rather than a Python for loops.
import itertools as it
def seq(alphabet, length):
return it.imap(''.join, it.chain.from_iterable(it.product(alphabet, repeat=c) for c in range(1, length+1)))
alphabet="abcdefghijklmnopqrstuvwxyz"
for x in seq(alphabet, 3):
print(x)
In Python 3.x you could just use map() rather than itertools.imap().

Categories

Resources