Cortar una lista en Python sin generar una copia

Resuelto Chris asked hace 13 años • 5 respuestas

Tengo el siguiente problema.

Dada una lista de números enteros L, necesito generar todas las sublistas L[k:] for k in [0, len(L) - 1], sin generar copias .

¿Cómo logro esto en Python? ¿Con un objeto buffer de alguna manera?

Chris avatar Feb 27 '11 11:02 Chris
Aceptado

La respuesta corta

La división de listas no genera copias de los objetos de la lista; simplemente copia las referencias a ellos. Esa es la respuesta a la pregunta formulada.

la respuesta larga

Pruebas de valores mutables e inmutables.

Primero, probemos la afirmación básica. Podemos demostrar que incluso en el caso de objetos inmutables como los números enteros, solo se copia la referencia. Aquí hay tres objetos enteros diferentes, cada uno con el mismo valor:

>>> a = [1000 + 1, 1000 + 1, 1000 + 1]

Tienen el mismo valor, pero puedes ver que son tres objetos distintos porque tienen ids diferentes:

>>> map(id, a)
[140502922988976, 140502922988952, 140502922988928]

Cuando los cortas, las referencias siguen siendo las mismas. No se han creado nuevos objetos:

>>> b = a[1:3]
>>> map(id, b)
[140502922988952, 140502922988928]

El uso de diferentes objetos con el mismo valor muestra que el proceso de copia no se molesta en realizar prácticas : simplemente copia directamente las referencias.

Las pruebas con valores mutables dan el mismo resultado:

>>> a = [{0: 'zero', 1: 'one'}, ['foo', 'bar']]
>>> map(id, a)
[4380777000, 4380712040]
>>> map(id, a[1:]
... )
[4380712040]

Examinar la sobrecarga de memoria restante

Por supuesto, las propias referencias están copiadas. Cada uno cuesta 8 bytes en una máquina de 64 bits. Y cada lista tiene su propia sobrecarga de memoria de 72 bytes:

>>> for i in range(len(a)):
...     x = a[:i]
...     print('len: {}'.format(len(x)))
...     print('size: {}'.format(sys.getsizeof(x)))
... 
len: 0
size: 72
len: 1
size: 80
len: 2
size: 88

Como nos recuerda Joe Pinsonault , esos gastos generales se acumulan. Y los objetos enteros en sí mismos no son muy grandes: son tres veces más grandes que las referencias. Entonces, esto le ahorra algo de memoria en un sentido absoluto, pero asintóticamente, podría ser bueno poder tener múltiples listas que sean "vistas" en la misma memoria.

Ahorrar memoria mediante el uso de vistas

Desafortunadamente, Python no proporciona una manera fácil de producir objetos que sean "vistas" en listas. ¡O tal vez debería decir "afortunadamente"! Significa que no tienes que preocuparte por el origen de una porción; Los cambios en el original no afectarán el corte. En general, eso hace que el razonamiento sobre el comportamiento de un programa sea mucho más fácil.

Si realmente desea ahorrar memoria trabajando con vistas, considere usar numpymatrices. Cuando se divide una numpymatriz, la memoria se comparte entre el segmento y el original:

>>> a = numpy.arange(3)
>>> a
array([0, 1, 2])
>>> b = a[1:3]
>>> b
array([1, 2])

¿Qué sucede cuando modificamos ay volvemos a mirar b?

>>> a[2] = 1001
>>> b
array([   1, 1001])

Pero esto significa que debe asegurarse de que cuando modifica un objeto, no esté modificando otro sin darse cuenta. Ésa es la contrapartida del uso numpy: ¡menos trabajo para la computadora y más trabajo para el programador!

senderle avatar Feb 27 '2011 05:02 senderle

Dependiendo de lo que estés haciendo, es posible que puedas utilizar islice.

Dado que opera mediante iteración, no creará nuevas listas, sino que simplemente creará iteradores con yieldelementos de la lista original según lo solicitado para sus rangos.

Amber avatar Feb 27 '2011 04:02 Amber

Una alternativa simple a isliceeso no itera a través de elementos de la lista que no necesita:

def listslice(xs, *args):
    for i in range(len(xs))[slice(*args)]:
        yield xs[i]

Uso:

>>> xs = [0, 2, 4, 6, 8, 10]

>>> for x in listslice(xs, 2, 4):
...     print(x)
4
6
Mateen Ulhaq avatar Mar 06 '2020 11:03 Mateen Ulhaq

Generalmente, la división de listas es la mejor opción.

Aquí hay una comparación rápida de rendimiento:

from timeit import timeit
from itertools import islice

for size in (10**4, 10**5, 10**6):
    L = list(range(size))
    S = size // 2
    def sum_slice(): return sum(L[S:])
    def sum_islice(): return sum(islice(L, S, None))
    def sum_for(): return sum(L[i] for i in range(S, len(L)))

    assert sum_slice() == sum_islice()
    assert sum_slice() == sum_for()

    for method in (sum_slice, sum_islice, sum_for):
        print(f'Size={size}, method={method.__name__}, time={timeit(method, number=1000)} ms')

Resultados:

Size=10000,   method=sum_slice,  time=0.0298 ms
Size=10000,   method=sum_islice, time=0.0449 ms
Size=10000,   method=sum_for,    time=0.2500 ms
Size=100000,  method=sum_slice,  time=0.3262 ms
Size=100000,  method=sum_islice, time=0.4492 ms
Size=100000,  method=sum_for,    time=2.4849 ms
Size=1000000, method=sum_slice,  time=5.4092 ms
Size=1000000, method=sum_islice, time=5.1139 ms
Size=1000000, method=sum_for,    time=26.198 ms
gatopeich avatar Oct 15 '2020 15:10 gatopeich