Complement of list comprehension in python [duplicate] - python

This question already has answers here:
How can I partition (split up, divide) a list based on a condition?
(41 answers)
Closed 8 years ago.
I'm wondering if there is not a way to compute the complement of a list comprehension in Python.
Something like:
evens = [i in range(10) if i % 2 == 0]
odds = [i in range(10) if i % 2 != 0]
is there a way to get both evens and odds in one call? For a very large list, or a more expensive if statement, I think this would save a lot of time.

I believe this question has been asked before, but I am not finding the link currently.
If you are trying to get more than one predicate and you only want to iterate once over the original generator, then you will have to use a simple for loop.
evens = []
odds = []
for i in xrange(10):
if i % 2 == 0: evens.append(i)
else: odds.append(i)
As #dawg pointed out, the logic inside the loop can be made more concise using clever indexing.
for i in xrange(10):
(evens,odds)[i%2].append(i)

itertools.groupby is what I'd use.
In [1]: import itertools as it
In [2]: key = lambda i: i%2 == 0
In [3]: l = list(range(10))
In [4]: l.sort(key=key)
In [5]: [list(i[1]) for i in it.groupby(l, key=key)]
Out[5]: [[1, 3, 5, 7, 9], [0, 2, 4, 6, 8]]

I would do one of the following:
evens = [i in range(10) if i % 2 == 0]
odds = [i in range(10) if i not in evens]
Or with better performances:
evens = [i in range(10) if i % 2 == 0]
evens_set = set(evens)
odds = [i in range(10) if i not in evens_set]
Working with set is better in performance, as the not in query costs O(1) instead of O(n) in lists

In short, you can get both True and False cases in one call, but you'd still need to split them into two lists. You could do
range_10 = range(10)
odds = range_10[1::2]
evens = range_10[::2]
but the benefit of that would be negligible. (In fact, you'd be creating three lists instead of two). You'd only want to do that if the cost of range(10) was so high that it would offset creating two lists.
Using slicing like I did should be slightly faster than using a test and explicitly appending.

Related

Python List comprehension missing info

I am trying to implement the following code into a list comprehension:
def incrementValues(m):
for i in range(len(m)):
for ii in range(len(m[i])):
if m[i][ii] % 2 == 0:
m[i][ii] //=2
return m
m = [[5, 4], [2, 3], [6, 7]]
print(incrementValues(m))
So far I have :
[[m for m in range(len(m))] for n in range(len(m))]
but I cannot work out where this info goes:
if m[i][ii] % 2 == 0:
m[i][ii] //=2
If someone could steer me in the right direction, that would be great! (Also, feel free to advise if my original code could be written in a better way)
Thanks :)
Your code doesn't create a list, so it probably shouldn't be a list comprehension, which is for creating lists.
Now, in general it is bad for a function to mutate it's inputs. So indeed, you could create a new list instead of modifying your list, and you could do that with a list comprehension:
[[x//2 if x % 2 == 0 else x for x in sub] for sub in m]
but this is doing something different than your code. It is important to understand that.
Note, if you must modify the list, then you should do it this way:
def increment_values(m): # stick to python naming conventions
for sub in m:
for i, val in enumerate(sub):
if val % 2 == 0:
sub[i] //= 2
return m
Of course, you can also use slice-assignment with a list-comprehension, although, this would make your code less space efficient (which is one nice aspect of modifying the list in-place):
def increment_values(m): # stick to python naming conventions
m[:] = [[x//2 if x % 2 == 0 else x for x in sub] for sub in m]
return m
But I would prefer the for-loop version if you are going to do this at all (and I would prefer a version that doesn't mutate the list to begin with...)
The corresponding list comprehension is
m = [[5, 4], [2, 3], [6, 7]]
res = [[item//2 if item%2==0 else item for item in items] for items in m]

python for loop with lists

I get:
if(lst[i]%2!=0):
IndexError: list index out of range
from this code:
lst=[1,2,3,4,5,6]
for i in range(len(lst)):
if(lst[i]%2!=0):
lst.remove(lst[i])
print(lst)
I am trying to print only the even numbers in a list and I do not see a problem with my code
why am I getting this error ?
You should not remove from a list while iterating it. Use, for instance, a list comprehension and slice assignment to achieve the same modification:
lst = [1,2,3,4,5,6]
lst[:] = [x for x in lst if x % 2 == 0]
# or simply (if you don't need to mutate the original list)
# lst = [x for x in lst if x % 2 == 0]
print(lst)
# [2, 4, 6]
This has also better time complexity (linear) whereas the repeated remove approach is quadratic.
Instead of removing the item from the original list, you can also split the original list into 2 new lists, evens and odds
lst=[1,2,3,4,5,6]
evens = []
odds = []
for i in lst):
if(i % 2 != 0):
odds.append(i)
else:
evens.append(i)
print(evens)

Generate list with every nth value from input

I had the following code:
return [p.to_dict() for p in points]
I changed it to only print every nth row:
n = 100
count = 0
output = []
for p in points:
if (count % n == 0):
output.append(p.to_dict())
count += 1
return output
Is there a more pythonic way to write this, to acheive the same result?
use enumerate and modulo on the index in a modified list comprehension to filter the ones dividable by n:
return [p.to_dict() for i,p in enumerate(points) if i % n == 0]
List comprehension filtering is good, but in that case, eduffy answer which suggests to use slicing with a step is better since the indices are directly computed. Use the filter part only when you cannot predict the indices.
Improving this answer even more: It's even better to use itertools.islice so not temporary list is generated:
import itertools
return [p.to_dict() for p in itertools.islice(points,None,None,n)]
itertools.islice(points,None,None,n) is equivalent to points[::n] but performing lazy evaluation.
The list slicing syntax takes an optional third argument to define the "step". This take every 3rd in a list:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(10)[::3]
[0, 3, 6, 9]
you can use enumerate with list comprehension.
[p.to_dict() for i, p in enumerate(points) if i %100 == 0]

Splitting Up Lists of Lists by Length in Python

Given the following problem, what is the most efficient (or reasonably efficient) way to do this in Python:
Problem. Given a list of lists,
L = [list_0, list_1, list_2, list_3, ..., list_n]
where len(list_i) <= 3, let's say, for each list inside of L. How can we split up L into L_1, L_2, L_3, where L_1 has only length 1 lists, L_2 has only length 2 lists, and L_3 has only length 3 lists?
Potential Solutions. Here's the best I could do; I've also included a sample set here as well. It runs in around 8.6 seconds on my PC.
import time
# These 4 lines make a large sample list-of-list to test on.
asc_sample0 = [[i] for i in range(500)]
asc_sample1 = [[i,j] for i in range(500) for j in range(20)]
asc_sample2 = [[i,j,k] for i in range(20) for j in range(10) for k in range(20)]
asc_sample = asc_sample0 + asc_sample1 + asc_sample2
start = time.clock()
cells0 = [i for i in asc if len(i) == 1]
cells1 = [i for i in asc if len(i) == 2]
cells2 = [i for i in asc if len(i) == 3]
print time.clock() - start
I also attempted to "pop" elements off and append to lists cells0, etc., but this took significantly longer. I also attempted to append and then remove that element so I could get through in one loop which worked okay when there were, say, 10^10 lists of size 1, but only a few of size 2 and 3, but, in general, it was not efficient.
I'd mostly appreciate some neat ideas. I know that one of the answers will most likely be "Write this in C", but for now I'd just like to look at Python solutions for this.
An old fashioned solution might work better here:
cells0, cells1, cells2 = [], [], []
for lst in asc_sample:
n = len(lst)
if n == 1:
cells0.append(lst)
elif n == 2:
cells1.append(lst)
else:
cells2.append(lst)
This is definitely one of the best because it runs in parallel. Another thing that you should look at though is the itertools.groupby and the built-in filter method.
result = dict()
for lst in L:
result.setdefault(len(lst), []).append(lst)
print result
Output
{
1: [[0], [1], [2], [3]],
2: [[0, 0], [0, 1], [0, 2]],
3: [[0, 0, 0], [0, 0, 1], [0, 0, 2]]
}
Indexing a list/tuple should be faster than doing key lookups. This is about 30% faster than the version given in the question
cells = [],[],[],[] # first list here isn't used, but it's handy for the second version
for i in asc:
cells[len(i)].append(i)
Slightly faster again by extracting the append methods (On larger lists this is almost twice as fast as the OP)
cells = [],[],[],[]
appends = [x.append for x in cells]
for i in asc:
appends[len(i)](i)

python return lists of continuous integers from list

I have a list of integers, and I want to generate a list containing a list of all the continuous integers.
#I have:
full_list = [0,1,2,3,10,11,12,59]
#I want:
continuous_integers = [[0,1,2,3], [10,11,12], [59]]
I have the following which works, but seems like a poor way to do it:
sub_list = []
continuous_list = []
for x in full_list:
if sub_list == []:
sub_list.append(x)
elif x-1 in sub_list:
sub_list.append(x)
else:
continuous_list.append(sub_list)
sub_list = [x]
continuous_list.append(sub_list)
I've seen other questions suggesting that itertools.groupby is an efficient way to do this, but I'm not familiar with that function and I seem to be having trouble with writing a lambda function to describe the continuous nature.
Question: Is there a better way to be doing this (possibly with itertools.groupby?)
Considerations: full_list will have between 1 and 59 integers, will always be sorted, and integers will be between 0 and 59.
You can use the following recipe:
from operator import itemgetter
from itertools import groupby
full_list = [0,1,2,3,10,11,12,59]
cont = [map(itemgetter(1), g) for k, g in groupby(enumerate(full_list), lambda (i,x):i-x)]
# [[0, 1, 2, 3], [10, 11, 12], [59]]

Categories

Resources