Repeat list if index range is out of bounds - python

I have a Python list
a = [1, 2, 3, 4]
and I'd like to get a range of indices such that if I select the indices 0 through N, I'm getting (for N=10) the repeated
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
I could of course repeat the list via (int(float(N) / len(a) - 0.5) + 1) * a first and select the range [0:10] out of that, but that feels rather clumsy.
Any hints?

You can simply use the modulo operator when accessing the list, i.e.
a[i % len(a)]
This will give you the same result, but doesn't require to actually store the redundant elements.

You can use itertools.cycle and itertools.islice:
from itertools import cycle, islice
my_list = list(islice(cycle(my_list), 10))
Note that if you just want to iterate over this once, you should avoid calling list and just iterate over the iterable, since this avoids allocating repeated elements.

One easy way is to use modulo with list comprehensions à la
a = [1, 2, 3 ,4]
[k % len(a) for k in range(10)]

>>> a = [1, 2, 3, 4]
>>> (a*3)[:-2]
>>> [1, 2, 3, 4, 1, 2, 3, 4, 1, 2]

Thought I would offer a solution using the * operator for lists.
import math
def repeat_iterable(a, N):
factor = N / len(a) + 1
repeated_list = a * factor
return repeated_list[:N]
Sample Output:
>>> print repeat_iterable([1, 2, 3, 4], 10)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
>>> print repeat_iterable([1, 2, 3, 4], 3)
[1, 2, 3]
>>> print repeat_iterable([1, 2, 3, 4], 0)
[]
>>> print repeat_iterable([1, 2, 3, 4], 14)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2]

How about faking it? Python is good at faking.
class InfiniteList(object):
def __init__(self, data):
self.data = data
def __getitem__(self, i):
return self.data[i % len(self.data)]
x = InfiniteList([10, 20, 30])
x[0] # 10
x[34] # 20
Of course, you could add __iter__, support for slices etc. You could also add a limit (N), but this is the general idea.

Related

List of consecutive numbers repeated according to values in another list

My target is to get a list of consecutive numbers, repeated accordingly with the initial list values. Lets say I have:
initialList=[1,2,3,5]
And I want to get:
targetList=[0,1,1,2,2,2,3,3,3,3,3]
...I'm totally new with Python, sorry for this -probably- very first steps question. Tried many searchs but the results didn't match with my needs, unfortunately. Thank you very much in advance.
The newbie-friendly solution is to use two loops:
result = []
number = 0
for repeat in initialList:
for _ in range(repeat):
result.append(number)
number += 1
print(result) # [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
If you prefer one-liners for whatever reason, you can combine enumerate and range to get
result = [num for num, repeat in enumerate(initialList) for _ in range(repeat)]
IMO, this is a more maintainable functional solution:
initialList = [1, 2, 3, 5]
def listify(x):
return [x]
# create sub-lists [[0], [1], [2], [3], ...]
sublists = map(listify, range(len(initialList)))
# attach to each sub-list the repititions required [([0], 1), ([2], 2), ...]
sublists_with_rep_spec = zip(sublists, initialList)
# create repetitions based on initialList (using list multiplication)
sublists_with_repetitions = starmap(operator.mul, sublists_with_rep_spec)
# flatten everything out
result = chain.from_iterable(sublists_with_repetitions)
print(list(result))
Note that this is all lazy (on python3) so everything will "happen" only when you actually call list.
Here is another way using repeat and chain.from_iterable
from itertools import repeat, chain
list(chain.from_iterable((repeat(idx, num)) for idx, num in enumerate(initialList)))
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
You can use enumerate:
initialList=[1,2,3,5]
final_result = [i for b in [[c]*d for c, d in enumerate(initialList)] for i in b]
Output:
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
This is possible via itertools, if you wish to remove the need for nested logic. itertools is part of the standard library.
For improving your understanding of Python, I recommend you see #Ajax1234's solution for some nested list comprehensions.
from itertools import chain
initialList = [1,2,3,5]
targetList = list(chain.from_iterable([i]*j for i, j in enumerate(initialList)))
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
Note: you can replace [i]*j with itertools.repeat(i, j) or numpy.repeat(i, j) if you use numpy. The former may be preferable as it is lazy.
Very simple solution using sum and enumerate
initialList = [1, 2, 3, 5]
targetList = sum((times*[index] for index, times in enumerate(initialList)), [])
You can try this approach:
data=[[i]*initialList[i] for i,j in enumerate(initialList)]
print([k for i in data for k in i])
Just for fun I tried with lambda :
initialList=[1,2,3,5]
print(list(map(lambda x:[x]*initialList[x],range(0,len(initialList)))))
lambda result is in nested list.
My solution
>>> initialList=[1,2,3,5]
>>> sum(([num]*count for num, count in enumerate(initialList)), [])
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
Another easy way:
from functools import reduce
initialList = [1,2,3,5]
targetList = [[index]*item for index, item in enumerate(initialList)]
targetList = reduce(lambda x,y: x+y, targetList)
print(targetList)
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
I find most of the current answers either poor performance-wise or hard to read. An alternative functional way of doing this would be by using such itertools functions as chain.from_iterable, repeat, and count:
from itertools import chain, count, repeat
initial_list = [1, 2, 3, 5]
result = list(chain.from_iterable(map(repeat, count(), initial_list)))
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]

Does Python have a way of returning a list without the first n elements?

Say I have [1, 2, 3, 4, 5, 6]. And I want [3, 4, 5, 6].
Currently I'm doing:
l = [1, 2, 3, 4, 5, 6]
l[-(len(l) - n):]
I'm not familiar with Python style, but this seems pretty hack-y.
Yes, by slicing:
>>> n = 2
>>> l = [1, 2, 3, 4, 5, 6]
>>> l[n:]
[3, 4, 5, 6]
Read the tutorial here to have a further understanding on how to manipulate Python data structures that support slicing.
Get creative and put it in a function, as mentioned in the comments:
def slice_it(l, n):
return l[n:]
demo:
>>> slice_it(l, 2)
[3, 4, 5, 6]
And as a lambda, as shown in the comments:
sliced_list = lambda l, n: l[n:]
demo:
>>> sliced_list(l, 2)
[3, 4, 5, 6]
You can use positive int as slice lower bound:
l = [1, 2, 3, 4, 5, 6]
l[n:]
You can just l[n:], rather than using negative indexing with the full length of the list.
For example, if len(l) == 6 then:
l[2:] == l[-4:]
Keep in mind #idjaw has a much better answer,
You can also use list comprehensions, like so:
>>> l = [1, 2, 3, 4, 5, 6]
>>> n = 3
>>> l = [x + 1 for x in range(len(l)) if x >= n]
>>> l
[4, 5, 6]

Split a list into increasing sequences using itertools

I have a list with mixed sequences like
[1,2,3,4,5,2,3,4,1,2]
I want to know how I can use itertools to split the list into increasing sequences cutting the list at decreasing points. For instance the above would output
[[1, 2, 3, 4, 5], [2, 3, 4], [1, 2]]
this has been obtained by noting that the sequence decreases at 2 so we cut the first bit there and another decrease is at one cutting again there.
Another example is with the sequence
[3,2,1]
the output should be
[[3], [2], [1]]
In the event that the given sequence is increasing we return the same sequence. For example
[1,2,3]
returns the same result. i.e
[[1, 2, 3]]
For a repeating list like
[ 1, 2,2,2, 1, 2, 3, 3, 1,1,1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
the output should be
[[1, 2, 2, 2], [1, 2, 3, 3], [1, 1, 1, 2, 3, 4], [1, 2, 3, 4, 5, 6]]
What I did to achieve this is define the following function
def splitter (L):
result = []
tmp = 0
initialPoint=0
for i in range(len(L)):
if (L[i] < tmp):
tmpp = L[initialPoint:i]
result.append(tmpp)
initialPoint=i
tmp = L[i]
result.append(L[initialPoint:])
return result
The function is working 100% but what I need is to do the same with itertools so that I can improve efficiency of my code. Is there a way to do this with itertools package to avoid the explicit looping?
With numpy, you can use numpy.split, this requires the index as split positions; since you want to split where the value decreases, you can use numpy.diff to calculate the difference and check where the difference is smaller than zero and use numpy.where to retrieve corresponding indices, an example with the last case in the question:
import numpy as np
lst = [ 1, 2,2,2, 1, 2, 3, 3, 1,1,1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
np.split(lst, np.where(np.diff(lst) < 0)[0] + 1)
# [array([1, 2, 2, 2]),
# array([1, 2, 3, 3]),
# array([1, 1, 1, 2, 3, 4]),
# array([1, 2, 3, 4, 5, 6])]
Psidom already has you covered with a good answer, but another NumPy solution would be to use scipy.signal.argrelmax to acquire the local maxima, then np.split.
from scipy.signal import argrelmax
arr = np.random.randint(1000, size=10**6)
splits = np.split(arr, argrelmax(arr)[0]+1)
Assume your original input array:
a = [1, 2, 3, 4, 5, 2, 3, 4, 1, 2]
First find the places where the splits shall occur:
p = [ i+1 for i, (x, y) in enumerate(zip(a, a[1:])) if x > y ]
Then create slices for each such split:
print [ a[m:n] for m, n in zip([ 0 ] + p, p + [ None ]) ]
This will print this:
[[1, 2, 3, 4, 5], [2, 3, 4], [1, 2]]
I propose to use more speaking names than p, n, m, etc. ;-)

Python - creating patterned lists

In my current Python project, I need to create some long lists of integers for later use in plots. Currently I'm attacking this in the following way:
volume_axis = []
for values in stripped_header:
for doses in range(100):
volume_axis.append(int(values))
This code will append to my blank list, giving me the first value in stripped header 100 times, then the next value in stripped header 100 times etc.
Is there a more elegant and pythonesque way to accomplish this?
for values in stripped_header:
volume_axis += [int(values)] * 100
or using itertools (may be more efficient)
from itertools import repeat
for values in stripped_header:
volume_axis += repeat(int(values), 100)
There have been a number of good pythonic answers to this question, but if your happy to use numpy (which is a dependency of matplotlib anyway) then this is a one liner:
>>> import numpy
>>> stripped_header = [1, 2, 4]
>>>
>>> numpy.repeat(stripped_header, 3)
array([1, 1, 1, 2, 2, 2, 4, 4, 4])
HTH
Using itertools:
from itertools import chain, repeat
list(chain.from_iterable(repeat(int(n), 100) for n in sh))
consider sh represent stripped_header
In [1]: sh = [1,2,3,4]
In [2]: [x for x in sh for y in [sh]*5]
Out[2]: [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]
or you can also go with for ease of understanding
In [3]: [x for x in sh for y in range(5)]
Out[3]: [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]

Access certain elements of a list

Can't figure out how to do this in a pretty way :
I have a list of n elements,
I want to access every m elements of the list.
For example : [1, 2, 3, 4, 5] and m = 2 would give
[2, 4]
I can do it simply with a loop, but ins't there a more "pythonic" way?
Thanks by advance !
EDIT :
Seems like I forgot something.
I want, not only get those values but modify them.
I tried slicing a[::2] = 3, but it doesn't work. . .
I'm searching for something similar
Slicing syntax does this for you:
>>> my_list = range(10)
>>> my_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[::2]
[0, 2, 4, 6, 8]
>>> my_list[1::2]
[1, 3, 5, 7, 9]
Here's a way to wrap a list to get the original assignment behavior you wanted, but I'm not sure I'd recommend it:
class AssignableSlice(list):
def __setitem__(self, i, v):
if isinstance(i, slice):
for ii in xrange(*i.indices(len(self))):
self[ii] = v
else:
super(AssignableSlice, self).__setitem__(i, v)
a = AssignableSlice(range(10))
print a
a[::2] = 3
print a
a[1::3] = 99
print a
produces:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[3, 1, 3, 3, 3, 5, 3, 7, 3, 9]
[3, 99, 3, 3, 99, 5, 3, 99, 3, 9]
Ned's answer shows how to use slices to access a portion of the list. You can also assign to a slice, but you need to assign a list to the slice, for example:
>>> my_list = range(5)
>>> my_list
[0, 1, 2, 3, 4]
>>> my_list[::2]
[0, 2, 4]
>>> my_list[::2] = [0, 0, 0]
>>> my_list
[0, 1, 0, 3, 0]
Note that when the step in your slice is anything besides the default of 1 the list that you assign needs to be the same length, however with a default step you can actually change the size of the list with slice assignment:
>>> my_list = range(5)
>>> my_list
[0, 1, 2, 3, 4]
>>> my_list[:1]
[0]
>>> my_list[:1] = [4, 3, 2] # replace the first item with 3 new items
>>> my_list
[4, 3, 2, 1, 2, 3, 4]
>>> my_list[2:5]
[2, 1, 2]
>>> my_list[2:5] = [] # remove the middle three items from the list
>>> my_list
[4, 3, 3, 4]
I juste found a way to do what I want using slicing.
The following :
candidates[::2] = [1] * len(candidates[::2])
will replace every 2 elements of candidates by 1

Categories