Do iterators save memory in Python? - python

I don't quite understand how iterators have memory in Python.
>>> l1 = [1, 2, 3, 4, 5, 6]
>>> l2 = [2, 3, 4, 5, 6, 7]
>>> iz = izip(l1, l2)
We still require O(min(l1, l2)) memory as we need to load the lists l1 and l2 in memory.
I thought one of the main uses of iterators was to save memory - yet it does not seem to be useful here.
Similarly, the code below is unclear to me:
>>> l1 = ( n for n in [1, 2, 3, 4, 5, 6] )
>>> l2 = ( n for n in [2, 3, 4, 5, 6, 7] )
>>> iz = izip(l1, l2)
We need to load the lists before converting them into generators, right? This means we'll waste memory. So - what is the point of generators here as well.
This is the only case that makes sense to me:
def build_l1():
for n in xrange(1, 6):
yield n
def build_l2:
for n in xrange(2, 7):
yield n
l1 = build_l1()
l2 = build_l2()
iz = izip(l1, l2)
None of the arrays is being loaded into memory. Hence we're in O(1) memory.
How does the memory usage of the iterator functions in Python work? The first two cases seem to use O(min(l1, l2)) memory. I thought the main point of iterators was to save memory, which makes the first two cases seem useless.

Your examples are too simplistic. Consider this:
nums = [1, 2, 3, 4, 5, 6]
nums_it = (n for n in nums)
nums_it is a generator that returns all items unmodified from nums. Clearly you do not have any advantage. But consider this:
squares_it = (n ** 2 for n in nums)
and compare it with:
squares_lst = [n ** 2 for n in nums]
With squares_it, we are generating the squares of nums on the fly only when requested. With squares_lst, we are generating all of them at once and storing them in a new list.
So, when you do:
for n in squares_it:
print(n)
it's like if you were doing:
for n in nums:
print(n ** 2)
But when you do:
for n in squares_lst:
print(n)
it's like if you were doing:
squares_lst = []
for n in nums:
squares_lst.append(n ** 2)
for n in squares_lst:
print(n)
If you don't need (or don't have) the list nums, then you can save even more space by using:
squares_it = (n ** 2 for n in xrange(1, 7))
Generators and iterators also provide another significant advantage (which may actually be a disadvantage, depending on the situation): they are evaluated lazily.
Also, generators and iterators may yield an infinite number of elements. An example is itertools.count() that yields 0, 1, 2, 3, ... without never ending.

>>> l1 = [1, 2, 3, 4, 5, 6]
>>> l2 = [2, 3, 4, 5, 6, 7]
>>> iz = izip(l1, l2)
We still require O(min(l1, l2)) memory as we need to load the lists l1 and l2 in memory.
With zip you need storage for the two original lists plus the zipped list. With izip you don't store the zipped list.
Big O notation isn't particularly helpful here if you have to work with a real physical machine instead of some abstract concept of a machine. There's a hidden constant multiplier on your O(n) calculations that could influence the practicality of the code well before n tends to infinity.
>>> l1 = ( n for n in [1, 2, 3, 4, 5, 6] )
>>> l2 = ( n for n in [2, 3, 4, 5, 6, 7] )
>>> iz = izip(l1, l2)
We need to load the lists before converting them into generators, right? This means we'll waste memory. So - what is the point of generators here as well.
No point to generators here. Any time you see n for n in <expr> without either a more complex expression before the for or an if <expr> filter after it, that's a code smell as you could just have used the original sequence directly. The generators only become useful when you transform the input values into something else or filter them.

Related

Sort a list from an index to another index [duplicate]

This question already has answers here:
Sort a part of a list in place
(3 answers)
Closed 3 years ago.
Suppose I have a list [2, 4, 1, 3, 5].
I want to sort the list just from index 1 to the end, which gives me [2, 1, 3, 4, 5]
How can I do it in Python?
(No extra spaces would be appreciated)
TL;DR:
Use sorted with a slicing assignment to keep the original list object without creating a new one:
l = [2, 4, 1, 3, 5]
l[1:] = sorted(l[1:])
print(l)
Output:
[2, 1, 3, 4, 5]
Longer Answer:
After the list is created, we will make a slicing assignment:
l[1:] =
Now you might be wondering what does [1:], it is slicing the list and starts from the second index, so the first index will be dropped. Python's indexing starts from zero, : means get everything after the index before, but if it was [1:3] it will only get values that are in between the indexes 1 and 3, let's say your list is:
l = [1, 2, 3, 4, 5]
If you use:
print(l[1:])
It will result in:
[2, 3, 4, 5]
And if you use:
print(l[1:3])
It will result in:
[2, 3]
About slicing, read more here if you want to.
And after slicing we have an equal sign =, that just simply changes what's before the = sign to what's after the = sign, so in this case, we use l[1:], and that gives [2, 3, 4, 5], it will change that to whatever is after the = sign.
If you use:
l[1:] = [100, 200, 300, 400]
print(l)
It will result in:
[1, 100, 200, 300, 400]
To learn more about it check out this.
After that, we got sorted, which is default builtin function, it simple sorts the list from small to big, let's say we have the below list:
l = [3, 2, 1, 4]
If you use:
print(sorted(l))
It will result in:
[1, 2, 3, 4]
To learn more about it check this.
After that we come back to our first topic about slicing, with l[1:], but from here you know that it isn't only used for assignments, you can apply functions to it and deal with it, like here we use sorted.
Maybe temporarily put something there that's smaller than the rest? Should be faster than the other solutions. And gets as close to your "No extra spaces" wish as you can get when using sort or sorted.
>>> tmp = l[0]
>>> l[0] = float('-inf')
>>> l.sort()
>>> l[0] = tmp
>>> l
[2, 1, 3, 4, 5]
Benchmarks
For the example list, 1,000,000 iterations (and mine of course preparing that special value only once):
sort_u10 0.8149 seconds
sort_chris 0.8569 seconds
sort_heap 0.7550 seconds
sort_heap2 0.5982 seconds # using -1 instead of -inf
For 50,000 lists like [int(x) for x in os.urandom(100)]:
sort_u10 0.4778 seconds
sort_chris 0.4786 seconds
sort_heap 0.8106 seconds
sort_heap2 0.4437 seconds # using -1 instead of -inf
Benchmark code:
import timeit, os
def sort_u10(l):
l[1:] = sorted(l[1:])
def sort_chris(l):
l = l[:1] + sorted(l[1:])
def sort_heap(l, smallest=float('-inf')):
tmp = l[0]
l[0] = smallest
l.sort()
l[0] = tmp
def sort_heap2(l):
tmp = l[0]
l[0] = -1
l.sort()
l[0] = tmp
for _ in range(3):
for sort in sort_u10, sort_chris, sort_heap, sort_heap2, sort_rev:
number, repeat = 1_000_000, 5
data = iter([[2, 4, 1, 3, 5] for _ in range(number * repeat)])
# number, repeat = 50_000, 5
# data = iter([[int(x) for x in os.urandom(100)] for _ in range(number * repeat)])
t = timeit.repeat(lambda: sort(next(data)), number=number, repeat=repeat)
print('%10s %.4f seconds' % (sort.__name__, min(t)))
print()
Use sorted with slicing:
l[:1] + sorted(l[1:])
Output:
[2, 1, 3, 4, 5]
For the special case that you actually have, according to our comments:
Q: I'm curious: Why do you want this? – Heap Overflow
A: I'm trying to make a next_permutation() in python – nwice13
Q: Do you really need to sort for that, though? Not just reverse? – Heap Overflow
A: Yup, reverse is ok, but I just curious to ask about sorting this way. – nwice13
I'd do that like this:
l[1:] = l[:0:-1]
You can define your own function in python using slicing and sorted and this function (your custom function) should take start and end index of the list.
Since list is mutable in python, I have written the function in such a way it doesn't modify the list passed. Feel free to modify the function. You can modify the list passed to this function to save memory if required.
def sortedList(li, start=0, end=None):
if end is None:
end = len(li)
fi = []
fi[:start] = li[:start]
fi[start:end] = sorted(li[start:end])
return fi
li = [2, 1, 4, 3, 0]
print(li)
print(sortedList(li, 1))
Output:
[2, 1, 4, 3, 0]
[2, 0, 1, 3, 4]

Python Algorithms: Necklace Generation / Circular Permutations

I am struggling with generating circular permutations, or solving the "Necklace Problem" with Python. I am basically looking to generate all circular permutations of a list as efficiently as possible.
Basically, assume we have a list [1,2,3,4], I want to generate all unique permutations in a circular fashion. So:
[1,2,3,4], [1,3,2,4]
Would be considered different, whereas:
[1,2,3,4], [4,1,2,3] would be considered the same, as I am looking only for unique permutations of a circular list.
I have tried generating all permutations with itertools but this is very inefficient when using larger length lists.
As another example, consider a running loop songs, characterised by [1,2,3,4,5] playing on repeat. I am trying to come up with an algorithm to generate only unique orders. Obviously [1,2,3,4,5] and [4,5,1,2,3] would result in the same order.
Struggling and would appreciate some help to solve this problem!
The following code produces all permutations of the last n-1 numbers, prepending each permutation with the first element of the original list.
from itertools import permutations
circular_perms = [my_list[:1]+list(perm) for perm in permutations(my_list[1:])]
Where my_list is your initial list of values to generation all circular permutations of.
Think of a "fixed point" on the necklace.
arrange for the lowest numbered bead, 1, to be rotated to this point
Then the only orders of the beads is all the permutations of items that start with 1
Because the itertool.permutations generator in Python has the first item in the input list varying the least in successive output, the following code just outputs all the solutions as seen when rotated to the fixed point above.
from itertools import permutations, islice
from math import factorial
items = [1, 2, 3 ,4]
ilength = len(items)
base_necklace_orders = list(islice(permutations(items), factorial(ilength) // ilength))
print(base_necklace_orders)
# [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2)]
There are two Python libraries (that I know of) which will generate combinatoric necklaces. They are Sage and SymPy.
The complexity for making permutation is about O(n*n!), so for large number or list it would inefficient to generate all possible permutations, You can use backtracking to generating list permutation, I'll share a link may be this would help.
The solution is based on the backtracking
def permute(a, l, r):
if l == r:
print(a)
else:
for i in range(l, r + 1):
a[l], a[i] = a[i], a[l]
permute(a, l + 1, r)
a[l], a[i] = a[i], a[l]
data = [1,2,3,4,5]
n = len(data)
a = list(data)
permute(a, 0, n - 1)
When you havea list of N elements, one of the circular permutations of the list is uniquely given by its first element. Than means that you will have exactly N circular permutation (including the original list), and you can pass from one to another by removing fist element and adding it at the end of the list.
You can easily build a generator for all the circular permutations of a list:
def circ_perm(lst):
cpy = lst[:] # take a copy because a list is a mutable object
yield cpy
for i in range(len(lst) - 1):
cpy = cpy[1:] + [cpy[0]]
yield cpy
Demo:
>>> list(circ_perm([1,2,3,4]))
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
If what you want is the unique permutations when two permutations that are a ciruclar permution of the other one, you can still use the fact that a circular permutation is given by its first element and fix the first element and find all the permutations of what remains:
def uniq_perm(lst):
gen = itertools.permutations(lst[1:])
for end in gen:
yield [lst[0]] + list(end)
Demo:
>>> list(uniq_perm([1,2,3,4]))
[[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 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]

How do I reverse a sublist in a list in place?

I'm supposed to create a function, which input is a list and two numbers, the function reverses the sublist which its place is indicated by the two numbers.
for example this is what it's supposed to do:
>>> lst = [1, 2, 3, 4, 5]
>>> reverse_sublist (lst,0,4)
>>> lst [4, 3, 2, 1, 5]
I created a function and it works, but I'm not sure is it's in place.
This is my code:
def reverse_sublist(lst,start,end):
sublist=lst[start:end]
sublist.reverse()
lst[start:end]=sublist
print(lst)
def reverse_sublist(lst,start,end):
lst[start:end] = lst[start:end][::-1]
return lst
Partial reverse with no temporary list (replace range with xrange if you use Python 2):
def partial_reverse(list_, from_, to):
for i in range(0, int((to - from_)/2)):
(list_[from_+i], list_[to-i]) = (list_[to-i], list_[from_+i])
list_ = [1, 2, 3, 4, 5, 6, 7, 8]
partial_reverse(list_, 3, 7)
print(list_)
Easiest way to reverse a list in a partial or complete manner.
listVar = ['a','b','c','d']
def listReverse(list,start,end):
while(start<end):
temp = list[start]
list[start] = list[end] #Swaping
list[end]=temp
start+=1
end-=1
print(list)
listReverse(listVar,1,3)
Output : - ['a', 'd', 'c', 'b']
Not sure if you have a similar problem as mine, but i needed to reverse a list in place.
The only piece I was missing was [:]
exStr = "String"
def change(var):
var[:] = var[::-1] # This line here
print(exStr) #"String"
change(exStr)
print(exStr) #"gnirtS"
... I'm not sure is it's in place.
...
lst[start:end]=sublist
Yes, it's in place. lst is never rebound, only its object mutated.
Just use a slice:
>>> lst = [1, 2, 3, 4, 5]
>>> lst[0:len(lst[3::-1])]=lst[3::-1]
>>> lst
[4, 3, 2, 1, 5]
Or, perhaps easier to understand:
>>> lst = [1, 2, 3, 4, 5]
>>> sl=lst[3::-1]
>>> lst[0:len(sl)]=sl
>>> lst
[4, 3, 2, 1, 5]
lst[::-1] is the idiomatic way to reverse a list in Python, The following show how and that it was in-place:
>>> lst = [1, 2, 3, 4, 5]
>>> id(lst)
12229328
>>> lst[:] = lst[::-1]
>>> lst
[5, 4, 3, 2, 1]
>>> id(lst)
12229328
Try some crazy slicing, see Explain Python's slice notation and http://docs.python.org/2.3/whatsnew/section-slices.html
x = [1,2,3,4,5,6,7,8]
def sublist_reverse(start_rev, end_rev, lst):
return lst[:end_rev-1:start_rev-1]+lst[:[end_rev]
print sublist_reverse(0,4,x)
[out]:
[8, 7, 6, 5, 4, 3, 2, 1]
I have two ways for in-place reversal, the simple way is to loop through the list half-way, swapping the elements with the respective mirror-elements. By mirror-element I mean (first, last), (2nd, 2nd-last), (3rd, 3rd-last), etc.
def reverse_list(A):
for i in range(len(A) // 2): # half-way
A[i], A[len(A) - i - 1] = A[len(A) - i - 1], A[i] #swap
return A
The other way is similar to the above but using recursion as opposed to a "loop":
def reverse_list(A):
def rev(A, start, stop):
A[start], A[stop] = A[stop], A[start] # swap
if stop - start > 1: # until halfway
rev(A, start + 1, stop - 1)
return A
return rev(A, 0, len(A) - 1)
I've conducted a tiny experiment and it seems that any assignment to list slice causes memory allocation:
import resource
resource.setrlimit(resource.RLIMIT_AS, (64 * 1024, 64 * 1024))
try:
# Python 2
zrange = xrange
arr_size = 3 * 1024
except NameError:
# Python 3
zrange = range
arr_size = 4 * 1024
arr = list(zrange(arr_size))
# We could allocate additional 100 integers, so there should be enough free memory
# to allocate a couple of variables for indexes in the statement below
# additional_memory = list(zrange(100))
# MemoryError is raised here
arr[:] = zrange(arr_size)
So you have to use for loop to reverse a sublist in place.
PS: If you want to repeat this test, you should ensure that setrlimit RLIMIT_AS works fine on your platform. Also arr_size may vary for different python implementations.
Two methods in-place and constant memory:
def reverse_swap(arr, start=None, end=None):
"""
Swap two edge pointers until meeting in the center.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
i = start
j = end - 1
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i += 1
j -= 1
def reverse_slice(arr, start=None, end=None):
"""
Use python slice assignment but use a generator on the right-hand-side
instead of slice notation to prevent allocating another list.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
arr[start:end] = (arr[i] for i in range(end - 1, start - 1, -1))
The simplest way is probably to use slice assignment and reversed():
lst[start:end] = reversed(lst[start:end])
You could also slice and reverse at the same time, however this generally requires either using negative indexes or specially handling the case when start = 0, i.e.:
lst[start:end] = lst[end-len(lst)-1:start-len(lst)-1:-1]
or
lst[start:end] = lst[end-1::-1] if start == 0 else lst[end-1:start-1:-1]
OTOH, using slicing alone is faster than the reversed() solution.
Much cleaner way to do this
a = [1,2,3,4,5,6,7,8,9]
i = 2
j = 7
while (i<j):
a[i],a[j]=a[j],a[i]
j -= 1
i += 1
print(a)
lst = [1,2,3,4,5,6,7,8]
Suppose you have to reverse 2nd position to 4th position in place.
lst[2:5] = lst[2:5][::-1]
Output:
[1,2,5,4,3,6,7,8]

List Comprehension to Sublists

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.

Categories