Periodically replacing values in a list

Riley 09/25/2018. 8 answers, 1.903 views

Suppose I have the following list in Python:

my_list = [10] * 95

Given n, I want to replace any other m elements with zero in my list, while keeping the next n elements.

For example, if n = 3 and m = 2, I want my list to look like:

[10, 10, 10, 0, 0, 10, 10, 10 ,0, 0, ..., 10, 10, 10 , 0, 0]

If it can't be filled perfectly, as is the case with n = 4 and m = 2, then it's OK if my list looks like this:

[10, 10, 10, 10, 0, 0, ..., 10, 10, 10, 10, 0]

How should I try to solve this problem?

Alfe 09/25/2018.
my_list = [10] * 95
n = 3
m = 2
for i in range(m):
my_list[n+i::m+n] = [0] * len(my_list[n+i::m+n])

This just needs m assignments to do the job (and m probably is small).

If you really just have two possible values (e. g. 10 and 0), you can do it even simpler:

my_list = [ 10 if i % (n+m) < n else 0 for i in range(95) ]

But that iterates in Python over the whole range of 95, so probably is not very fast.

A bit more complex but probably more efficient (especially for huge lists and large values for n and m) would be this:

my_list = (([ 10 ] * n + [ 0 ] * m) * (95 // (n + m) + 1))[:95]

But it builds internally lots of lists, so its up to tests to find out whether this is efficient in your case. (Also memory consumption should be taken into account for large lists.)

If you can use numpy (a bit off the question, but since it's widespread):

my_list = (np.arange(95) % (n+m) < n) * 10

Aran-Fey 09/25/2018.

You could use itertools.cycle to create an endless sequence of [10, 10, 10, 0, 0] and then take the first 95 elements of that sequence with itertools.islice:

n = 3
m = 2

pattern = [10] * n + [0] * m
my_list = list(itertools.islice(itertools.cycle(pattern), 95))

Eric Duminil 09/25/2018.

Yet another possibility, this time with enumerate:

[x * (i % (n + m) < n) for i, x in enumerate(my_list)]

It uses the fact that False and True are equal to 0 and 1 in Python (see here).

As a bonus, it works fine even if the list isn't constant:

>>> n = 4
>>> m = 2
>>> my_list = range(20)
>>> [x * (i % (n+m) < n) for i, x in enumerate(my_list)]
[0, 1, 2, 3, 0, 0, 6, 7, 8, 9, 0, 0, 12, 13, 14, 15, 0, 0, 18, 19]

If the list contains strings, it replaces them with an empty string instead of 0:

>>> my_list = 'abcdefghijk'
>>> [x * (i % (n+m) < n) for i, x in enumerate(my_list)]
['a', 'b', 'c', 'd', '', '', 'g', 'h', 'i', 'j', '']

Shintlor 09/25/2018.

This worked for me:

list = [10] * 95

n = 4
m = 2

list = np.asarray(list)*amask

which outputs:

array([10., 10., 10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,  0., 10.,
10., 10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,  0., 10., 10.,
10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,  0., 10., 10., 10.,
10.,  0.,  0., 10., 10., 10., 10.,  0.,  0., 10., 10., 10., 10.,
0.,  0., 10., 10., 10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,
0., 10., 10., 10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,  0.,
10., 10., 10., 10.,  0.,  0., 10., 10., 10., 10.,  0.,  0., 10.,
10., 10., 10.,  0.])

The code takes n and m and constructs a mask of ones and zeros with a length matching your initial list using the np.tile function. Afterwards you just multiply the mask onto the list and get the zeros where you want them to be. It should also be flexibel to different lengths of the list and an (almost) arbitrary choice of n and m.

You can cast the array back to a list if you want.

klim 09/25/2018.

my_list = [10] * 95
n = 3
m = 2

for i in range(n, len(my_list)-1, n+m):
my_list[i:i+m] = [0]*m

print(my_list)

Edit

I found out that the above code changes the length of resulting list in some cases.

>>> a = [1,2,3]
>>> a[2:4] = [0] * 2
>>> a
[1, 2, 0, 0]

Thus, the length should be restored somehow.

my_list = [10] * 95
cp_list = list(my_list)
n = 3
m = 5

for i in range(n, len(my_list)-1, n+m):
cp_list[i:i+m] = [0]*m

cp_list = cp_list[:len(my_list)]
print(cp_list)

timgeb 09/25/2018.

numpy can do this pretty concisely, too!

a = np.array(my_list).reshape(-1, n + m)
a[:, n:] = 0
result = a.ravel().tolist()

Moonsik Park 09/25/2018.
[j for i in [[input_num] * n + [0] * m for x in range(int(num / (m + n)) + 1)][:num] for j in i]

Maybe?

Result

>>> num, input_num, m, n=95, 10, 2, 3
>>> [j for i in [[input_num] * n + [0] * m for x in range(int(num / (m + n)) + 1)][:num] for j in i]
[10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0, 10, 10, 10, 0 , 0, 10, 10, 10, 0, 0, 10, 10, 10, 0, 0]

pylang 10/02/2018.

Also in the itertools family, you can repeat a desired pattern:

Given

import itertools as it

m, n = 2, 3
p = n + 1    

Code

pattern = it.repeat([10] * n + [0] * m)
res = list(it.islice(it.chain.from_iterable(pattern), None, 95))
print(res)
# [10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 0, 0, ... 10, 10, 10, 10, 0, 0]

pattern = it.repeat([10] * p + [0] * m)
res = list(it.islice(it.chain.from_iterable(pattern), None, 95))
print(res)
# [10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 0, 0, ... 10, 10, 10, 10, 0]

Test

assert len(res) == 95

However, @Aran-Fey's itertools.cycle solution is cleaner as it does not require chaining.