iterating over a sublist in python - python

Say I have the following list:
my_list = [2, 3, 4, 1, 44, 222, 43, 22]
How can I assign a constant value to all the elements in a sublist without the use of for loop? something like:
my_list[0:5:1] = 1 # Assign 1 to first 5 elements. This code is wrong since list requires an iterator
Specifically, I would like to assign a constant value to all the elements, starting from an index, i till the end of the list i.e. say
my_list[i:end] = 1 # What I would like to do. The code itself is wrong
Any suggestions on how to do it in the cleanest way in python?

Sometimes a 2-liner is better than a 1-liner.
for i in range(0,5):
my_list[i] = 0
If you really want to make something obfuscated:
Here's a one-liner which does the job without changing the original array.
new_list = [0 if i in range(0,5) else x for x, i in zip(my_list, xrange(len(my_list)))]
Which would you rather come across in unknown code? Or your own code 6 months later.

The following should work:
my_list[i:end] = [1]*(end-i)
Examples:
>>> def test(my_list, i, end):
... my_list[i:end] = [1]*(end-i)
... return my_list
>>> test(range(10), 0, 5)
[1, 1, 1, 1, 1, 5, 6, 7, 8, 9]
>>> test(range(10), 5, 10)
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1]
>>> test(range(10), 3, 8)
[0, 1, 2, 1, 1, 1, 1, 1, 8, 9]

One possibility is stuff [0:5] = [1] * 5. However, you should be cautious since this will change the size of the list if you get the number of replacing elements wrong. If you do stuff [0:5] = [1] * 6, you will increase the size of the list (i.e., putting six 1s where there used to be five original values).

For going to the end of the list, just omit the end of the slice. Then, you can repeat a list to assign to it:
my_list[i:] = [1] * (len(my_list) - i)

How about this?
from itertools import repeat
my_list[start:end] = repeat( 1, end - start )

Would this work:
my_list = my_list[:i] + [1 for x in xrange(len(my_list) - i)]

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]

Strange behaviour when swapping numbers in a list and then using list comprehension

I am not really sure why this code does not swap the numbers as it was instructed to in the for loop. Instead, it doesn't change the order of the numbers in the list at all. Is there a reason it behaves that way?
Here is the code:
def foo():
for i in range(len(L)):
L[i], L[-1 - i] = L[-1 - i], L[i]
L = [i for i in range(10)]
foo()
print(L) # Output: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
# As opposed to 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
The problem is that you swapped them all, and then swapped them back again! When i is 1, you swap it with index 8, then when i is 8 you swap back to index 1. To understand exactly what happens when the program runs, it may help you to step through it using the excellent Python Tutor website.
You need to swap index i with index -1 - i only when the first index is before the second in the list. The simplest fix is to change the range from len(L) to len(L) // 2, so the index i only goes halfway through the list, and the other index goes backwards through the other half.
def foo(L):
for i in range(int(len(L)/2)):
L[i], L[-i - 1] = L[-1 - i], L[i]
L = [i for i in range(10)]
foo(L)
print(L)

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]

What value do I use in a slicing range to include the last value in a numpy array?

Imagine some numpy array, e.g. x = np.linspace(1,10).
x[i:j] gives me a view into x for the range [i,j).
I love that I can also do x[i:-k] which excludes the last k elements.
However, in order to include the last element I need to do x[i:].
My question is this: How do I combine these two notations if I for instance need to loop over k.
Say that I want to do this:
l = list()
for k in [5,4,3,2,1]:
l.append(x[:-k])
l.append(x[:])
What annoys me is that last line. In this simple example of course it doesn't do much of a difference, but sometimes this becomes much more annoying. What I miss is something more DRY-like.
The following snippet course does NOT yield the desired result, but represents the style of code I seek:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:-k])
It's a bit of a pain, but since -0 is the same as 0, there is no easy solution.
One way to do it would be:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:-k or None])
This is because when k is 0, -k or None is None, and x[:None] will do what you want. For other values of k, -k or None will be -k.
I am not sure if I like it myself though.
You can't, because -0 doesn't slice that way in python (it becomes 0)
You could just do the old school:
l = list()
for k in [5,4,3,2,1,0]:
l.append(x[:len(x)-k])
The value None, in a slice, is the same as putting nothing there. In other words, x[:None] is the same as x[:]. So:
l = list()
for k in [-5,-4,-3,-2,-1,None]:
l.append(x[:k])
However… this code is a lot easier to write as a list comprehension:
l = [x[:k] for k in (-5,-4,-3,-2,-1,None)]
Or… you might want to look at whatever it is you're trying to do and see if there's a higher-level abstraction that makes sense, or maybe just another way to organize things that's more readable (even if it's a bit more verbose). For example, depending on what x actually represents, this might be more understandable (or it might be less, of course):
l = []
for k in range(6):
l.insert(0, x)
x = x[:-1]
Perhaps this, then:
l = list()
for k in [5,4,3,2,1,None]:
l.append(x[:-k if k else None])
If x is simply range(10), the above code will produce:
[[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 4, 5, 6, 7],
[0, 1, 2, 3, 4, 5, 6, 7, 8],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]

return a list containing elements of another list

I need to write an expression and I'm completely stuck. I have part of the code that I think I have written correctly but I'm stick on the rest of it. I need the code to return a new list containing every 3rd element in the list, starting at index 0.
For example: if I have the list [0, 1, 2, 3, 4, 5] I need it to return [0, 3]
The code I have so far is:
result = []
i = 0
while i < len(L):
result.append(L[i])
i =
return result
Can someone please help me figure out what I need the i = expression to be for this code to work.
First of all, you can make use of extended slice notation to make things easier:
In [1]: l = [0, 1, 2, 3, 4, 5]
In [2]: l[::3]
Out[2]: [0, 3]
From the docs:
Some sequences also support “extended slicing” with a third “step” parameter: a[i:j:k] selects all items of a with index x where x = i + n*k, n >= 0 and i <= x < j.
As for your code sample, you probably need i = i + 3 or i += 3 there.
Maybe try this:
result = []
for i in range(0, len(L), 3):
result.append(L[i])
return result
Another alternative is to use enumerate.
[j for i, j in enumerate([0, 1, 2, 3, 4, 5]) if i % 3 == 0]
this will give tyou an iterable sequence:
import itertools
l = [0, 1, 2, 3, 4, 5]
itertools.islice(l, 0, None, 3)
to turn it into a list, use the list() function.
impiort itertools
def get_sublist(l):
return list(itertools.islice(l, 0, None, 3))
python 3.2
one way:
>>> [i for i in range(0,len(L),3)]
[0,3]
your method:
result = []
i = 0
while i <= len(L):
result.append(L[i])
i+=3
return result

Categories