10

I have a list with numbers: numbers = [1, 2, 3, 4].

I would like to have a list where they repeat n times like so (for n = 3):

[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4].

The problem is that I would like to only use itertools for this, since I am very constrained in performance.

I tried to use this expression:

list(itertools.chain.from_iterable(itertools.repeat(numbers, 3)))

But it gives me this kind of result:

[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

which is obviously not what I need.

Is there a way to do this with itertools only, without using sorting, loops and list comprehensions? The closest I could get is:

list(itertools.chain.from_iterable([itertools.repeat(i, 3) for i in numbers])),

but it also uses list comprehension, which I would like to avoid.

Sergey Zakharov
  • 1,493
  • 3
  • 21
  • 40
  • Is there a reason why you are avoiding list comprehensions? They are pretty fast and often as performant as `itertools`. – pylang Aug 24 '17 at 22:50
  • Same question but without restriction on itertools: [Repeating elements of a list n times](https://stackoverflow.com/questions/24225072/repeating-elements-of-a-list-n-times) – Georgy Oct 01 '19 at 21:10

3 Answers3

12

Firstly, using functions from itertools won't necessarily be faster than a list comprehension: you should benchmark.

Pure list comprehension approach:

>>> numbers = [1, 2, 3, 4]
>>> [y for x in numbers for y in (x,)*3]
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]

Using chain.from_iterable() with repeat() in a generator expression:

>>> from itertools import chain, repeat
>>> list(chain.from_iterable(repeat(n, 3) for n in numbers))
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]

Using chain.from_iterable() and zip():

>>> from itertools import chain
>>> list(chain.from_iterable(zip(*(numbers,)*3)))
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • 1
    I find this list comprehension a tad easier to understand, and is faster for larger repetition values: `[x for x in numbers for _ in range(3)]`. I suspect the lower iteration edge for the multiplication method is due to function call overhead of range. – Mattwmaster58 Mar 06 '22 at 09:58
  • @Mattwmaster58: You could make it slightly faster by replacing `range(3)` with `repeat(None, 3)`, but still the multiplication version wins. – Eugene Yarmash Mar 07 '22 at 08:16
6

Since you don't want to use list comprehension, following is a pure (+zip) itertools method to do it -

from itertools import chain, repeat

list(chain.from_iterable(zip(*repeat(numbers, 3))))
# [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
shad0w_wa1k3r
  • 12,955
  • 8
  • 67
  • 90
3

Think you were very close, just rewriting the comprehension to a generator:

n = 3
numbers = [1, 2, 3, 4]
list(itertools.chain.from_iterable((itertools.repeat(i, n) for i in numbers)))
Roelant
  • 4,508
  • 1
  • 32
  • 62