How to duplicate elements in a nested list? - python

I have a nested list that looks like this:
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
I want to duplicate each element in each inner list by 3 and place them side to side as such:
[[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 4, 4, 5, 5, 5, 6, 6, 6], [7, 7, 7, 8, 8, 8, 9, 9, 9]]
This is my current solution:
from itertools import repeat
x2 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ls = []
for i in x2:
ls.append([x for item in i for x in repeat(item, 3)])
ls
>>> [[1, 1, 1, 2, 2, 2, 3, 3, 3],
[5, 5, 5, 6, 6, 6, 7, 7, 7],
[9, 9, 9, 10, 10, 10, 11, 11, 11],
[4, 4, 4, 8, 8, 8, 3, 3, 3]]
Is there any way to make it faster?

Here is one solution:
ls=[sum([[i]*3 for i in k], []) for k in x2]
>>>print(ls)
[[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 4, 4, 5, 5, 5, 6, 6, 6], [7, 7, 7, 8, 8, 8, 9, 9, 9]]

As you asked for faster, here are some comparisons between the different possible ways presented here and other places.
I used your method using repeat and added a list-comp version of it just to match the others.
I used #IoaTzimas version using sum.
I used the version using zip of #Steven Rumbalski from Duplicate elements in a list
I used the "pure" list-comp solution by #Wololo from the comments.
The results (on my machine) are (ordered ascending):
using_zip 5.630
using_repeat_comp 5.818
using_sum 5.905
using_repeat 6.421
using_pure 6.878
Surprisingly (to me), the zip solution is actually fastest, and the list-comp version of your repeat solution second. I would assume that the "pure" list-comp will be fastest but it was actually the worst. I guess TIL...
There is another result (proposed by #Henry Tjhia):
using_sort 3.219
Significantly better as you can see, but I put it separately as it relies heavily on the above specific example. So if you want to duplicate a list of ascending numbers that would be your best choice.
The code to produce them is:
import timeit
from itertools import repeat
x2 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
def using_repeat():
ls = []
for i in x2:
ls.append([x for item in i for x in repeat(item, 3)])
return ls
def using_repeat_comp():
return [[x for item in i for x in repeat(item, 3)] for i in x2]
def using_zip():
return [[x for triple in zip(l, l, l) for x in triple] for l in x2]
def using_sum():
return [sum([[i]*3 for i in k], []) for k in x2]
def using_pure():
return [[val for val in sublist for _ in range(3)] for sublist in x2]
def using_sort():
return [sorted(sub[:]*3) for sub in x2]
for func in [using_repeat, using_repeat_comp, using_zip, using_sum, using_pure, using_sort]:
print(func.__name__, timeit.timeit(f"{func.__name__}()", f"from __main__ import {func.__name__}"))

Related

Python: I have a list and I want to add it to each element of the list

Let us take an example:
I have a list: 1, 2, 3, 4, 5
I want to add each element to be added with each other.
Such as 1+2, 1+3, 1+4, 1+5, 2+1, 2+3, 2+4, 2+5, and so on.
So the output should come something like 3, 4, 5, 6, 3, 5, 6, 7 and so on.
Can anyone please help me solve this?
from itertools import starmap, permutations
from operator import add
values = [1, 2, 3, 4, 5]
print(list(starmap(add, permutations(values, 2))))
Output:
[3, 4, 5, 6, 3, 5, 6, 7, 4, 5, 7, 8, 5, 6, 7, 9, 6, 7, 8, 9]
>>>
use itertools.permutations:
from itertools import permutations
my_list = [1, 2, 3, 4, 5]
new_list = [x+y for x,y in permutations(my_list, 2)]
Output:
[3, 4, 5, 6, 3, 5, 6, 7, 4, 5, 7, 8, 5, 6, 7, 9, 6, 7, 8, 9]
You can do something like this.
def yar(int_list):
har = []
for num in int_list:
fiddly = []
for value in int_list:
add = num
add += value
fiddly.append(add)
har.append(fiddly)
return har
fee = [1, 2, 3, 4]
print(yar(fee))
# would output [[2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]
The bread and butter to solving this is a nested for loop. For each number in the list (being fee in this case), loop through each number in the list fee and add it to the value in stored in add from the outer loop, then store that value in a list (that being fidddly).
I added the har list so users can access the combinations from each iteration. (index 0 for combos of 1, index 1 for combos of 2, etc.)
You can make this a list comprehension somehow, I'm sure of it for some reason. I'm just not the best at figuring that out yet, but this code still works.
If all your elements are distinct, you can do it in a list comprehension than adds every number with all the numbers except itself:
L = [1, 2, 3, 4, 5]
S = [ a+b for a in L for b in L if a!=b]
# [3, 4, 5, 6, 3, 5, 6, 7, 4, 5, 7, 8, 5, 6, 7, 9, 6, 7, 8, 9]
If there can be duplicate numbers in the list, then you need to exclude addition of numbers with themselves based on their indexes:
[ a+b for i,a in enumerate(L) for j,b in enumerate(L) if i!=j]

padding while creating sublists

Is there an elegant way how to pad the last sublist with zeroes while creating sublists from a list of integers?
So far I have this oneliner and need to fill the last sublist with 2 zeroes
[lst[x:x+3] for x in range(0, len(lst), 3)]
for example
lst =[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result should be:
[1,2,3][4,5,6][7,8,9][10,0,0]
With itertools.zip_longest, consuming the same iterator created off of the list, and fill in the missing values as 0 :
[[*i] for i in itertools.zip_longest(*[iter(lst)] * 3, fillvalue=0)]
Example:
In [1219]: lst =[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [1220]: [[*i] for i in itertools.zip_longest(*[iter(lst)] * 3, fillvalue=0)]
Out[1220]: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 0, 0]]
Without itertools:
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print([lst[x:x+3]+[0]*(x-len(lst)+3) for x in range(0, len(lst), 3)])
Prints:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 0, 0]]

Replacing items in a list in all possible permutations

Let's say I have a list or an array of the type:
mylist = [1, 2, 3, 4]
And I want to replace an item in this list. Normally I would use something like the following:
mylist[2] = 7
That works well. However, can someone explain how to create all possible permutations of mylist when replacing one or more items in mylist. For example, I want to create the following:
[7, 2, 3, 4]
[7, 7, 3, 4]
[7, 2, 7, 4]
[7, 7, 7, 4]
[7, 2, 3, 7]
...(and so on)
I know I can use itertools to generate all possible permutations, but how can I specify that I want to substitute an item in all possible locations in the list before generating the permutations? Here is how I tried to use itertools:
list(itertools.permutations([1,2,3,4,7], 4))
This doesn't work because it doesn't substitute 7 more than one time per permutation, and it also generates permutations that do not include the number 7.
Use itertools.combinations to find the indices to replace:
replace = 7
mylist = [1, 2, 3, 4]
for i in range(1, len(mylist) + 1):
for selected in itertools.combinations(range(len(mylist)), i):
res = mylist[:]
for n in selected:
res[n] = replace
print(res)
Output:
[7, 2, 3, 4]
[1, 7, 3, 4]
[1, 2, 7, 4]
[1, 2, 3, 7]
[7, 7, 3, 4]
[7, 2, 7, 4]
[7, 2, 3, 7]
[1, 7, 7, 4]
[1, 7, 3, 7]
[1, 2, 7, 7]
[7, 7, 7, 4]
[7, 7, 3, 7]
[7, 2, 7, 7]
[1, 7, 7, 7]
[7, 7, 7, 7]
You can create a function and just pass the list and value to that function and you get what you want :
import itertools
def replaced_it(list_1,value):
final_list = []
len_=len(list_1)
track_index = [k for k, j in enumerate(list_1)]
for i in range(len(track_index) + 1):
empty_list = [value]
replaced_one = list_1[:]
for ia in itertools.permutations(track_index, r=i):
if ia:
for i, j in zip(ia, empty_list * len(ia)):
replaced_one[i] = j
if replaced_one not in final_list:
final_list.append(replaced_one)
replaced_one = list_1[:]
return final_list
print(replaced_it([1,2,3,4],7))
output:
[[7, 2, 3, 4], [1, 7, 3, 4], [1, 2, 7, 4], [1, 2, 3, 7], [7, 7, 3, 4], [7, 2, 7, 4], [7, 2, 3, 7], [1, 7, 7, 4], [1, 7, 3, 7], [1, 2, 7, 7], [7, 7, 7, 4], [7, 7, 3, 7], [7, 2, 7, 7], [1, 7, 7, 7], [7, 7, 7, 7]]

How to perform multiple list slices at the same time?

Say I have a list:
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
and I have a list of indices:
indices = (2, 5, 7)
What I would like to be able to do is slice the list at all 3 indices at the same time. In other words, id like to perform the following lines of code in one line:
sub1 = L1[:2]
sub2 = L1[2:5]
sub3 = L1[5:7]
sub4 = L1[7:]
I could fairly easily write a function that does this, but I was wondering if it is possible to do this in one expression.
You could use operator.itemgetter with slice objects:
>>> from operator import itemgetter
>>> get = itemgetter(slice(0, 2), slice(2, 5), slice(5, 7), slice(7, None))
>>> values = range(1, 10)
>>> values
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> get(values)
([1, 2], [3, 4, 5], [6, 7], [8, 9])
Are you looking for a one liner? We could give you one, but I would just keep it simple:
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
indices = (2, 5, 7)
start = 0
sections = []
for end in indices:
sections.append(L1[start:end])
start = end
sections.append(L1[start:])
for part in sections:
print part
# [1, 2]
# [3, 4, 5]
# [6, 7]
# [8, 9]
Yes, it can be done in one expression, but it's not very readable:
seq = [1, 2, 3, 4, 5, 6, 7, 8, 9]
ind = (2, 5, 7)
print([seq[p:q] for p,q in zip((0,)+ind, ind+(len(seq),))])
output
[[1, 2], [3, 4, 5], [6, 7], [8, 9]]
If you really want to assign the sublists to separate names you can do it like this:
s1, s2, s3, s4 = [seq[p:q] for p,q in zip((0,)+ind, ind+(len(seq),))]
but it's probably better to just keep them in a list of lists.
Well, with the help of some built-in functionality you could do it in one line. The 'biggest' problem is transforming the list of end points to a list of (start,end) tuples or slice objects.
The code below extends the endpoints list with 0 on one end and None at the other end and uses zip() to combine pairs of them. Then use the operator.itemgetter() trick to extract the slices:
import operator
ends = [2,5,7]
values = range(20)
operator.itemgetter(*[slice(*z) for z in zip([0]+ends, ends+[None])])(values)
Output:
([0, 1], [2, 3, 4], [5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
List comprehensions are amazing.
Starting with your list:
>>> L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Pad your indices with None:
>>> indices = (None, 2, 5, 7, None)
Now comprehend away:
>>> [L1[start:stop] for start, stop in zip(indices[:-1], indices[1:])]
[[1, 2], [3, 4, 5], [6, 7], [8, 9]]

How do I use modular arithmetic on indices?

I am trying to create a function that, for each member of a list, returns the value of that member and the number of values either side of it. The only trick is that it has to "wrap around" when it is at the start or end of the list
For example:
a = [0,1,2,3,4,5,6,7,8,9]
myfunc(a,2) # 2 indicates 2 either side
[8,9,0,1,2]
[9,0,1,2,3]
[0,1,2,3,4]
...
...
[6,7,8,9,0]
[7,8,9,0,1]
I can work out how to do it from index 2 until 7:
def myfunc(vals, rnge):
for i in range(0+rnge, len(vals)-rnge):
print vals[i-rnge:i+rnge+1]
But I can't work out how to handle when it needs to wrap around.
you could try this (extend vals in both directions first). there may be something in collections that allows for this to be done more efficiently:
def myfunc(vals, rnge):
vals_ext = vals[-rnge:] + vals + vals[:rnge]
for i in range(len(vals)):
print( vals_ext[i:i+2*rnge+1] )
output:
[8, 9, 0, 1, 2]
[9, 0, 1, 2, 3]
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
[6, 7, 8, 9, 0]
[7, 8, 9, 0, 1]
How about:
def myfunc(vals, rnge):
valsX3 = vals*3;
for i in range(len(vals)):
print valsX3[i:i+2*rnge+1]
You could use something like this to avoid the duplication of arrays:
wrapping around slices in Python / numpy
Not sure how it does it internally though.
Here is an alternative approach you might also find useful:
def myfunc(alist, offset):
adeque = collections.deque(a)
adeque.rotate(offset)
for i in xrange(len(alist)):
print list(itertools.islice(adeque, 0, 2*offset+1))
adeque.rotate(-1)
a = [0,1,2,3,4,5,6,7,8,9]
myfunc(a,2) # 2 indicates 2 either side
It makes use of the deque collections object which has an efficient rotate function.
Generate your lists using the numpy element-wise modulo of an (unshifted) array.
You want 5 elements that wrap around 10, so use modulo 10. For example if the list starts with 8:
np.mod(np.arange(8,8+5,1),10) returns [8, 9, 0, 1, 2]
To get all 10 possible lists, evaluate list(np.mod(np.arange(s,s+5,1),10))for each start s=1,2,..,10
Or, in a single list comprehension without resorting to numpy,
[[t%10 for t in range(s,s+5)] for s in range(10)]
returns
[[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8],
[5, 6, 7, 8, 9],
[6, 7, 8, 9, 0],
[7, 8, 9, 0, 1],
[8, 9, 0, 1, 2],
[9, 0, 1, 2, 3]]
Here is the solution :
def defunc(vals, start, rnge):
n = len(vals)
new = []
for i in xrange((start - rnge), (start + rnge)+ 1):
new.append(vals[i%n])
return new
def myfunc(vals, rnge):
n = len(vals)
for i in range(n):
print defunc(vals,i,rnge)
I am not slicing the list to make the logic clearer.
I hope this works

Categories

Resources