24

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?

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
Riley
  • 933
  • 10
  • 26
  • Will the input list always be filled with one and the same value (like 10 in your example)? In other words, do you want to *create* a new list like `[10, 10, 10, 0, 0, ...]` or do you want to overwrite every nth value in an *existing* list? – Aran-Fey Sep 25 '18 at 08:13
  • @Aran-Fey 10 is indeed just an example. But the input list will stay constant. So it can be any number, but it will always be the same one. It doesn't really matter to me if the list gets overwritten or if a new one is created. – Riley Sep 25 '18 at 08:13
  • Is the length of the list going to be the same? – alec_djinn Sep 25 '18 at 08:17
  • @alec_djinn, yes it will. It would be nice to see a more general method, but if it works for a specific length, that's also ok. – Riley Sep 25 '18 at 08:22
  • 7
    How is this question not worth closing? The OP basically asks for an answer without any visible effort to have tried something – Denny Sep 25 '18 at 11:15
  • 1
    @Denny, in the beginning, I was also very confused about this system on this site. 3 criteria for a good question: 1) (visible) effort. 2) Useful and 3) Clear. And more than not, the two last criteria score higher on the priority list for most users. If you browse this and other stack exchange sites, you'll find countless examples of questions that don't show the visible effort, but are just useful (and maybe more importantly) clear for other users. – Riley Sep 25 '18 at 11:22
  • The (implicit) rule is, you should make "interest + usefulness + effort" sufficient. If the interest and the usefulness is low, put in some effort. There are some (old!) questions on the site with low effort, but they are useful. – user202729 Sep 25 '18 at 13:07
  • 4
    Personally, I think the question is very specific, and it's quite unlikely that somebody will come across it and found this question through a Google search (without knowing about the question in advance, of course); besides, it's pretty basic. – user202729 Sep 25 '18 at 13:08
  • @user202729, why is this not useful then? I think it came up in some google searches already, as this has already +400 views. And for the record: I did put in some effort, I just wanted to see some alternative methods. – Riley Sep 25 '18 at 13:08
  • 4
    It's easy -> it gets many answers -> the "hotness formula" think (_no comment on the formula_) it's hot -> it's displayed on the HNQ list -> it attracts more viewers, mostly not come from [so]. Besides it's not possible to understand the question just by reading the title (because it's specific!) so the viewer is forced to click on the link, that generates view. – user202729 Sep 25 '18 at 15:17

8 Answers8

28

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))
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • Why use itertools when you can use list expression? –  Sep 25 '18 at 08:28
  • 6
    @MoonsikPark Not sure what you mean. If you're referring to your own solution: Because I think mine is more readable than yours. – Aran-Fey Sep 25 '18 at 08:29
27
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
Alfe
  • 56,346
  • 20
  • 107
  • 159
  • Could you post your 3 different solutions as 3 separate answers, please? I want to upvote the 2nd one, but the others... not so much. – Aran-Fey Sep 25 '18 at 08:43
  • 3
    @Aran-Fey That would be overkill, I guess. Feel free to not upvote in this case, even if an upvote just states that the answer provided _any_ help (not was perfect). And consider that the second one is probably the slowest of the four (only relevant for large numbers of course). – Alfe Sep 25 '18 at 10:47
7

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', '']
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
6

This worked for me:

list = [10] * 95

n = 4
m = 2

amask = np.tile(np.concatenate((np.ones(n),np.zeros(m))),int((len(list)+1)/(n+m)))[:len(list)]

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.

Shintlor
  • 742
  • 6
  • 19
  • 1
    `numpy`? For real? – Alfe Sep 25 '18 at 08:19
  • 1
    @Alfe: What's the problem with numpy? It's widespread and it's specifically designed for lists/arrays. – Eric Duminil Sep 25 '18 at 09:51
  • @EricDuminil Just overkill for the job, that's all. It's like asking for a `print()` and getting a `f=open('/dev/stdout', 'w'); f.write("foo"); f.flush(); f.close()`. Works, yeah. But adds _unnecessary_ complexity. – Alfe Sep 25 '18 at 10:41
  • 1
    Btw, in `numpy`, I would have done it as `(np.arange(95) % 5 < 3) * 10`. – Alfe Sep 25 '18 at 10:44
  • @Alfe: Only if the original array is constant. And the whole Python environment is becoming so powerful that it could be considered overkill for all the basic tasks. – Eric Duminil Sep 25 '18 at 11:54
  • 3
    @Alfe Indeed, your accepted version is much more elegant -> +1. But nevertheless my suggestion also gets the job done and sometimes that's all you need. Thanks for pointing it out though. – Shintlor Sep 25 '18 at 12:19
5

How about this?

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)
klim
  • 1,179
  • 8
  • 11
  • 1
    Nice approach. But only if `len(my_list)` is smaller than `m`, this is more efficient than my solution. – Alfe Sep 25 '18 at 08:24
  • 1
    There is a downvote on this answer but no critic comment. Downvoter, please comment on what you didn't like. – Alfe Sep 25 '18 at 08:29
  • 2
    For deleting the last elements of a list, best use `del cp_list[n:]`. – Alfe Sep 25 '18 at 14:58
3
[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]
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • @Julien No, OP said ' it can be any number, but it will always be the same one' so it will be full of any same number. –  Sep 25 '18 at 08:20
  • Oops. Indeed it is. Edited. –  Sep 25 '18 at 08:44
3

numpy can do this pretty concisely, too!

a = np.array(my_list).reshape(-1, n + m)
a[:, n:] = 0
result = a.ravel().tolist()
timgeb
  • 76,762
  • 20
  • 123
  • 145
  • 2
    It doesn't seem to work with `n = 4` and `m = 2`. It raises `ValueError: cannot reshape array of size 95 into shape (6)`. – Eric Duminil Sep 25 '18 at 09:57
  • @EricDuminil shouldn't an error be raised if the length of the original list is not a multiple of the length of the subsequences to be repeated? – timgeb Sep 25 '18 at 10:02
  • 1
    Please take a look at the second half of the question. This use case is specifically mentioned by OP. – Eric Duminil Sep 25 '18 at 10:03
  • 3
    @EricDuminil you are right. This answer does not work in the latter case. – timgeb Sep 25 '18 at 10:05
0

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.

pylang
  • 40,867
  • 14
  • 129
  • 121