List Comprehension to Sublists - python

As a beginner computer science student I was assigned to write a function to sort a list of even and odd numbers into two sublists. Wait... don't down vote me. I have been learning on my own a bit and experimenting with list comprehension and timeit and was wondering if I could recreate this with list comprehension to do something a bit more challenging instead.
I've figured out how to use list comprehension to flatten sublists, but not the other way around. Is it possible?
def odd_even_filter(numbers):
even = []
odd = []
for i in numbers:
if i % 2 == 0:
even.append(i)
else:
odd.append(i)
return [even, odd]
odd_even_filter([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>[[2,4,6,7],[1,3,5,7,9]]
Just trying to see if i can take a flat list and generate nested lists using list comprehension. It may not be worth it and not the python way, but just experimenting.

If you're aiming for the shortest, yet pythonic answer, How about?
odd = [i for i in numbers if i % 2] # this is O(n)
even = list(set(numbers) - set(odd)) # this is O(n log n)
An inefficient, but still clear alternative is:
even = numbers - odd # this is O(n^2)
An O(n) alternative (the best?) would be:
odd = [i for i in numbers if i % 2] # this is O(n)
even = [i for i in numbers if not i % 2] # this is O(n)

There is always a tradeoff between readability and compactness in code. In this case, I believe the answer by devnull is excellent. He uses list comprehensions and Python if expression resulting in something very readable in a single line. If your test criterion is more stringent, it is generally more useful to separate out the conditions into their own functions. For your example, these would be:
def even(x): return x%2 == 0
def odd(x) : return x%2 != 0
and then use them for filtering out the results like so:
def oddEvenFilter(x): return [filter(even, x), filter(odd, x)]
These are three lines of code, but in combination is very readable.

This works, not easier to read at all, but it is possible with range being the number of subgroups, 2 for even and odd.
return [[ n for n in numbers if n % 2 == 0] if i == 0 else [ n for n in numbers if n % 2 != 0] for i in range(2)]
Ran this through timeit and it takes twice as long which is to be expected. devnull's answer takes a little bit longer too.
def odd_even_filter(numbers):
even = []
odd = []
for i in numbers:
if i % 2 == 0:
even.append(i)
else:
odd.append(i)
return [even, odd]
def odd_even_filter_2(numbers):
return [[ n for n in numbers if n % 2 == 0] if i == 0 else [ n for n in numbers if n % 2 != 0] for i in range(2)]
def odd_even_filter_3(numbers):
even = []
odd = []
[ odd.append(n) if n % 2 != 0 else even.append(n) for n in numbers]
return [even,odd]
print(timeit.timeit('odd_even_filter([1, 2, 3, 4, 5, 6, 7, 8, 9])', setup="from __main__ import odd_even_filter" ))
print(timeit.timeit('odd_even_filter_2([1, 2, 3, 4, 5, 6, 7, 8, 9])', setup="from __main__ import odd_even_filter_2" ))
print(timeit.timeit('odd_even_filter_3([1, 2, 3, 4, 5, 6, 7, 8, 9])', setup="from __main__ import odd_even_filter_3" ))
>>2.2804439414858675
>>4.190625469924679
>>3.0541580641135733

if your number are in sequence you can even use slice:
>>> [r[1::2],r[2::2]]
[[1, 3, 5, 7, 9], [2, 4, 6, 8]]
>>>

Based on ssm's post:
>>> l = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [list(filter(lambda x: x%2==0, l)), list(filter(lambda x: x%2==1, l))]
[[2, 4, 6, 8], [1, 3, 5, 7, 9]]
This solution is slow though.

Related

Sum of certain items in a list

I'm working on a probability-related problem. I need to sum only specific items on a certain list.
I've tried using "for" functions and it hasn't worked. I'm looking for a way to select items based on their positions on the list, and summing them.
You can use operator.itemgetter to select only certian index’s in a list or keys in a dict.
from operator import itemgetter
data = [1, 2, 3, 4, 5, 6, 7, 8]
get_indexes = itemgetter(2, 5, 7)
#this will return indexes 2, 5, 7 from a sequence
sum(get_indexes(data)) #3+6+8
#returns 17
That example is for lists but you can use itemgetter for dict keys too just use itemgetter('key2', 'key5', 'key7')({some_dict})
To get only even or odd indexes use slicing not enumerate and a loop it’s much more efficient and easier to read:
even = sum(data[::2])
odd = sum(data[1::2])
You can also use filter but I wouldn’t suggest this for getting by index:
sum(filter(lambda n: data.index(n) % 2 == 0, data))
You really should have put more into your question, but:
stuff = [1, 2, 3, 4, 5, 6, 7, 8]
# sum the numbers that have even indices:
funny_total = sum([x for i, x in enumerate(stuff) if i % 2 == 0 ])
funny_total
# 16
That should get you started. An approach with a for loop would have worked, as well. You just likely have a bug in your code.
stuff = [1, 2, 3, 4, 5, 6, 7, 8]
indices_to_include = [1, 3, 4, 5, 6]
funny_total = 0
for i, x in enumerate(stuff):
if i in indices_to_include:
funny_total += x
You could also:
def keep_every_third(i):
return i % 3 == 0
# variable definitions as above...
for i, x in enumerate(stuff):
if keep_every_third(i):
# do stuff

Summing elements at the beginning with the elements at the end of a list

Given a list of numbers, create a new list of numbers such that the first and last numbers are added and stored as the first number, the second and second-to-last numbers are stored as the second number, and so on
num_list = [1,2,3,4,5,6]
num_list2 = [num_list[-1] + num_list[0], num_list[-2] + num_list[1],
num_list[-3] + num_list[2]]
print(num_list2)
output is [7,7,7]
I got the correct output this way but I am sure this is not an efficient way to do it. Is there a better way? I also am supposed to check for even and odd length of the list and if its an odd number of integers, add the central integer in the original list to the end of the new list but don't know how I would go about doing this
I think this is more efficient, i just simply did a for loop:
num_list2 = []
num_list = [1,2,3,4,5,6]
for i in range(round(len(num_list)/2)):
num_list2.append(num_list[i]+num_list[-(i+1)])
print(num_list2)
Output:
[7, 7, 7]
Let us using reversed
[x + y for x, y in zip(num_list, list(reversed(num_list)))][:len(num_list)//2]
Out[406]: [7, 7, 7]
Here's an inefficient[1], but clear way of doing this:
from itertools import zip_longest # or izip_longest in Python2
lst = [1,2,3,4,5,6]
chop_index = len(lst) // 2 # (or +1, depending on how you want to handle odd sized lists)
lh, rh = lst[:chop_index], lst[:chop_index-1:-1]
print(lh, rh) # To see what's going on in the "chopping"
sums = [x + y for (x,y) in zip_longest(lh, rh, fillvalue=0)]
print(sums)
You could improve it by using islice and reversed iterators, or use index math exclusively.
Output:
lst = [1,2,3,4,5,6] => [7, 7, 7]
lst = [1,2,3,4,5,6,7] => [8, 8, 8, 4]
[1] This makes two copies of the list parts. For long lists this is silly, and you shouldn't use this method. It was mostly written to highlight zip_longest's fillvalue optional argument.
Using itertools.islice on a generator:
from itertools import islice
num_list = [1,2,3,4,5,6]
generator = (x + y for x, y in zip(num_list, num_list[::-1]))
print(list(islice(generator, len(num_list)//2)))
# [7, 7, 7]
You can use the following method, which is compatible with asymmetrical list.
def sum_start_end(list_):
result = [x + y for x, y in zip(list_, list_[::-1])][:len(list_) // 2]
if len(list_) % 2 != 0:
result.append(list_[len(list_) // 2])
return result
so for a symmetric list
>>> num_list = [1, 2, 3, 4, 5, 6]
>>> sum_start_end(num_list)
[7, 7, 7]
and for asymmetric list
>>> num_list = [1, 2, 3, 4, 5, 6, 7]
>>> sum_start_end(num_list)
[8, 8, 8, 4]
It's simpler than you imagine.
Just observe your manual attempt and try to infer from it. We can simply do
x = len(num_list)//2 + len(num_list)%2
for i in range(x):
sumBoth = num_list[i] + num_list[-i-1]
num_list2.append(sumBoth)
or with a simpler one-liner
num_list2 = [ num_list[i] + num_list[-i-1] for i in range(len(num_list)//2+len(num_list)%2)]
This works for even as well as odd lengths because of the len(num_list)%2 at the end in the range.

Checking if you can divide list into 2 with exact sum of numbers that are in them

My problem is that I have a list, for example
l =[1, 2, 3, 4, 5, 15]
and I would like to divide it in two lists, list1 that would have a single element of the actual list which should be the sum of all other numbers in the list, and list2 containing rest. So the output for this would be ([1, 2, 3, 4, 5], [15]) if its possible if not, return False.
This is one way, though not necessarily optimal. It uses the, in my opinion underused, for...else... construct.
I've also reversed the range iterator. This is more efficient in the case you provided.
l = [1, 2, 3, 4, 5, 15]
def splitter(l):
for i in reversed(range(len(l))):
if sum(l[:i]) == sum(l[i:]):
return [l[:i], l[i:]]
else:
return False
splitter(l) # [[1, 2, 3, 4, 5], [15]]
Should it be possible for the positions of the values to change in the list? If not you can try an iteration such as:
l = [1, 2, 3, 4, 5, 15]
dividable = "False"
x = 0
while dividable == "False":
l1 = l[0:x]
l2 = l[x:len(l)]
if sum(l1) == sum(l2):
dividable = "True"
elif x == len(l):
#not possible
break
else:
x += 1
This answer should help in all cases.
No imports required and no sorting required for the data.
def split_list(l):
dividable=False
index=0
for i in range(len(l)):
if l[i]==sum(l)-l[i]:
dividable=True
index=i
break
if dividable:
l1=l[index]
l.remove(l[index])
return (l1,l)
else:
return False
Might not be the optimised way, but a better and clear way to understand for beginners.
split_list([1,2,3,4,5,15])
[15],[1,2,3,4,5]
Hope this helps. Thanks
what about this?
l =[1, 2, 3, 4, 5, 15]
l=sorted(l)
track=[]
for i in l:
track.append(i)
if sum(track) in l and len(track)==len(l[1:]):
print(track,[sum(track)])
output:
[1, 2, 3, 4, 5], [15]
You need to do a couple of steps:
1) Sort the list from small to large. (Into a new list if you don't want to alter the original)
2) Sum every other element of the list and see if it's equal.
3) If false return false
4) if true:
Store the last (biggest) value in a variable and delete this from the duplicate of the original list.
Make a second list with only that last value in it.
Create another new list and add the altered duplicate list and the list made of the biggest element.
Return the last created list.
Then you're done
Brute force:
import itertools
x = [1, 2, 3, 4, 5, 15]
for size in range(1,len(x)):
for sublist in itertools.combinations(x, size):
comp = x[:]
for n in sublist:
comp.remove(n)
if sum(comp) == sum(sublist):
print(comp, sublist)
[1, 2, 3, 4, 5] (15,)
[15] (1, 2, 3, 4, 5)
This approach can handle duplicated numbers.
Using numpy:
def split(l):
c = np.cumsum(l)
idx = np.flatnonzero(np.equal(l, c[-1] / 2.0))
return (l[:idx[0]], l[idx[0]:]) if idx.size > 0 else False
Alternatively, if using Python > 3.2:
import itertools
def split(l):
c = list(itertools.accumulate(l))
h = c[-1] / 2.0
if h in c:
i = l.index(h)
return l[:i], l[i:]
return False
Finally, if you want to use "pure" Python (no imports):
def split(l):
c = [sum(l[:k]) for k in range(1, len(l) + 1)]
h = c[-1] / 2.0
if h in c:
i = l.index(h)
return l[:i], l[i:]
return False

Writing an implementation of Selection Sort in Python and my code is throwing "min() arg is an empty set" on a populated list

As an exercise, I am writing an implementation of selection sort (as well as insertion sort and quick sort); for me, the best way to gain a deeper understanding of a function is implementing it myself. I have the following code:
def selectionSort(L):
S = []
i = 0
a = len(L)
while i <= a:
S.append(min(L)) #Find min(L) and add it to set S
L.remove(min(L)) #Remove that same element min(L) from L
i += 1
return S #Return the sorted list
L = [int(x) for x in input('Input a list to be sorted: ').split()]
print(selectionSort(L))
The idea here is to have the user input a list of integers to be sorted and run the selectionSort() function on the list. For the life of me, I cannot figure out why min(L) is throwing the error min() arg is an empty sequence. I have written other code which takes a list as input in the same manner and it works fine.
You are looping once too many:
i = 0
a = len(L)
while i <= a:
#
i += 1
Here i goes from 0 through to len(a) inclusive, so you loop len(a) + 1 times. But there are only len(a) items in the list.
Your options are to pick one of the following
start i at 1, not at 0
stop at i < a (dropping the =) to not include len(a)
Use a for i in range(len(a)) loop; this produces values from 0 through to len(a) - 1.
Simply test if L is empty with
while L:
and remove a and i from your code altogether.
The latter option leads to less code and is clearer:
def selectionSort(L):
S = []
while L:
pick = min(L)
S.append(pick)
L.remove(pick)
return S #Return the sorted list
Note that I store the result of min(L) first to avoid scanning the list twice.
Your issue should already be fixed by #AChampion's comment, I just want to add some clarifications about what you're trying to do. The following code should work for you, but it still has some other issues:
def selectionSort(L):
S = []
i = 0
a = len(L)
while i < a: # Or even better: for i in range(len(L)) and without i += 1
S.append(min(L)) #Find min(L) and add it to set S
L.remove(min(L)) #Remove that same element min(L) from L
i += 1
return S
But, look at the following outputs:
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> selectionSort(my_list)
[1, 2, 3, 4, 4, 5, 7, 7, 9]
>>>
>>> my_list
[]
You see, the input my_list is empty now, because it is altered inside your function. To fix this issue, you can do: (because you may still need to use your original list)
def my_sort(my_list):
temp = my_list[:]
sorted_list = []
for i in range(len(temp)):
sorted_list.append(min(temp))
temp.remove(min(temp))
return sorted_list
Now, inside our function, we use a copy of our original list temp = my_list[:], this is equivalent to temp = copy.copy(my_list):
>>> import copy
>>>
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> id(my_list)
36716416
>>> l1 = my_list
>>> id(l1)
36716416 # In this case the id of l1 is the same as my_list's id!
>>> l2 = my_list[:]
>>> id(l2)
36738832 # This is NOT the same as my_list's id
>>> l3 = copy.copy(my_list)
>>> id(l3)
36766424 # This is NOT the same as my_list's id
Output of the new function:
>>> my_list = [3, 5, 1, 9, 2, 7, 4, 7, 4]
>>> my_sort(my_list)
[1, 2, 3, 4, 4, 5, 7, 7, 9]
>>>
>>> my_list
[3, 5, 1, 9, 2, 7, 4, 7, 4] # Our original list is still alive!
You may need:
As per your goal (implementing your own function for learning purpose), you may also need to use your own min function instead of the built-in one, the following is an example that you can try:
def my_min(my_list):
min = my_list[0]
for i in range(1, len(my_list)):
if min > my_list[i]:
min = my_list[i]
return min
Output:
>>> l1 = [3, 7, 2, 5]
>>> my_min(l1)
2

Cycle a list from alternating sides

Given a list
a = [0,1,2,3,4,5,6,7,8,9]
how can I get
b = [0,9,1,8,2,7,3,6,4,5]
That is, produce a new list in which each successive element is alternately taken from the two sides of the original list?
>>> [a[-i//2] if i % 2 else a[i//2] for i in range(len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Explanation:
This code picks numbers from the beginning (a[i//2]) and from the end (a[-i//2]) of a, alternatingly (if i%2 else). A total of len(a) numbers are picked, so this produces no ill effects even if len(a) is odd.
[-i//2 for i in range(len(a))] yields 0, -1, -1, -2, -2, -3, -3, -4, -4, -5,
[ i//2 for i in range(len(a))] yields 0, 0, 1, 1, 2, 2, 3, 3, 4, 4,
and i%2 alternates between False and True,
so the indices we extract from a are: 0, -1, 1, -2, 2, -3, 3, -4, 4, -5.
My assessment of pythonicness:
The nice thing about this one-liner is that it's short and shows symmetry (+i//2 and -i//2).
The bad thing, though, is that this symmetry is deceptive:
One might think that -i//2 were the same as i//2 with the sign flipped. But in Python, integer division returns the floor of the result instead of truncating towards zero. So -1//2 == -1.
Also, I find accessing list elements by index less pythonic than iteration.
cycle between getting items from the forward iter and the reversed one. Just make sure you stop at len(a) with islice.
from itertools import islice, cycle
iters = cycle((iter(a), reversed(a)))
b = [next(it) for it in islice(iters, len(a))]
>>> b
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
This can easily be put into a single line but then it becomes much more difficult to read:
[next(it) for it in islice(cycle((iter(a),reversed(a))),len(a))]
Putting it in one line would also prevent you from using the other half of the iterators if you wanted to:
>>> iters = cycle((iter(a), reversed(a)))
>>> [next(it) for it in islice(iters, len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
>>> [next(it) for it in islice(iters, len(a))]
[5, 4, 6, 3, 7, 2, 8, 1, 9, 0]
A very nice one-liner in Python 2.7:
results = list(sum(zip(a, reversed(a))[:len(a)/2], ()))
>>>> [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
First you zip the list with its reverse, take half that list, sum the tuples to form one tuple, and then convert to list.
In Python 3, zip returns a generator, so you have have to use islice from itertools:
from itertools import islice
results = list(sum(islice(zip(a, reversed(a)),0,int(len(a)/2)),()))
Edit: It appears this only works perfectly for even-list lengths - odd-list lengths will omit the middle element :( A small correction for int(len(a)/2) to int(len(a)/2) + 1 will give you a duplicate middle value, so be warned.
Use the right toolz.
from toolz import interleave, take
b = list(take(len(a), interleave((a, reversed(a)))))
First, I tried something similar to Raymond Hettinger's solution with itertools (Python 3).
from itertools import chain, islice
interleaved = chain.from_iterable(zip(a, reversed(a)))
b = list(islice(interleaved, len(a)))
If you don’t mind sacrificing the source list, a, you can just pop back and forth:
b = [a.pop(-1 if i % 2 else 0) for i in range(len(a))]
Edit:
b = [a.pop(-bool(i % 2)) for i in range(len(a))]
Not terribly different from some of the other answers, but it avoids a conditional expression for determining the sign of the index.
a = range(10)
b = [a[i // (2*(-1)**(i&1))] for i in a]
i & 1 alternates between 0 and 1. This causes the exponent to alternate between 1 and -1. This causes the index divisor to alternate between 2 and -2, which causes the index to alternate from end to end as i increases. The sequence is a[0], a[-1], a[1], a[-2], a[2], a[-3], etc.
(I iterate i over a since in this case each value of a is equal to its index. In general, iterate over range(len(a)).)
The basic principle behind your question is a so-called roundrobin algorithm. The itertools-documentation-page contains a possible implementation of it:
from itertools import cycle, islice
def roundrobin(*iterables):
"""This function is taken from the python documentation!
roundrobin('ABC', 'D', 'EF') --> A D E B F C
Recipe credited to George Sakkis"""
pending = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables) # next instead of __next__ for py2
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
so all you have to do is split your list into two sublists one starting from the left end and one from the right end:
import math
mid = math.ceil(len(a)/2) # Just so that the next line doesn't need to calculate it twice
list(roundrobin(a[:mid], a[:mid-1:-1]))
# Gives you the desired result: [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
alternatively you could create a longer list (containing alternating items from sequence going from left to right and the items of the complete sequence going right to left) and only take the relevant elements:
list(roundrobin(a, reversed(a)))[:len(a)]
or using it as explicit generator with next:
rr = roundrobin(a, reversed(a))
[next(rr) for _ in range(len(a))]
or the speedy variant suggested by #Tadhg McDonald-Jensen (thank you!):
list(islice(roundrobin(a,reversed(a)),len(a)))
Not sure, whether this can be written more compactly, but it is efficient as it only uses iterators / generators
a = [0,1,2,3,4,5,6,7,8,9]
iter1 = iter(a)
iter2 = reversed(a)
b = [item for n, item in enumerate(
next(iter) for _ in a for iter in (iter1, iter2)
) if n < len(a)]
For fun, here is an itertools variant:
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> list(chain.from_iterable(izip(islice(a, len(a)//2), reversed(a))))
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
This works where len(a) is even. It would need a special code for odd-lengthened input.
Enjoy!
Not at all elegant, but it is a clumsy one-liner:
a = range(10)
[val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]
Note that it assumes you are doing this for a list of even length. If that breaks, then this breaks (it drops the middle term). Note that I got some of the idea from here.
Two versions not seen yet:
b = list(sum(zip(a, a[::-1]), ())[:len(a)])
and
import itertools as it
b = [a[j] for j in it.accumulate(i*(-1)**i for i in range(len(a)))]
mylist = [0,1,2,3,4,5,6,7,8,9]
result = []
for i in mylist:
result += [i, mylist.pop()]
Note:
Beware: Just like #Tadhg McDonald-Jensen has said (see the comment below)
it'll destroy half of original list object.
One way to do this for even-sized lists (inspired by this post):
a = range(10)
b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
I would do something like this
a = [0,1,2,3,4,5,6,7,8,9]
b = []
i = 0
j = len(a) - 1
mid = (i + j) / 2
while i <= j:
if i == mid and len(a) % 2 == 1:
b.append(a[i])
break
b.extend([a[i], a[j]])
i = i + 1
j = j - 1
print b
You can partition the list into two parts about the middle, reverse the second half and zip the two partitions, like so:
a = [0,1,2,3,4,5,6,7,8,9]
mid = len(a)//2
l = []
for x, y in zip(a[:mid], a[:mid-1:-1]):
l.append(x)
l.append(y)
# if the length is odd
if len(a) % 2 == 1:
l.append(a[mid])
print(l)
Output:
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]

Categories