Pergunta

I'm fairly new to Python, and think this should be a fairly common problem, but can't find a solution. I've already looked at this page and found it helpful for one item, but I'm struggling to extend the example to multiple items without using a 'for' loop. I'm running this bit of code for 250 walkers through Emcee, so I'm looking for the fastest way possible.

I have a list of numbers, a = [x,y,z] that I want to repeat b = [1,2,3] times (for example), so I end up with a list of lists:

[
 [x],
 [y,y],
 [z,z,z]
]

The 'for' loop I have is:

c = [ ]
for i in range (0,len(a)):
    c.append([a[i]]*b[i])

Which does exactly what I want, but means my code is excruciatingly slow. I've also tried naively turning a and b into arrays and doing [a]*b in the hopes that it would multiply element by element, but no joy.

Foi útil?

Solução

You can use zip and a list comprehension here:

>>> a = ['x','y','z']
>>> b = [1,2,3]
>>> [[x]*y for x,y in zip(a,b)]
[['x'], ['y', 'y'], ['z', 'z', 'z']]

or:

>>> [[x for _ in xrange(y)] for x,y in zip(a,b)]
[['x'], ['y', 'y'], ['z', 'z', 'z']]

zip will create the whole list in memory first, to get an iterator use itertools.izip

In case a contains mutable objects like lists or lists of lists, then you may have to use copy.deepcopy here because modifying one copy will change other copies as well.:

>>> from copy import deepcopy as dc
>>> a = [[1 ,4],[2, 5],[3, 6, 9]]
>>> f = [[dc(x) for _ in xrange(y)] for x,y in zip(a,b)]

#now all objects are unique
>>> [[id(z) for z in x] for x in f]
[[172880236], [172880268, 172880364], [172880332, 172880492, 172880428]]

timeit comparisons(ignoring imports):

>>> a = ['x','y','z']*10**4
>>> b = [100,200,300]*10**4

>>> %timeit [[x]*y for x,y in zip(a,b)]
1 loops, best of 3: 104 ms per loop

>>> %timeit [[x]*y for x,y in izip(a,b)]
1 loops, best of 3: 98.8 ms per loop

>>> %timeit map(lambda v: [v[0]]*v[1], zip(a,b))
1 loops, best of 3: 114 ms per loop

>>> %timeit map(list, map(repeat, a, b))
1 loops, best of 3: 192 ms per loop

>>> %timeit map(list, imap(repeat, a, b))
1 loops, best of 3: 211 ms per loop

>>> %timeit map(mul, [[x] for x in a], b)
1 loops, best of 3: 107 ms per loop

>>> %timeit [[x for _ in xrange(y)] for x,y in zip(a,b)]
1 loops, best of 3: 645 ms per loop

>>> %timeit [[x for _ in xrange(y)] for x,y in izip(a,b)]
1 loops, best of 3: 680 ms per loop

Outras dicas

The fastest way to do it is with map() and operator.mul():

>>> from operator import mul
>>> map(mul, [['x'], ['y'], ['z']], [1, 2, 3])
[['x'], ['y', 'y'], ['z', 'z', 'z']]
>>> from itertools import repeat
>>> from itertools import starmap
>>> a = ['x','y','z']
>>> b = [1,2,3]
>>> starmap(repeat,zip(a,b))

starmap returns an iterable which contains values equal to the result of calling repeat with arguments equal to the values contained in a tuple, in this case for example ('x',1).

>>> for p in starmap(repeat,zip(a,b)):
    print(list(p))


['x']
['y', 'y']
['z', 'z', 'z']

@kirelagin suggested a version without for loops, here's one that also doesn't have lambdas (Keep in mind the solution by @AshwiniChaudhary is most readable)

>>> from itertools import repeat
>>> a = ['x','y','z']
>>> b = [1,2,3]
>>> map(list, map(repeat, a, b))
[['x'], ['y', 'y'], ['z', 'z', 'z']]

>>> map(repeat, a, b)
[repeat('x', 1), repeat('y', 2), repeat('z', 3)]

creates a list of repeat objects (use imap on Python 2.x if you want a lazy iterator instead of a list) which don't take up any extra space in memory, these are great if you just want to iterate over the items instead of store them)

Here is a version without for loops if you don't like them for some reason:

map(lambda v: [v[0]]*v[1], zip(a,b))

I should also warn you that this version is slightly slower than a list comprehension:

$ a = ['hi']*100
$ b = [20]*100

$ %timeit map(lambda v: [v[0]]*v[1], zip(a,b))
10000 loops, best of 3: 101 us per loop

%timeit [[x]*y for x,y in zip(a,b)]
10000 loops, best of 3: 74.1 us per loop

I'd also recommend using itertools.izip instead of zip if you are on Python 2.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top