Python - creating patterned lists - python

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]

Related

How to duplicate a specific value in a list/array?

Any advice on how to repeat a certain value in an array in Python?
For instance, I want to repeat only 2 in array_a:
array_a = [1, 2, 1, 2, 1, 1, 2]
Wanted outcome is: I repeat each 2 and leave the 1:
array_a = [1, 2, 2, 1, 2, 2, 1, 1, 2, 2] # only the `2` should be repeated
I tried numpy and I could duplicate the entire array but not a certain value.
If you're interested in a numpy solution, you can repeat an array on itself using np.repeat.
>>> import numpy as np
>>> np.repeat(array_a, array_a)
array([1, 2, 2, 1, 2, 2, 1, 1, 2, 2])
This works only if you haves 1s and 2s in your data. For a generic solution, consider
>>> n_repeats = 2
>>> temp = np.where(np.array(array_a) == 2, n_repeats, 1)
>>> np.repeat(array_a, temp)
array([1, 2, 2, 1, 2, 2, 1, 1, 2, 2])
May be you can use dictionary to each unique element and number of times it needs to be repeated. Then using list comprehension to create array:
array_a = [1,2,1,2,1,1,2]
repeat_times = {1:1, 2:2} # 1 is 1 time and 2 is repeated two times
result = [i for i in array_a for j in range(repeat_times[i])]
print(result)
Output:
[1, 2, 2, 1, 2, 2, 1, 1, 2, 2]
This seems a good use-case for a generator:
>>> def repeater(iterable, repeat_map):
... for value in iterable:
... for i in range(repeat_map.get(value, 1)):
... yield value
...
>>> array_a = [1,2,1,2,1,1,2]
>>> list(repeater(array_a, repeat_map={2: 2}))
[1, 2, 2, 1, 2, 2, 1, 1, 2, 2]
If you convert this to a list, you can loop through it, and if it matches your criteria, add an extra version. For example:
a = [1,2,1,2,1,1,2]
long_a = []
for x in a:
long_a.append(x)
if x == 2:
long_a.append(x)
loop over the array (a 'list' in python)
find the the number
get the position of the matched number in the array
insert another number after each matched position
https://docs.python.org/3/reference/compound_stmts.html#for
https://docs.python.org/2/tutorial/datastructures.html#more-on-lists
An attempt using comprehensions.
array = [1, 2, 1, 2, 1, 1, 2]
element_to_repeat = 2
result = [
repeats_element
for repeats in
((element,)*2 if element == element_to_repeat else (element,) for element in array)
for repeats_element in repeats
]
It basically spits out tuples, "repeats", which contain the element once if it's not the element to repeat, or twice if it's the element to repeat. Then all of the elements of these "repeats" tuples are flattened into the answer.
Using a generator.
array = [1, 2, 1, 2, 1, 1, 2]
element_to_repeat = 2
def add_repeats(array, element_to_repeat):
for element in array:
if element == element_to_repeat:
yield element
yield element
else:
yield element
result = list(add_repeats(array, element_to_repeat))
Here is a handy one-liner using itertools and list comprehension with if and else in it. First it makes a nested list (to have the ability to repeat items on a certain position) and then it will simply flatten it at the end using .chain()-method:
from itertools import chain
array_a = [1, 2, 1, 2, 1, 1, 2]
list(chain.from_iterable([[item, item] if item == 2 else [item] for item in array_a]))
[1, 2, 2, 1, 2, 2, 1, 1, 2, 2] # output
The specific value to double is inside the if-statement. Using multipliers (instead of [item, item]) and a variable (instead of 2) would make this easily more generic, see this for example:
from itertools import chain
def repeat_certain_value(array, val, n):
return list(chain.from_iterable(([i] * n if i == val else [i] for i in array)))
repeat_certain_value([1, 2, 1, 2, 1, 1, 2], 2, 2)
[1, 2, 2, 1, 2, 2, 1, 1, 2, 2] # output
repeat_certain_value([0, -3, 1], -3, 5)
[0, -3, -3, -3, -3, -3, 1] # output
While this approach is a handy one-liner using builtin libraries, the approach from coldspeed is faster:
%timeit for x in range(1000): repeat_certain_value([1, 1, 1, 2, 2, 2, 3, 3, 3] * 100, 2, 2)
10 loops, best of 3: 165 ms per loop
%timeit for x in range(1000): coldspeeds_solution([1, 1, 1, 2, 2, 2, 3, 3, 3] * 100, 2, 2)
10 loops, best of 3: 100 ms per loop
Can try a list comprehension and create a flat function:
array_a = [1, 2, 1, 2, 1, 1, 2]
def flat(l):
newl=[]
for i in l:
if isinstance(i,list):
newl.extend(i)
else:
newl.append(i)
return newl
print(flat([[i]*2 if i==2 else i for i in array_a]))
Output:
[1, 2, 2, 1, 2, 2, 1, 1, 2, 2]

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]

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. ;-)

Repeat list if index range is out of bounds

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.

python: sampling without replacement from a 2D grid

I need a sample, without replacement, from among all possible tuples of numbers from range(n). That is, I have a collection of (0,0), (0,1), ..., (0,n), (1,0), (1,1), ..., (1,n), ..., (n,0), (n,1), (n,n), and I'm trying to get a sample of k of those elements. I am hoping to avoid explicitly building this collection.
I know random.sample(range(n), k) is simple and efficient if I needed a sample from a sequence of numbers rather than tuples of numbers.
Of course, I can explicitly build the list containing all possible (n * n = n^2) tuples, and then call random.sample. But that probably is not efficient if k is much smaller than n^2.
I am not sure if things work the same in Python 2 and 3 in terms of efficiency; I use Python 3.
Depending on how many of these you're selecting, it might be simplest to just keep track of what things you've already picked (via a set) and then re-pick until you get something that you haven't picked already.
The other option is to just use some simple math:
numbers_in_nxn = random.sample(range(n*n), k) # Use xrange in Python 2.x
tuples_in_nxn = [divmod(x,n) for x in numbers_in_nxn]
You say:
Of course, I can explicitly build the
list containing all possible (n * n =
n^2) tuples, and then call
random.sample. But that probably is
not efficient if k is much smaller
than n^2.
Well, how about building the tuple after you have randomly picked one? Ie, if you can build the tuples before you randomly choose which one to pick, you can do the picking first and building later.
I don't understand how your tuples are supposed to look, but here is an example, although I realize your tuples are all of the same length, this shows the principle:
Instead of doing this:
>>> import random
>>> all_sequences = [range(x) for x in range(10)]
>>> all_sequences
[[], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3], [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]]
>>> random.sample(all_sequences, 3)
[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6, 7, 8]]
You would do this:
>>> import random
>>> selection = random.sample(range(10), 3)
>>> [range(x) for a in selection]
[[0, 1, 2, 3, 4, 5, 6, 7, 8], [0, 1, 2, 3, 4, 5, 6, 7, 8], [0, 1, 2, 3, 4, 5, 6, 7, 8]]
Without trying (no python at hand):
random.shuffle(range(n))[:k]
see comments. Didn't sleep enough...

Categories