¿Debo almacenar un cálculo en una variable si se usará mucho?

Si tengo una función para, por ejemplo, verificar si list1 es una lista secundaria de list2, qué opción es mejor:

Opción 1:

def isSublist1(list1,list2): "This fuction checks if list1 is a sublist of list2." for i in range(len(list2)): part=list2[i:] # part is a list with all the elements from i to the end of list2 if len(part)<len(list1): return False if list1==part[:len(list1)]: # if list1 is in the beginning of part return True return False 

O la opción 2:

 def isSublist2(list1,list2): "This fuction checks if list1 is a sublist of list." for i in range(len(list2)): if len(list2[i:])<len(list1): return False if list1==list2[i:][:len(list1)]: # if list1 is in the beginning of list2[i:] (part) return True return False 

En la opción 1, uso una variable llamada part para almacenar una sección de la list2 sin embargo, en la opción 2, la part no es una variable, la sección de la list2 se calcula cuando es necesario. ¿La opción 1 es más rápida? ¿Gasta más espacio?

Mi problema no es específicamente con esta función, sé que hay otras formas de implementar esta función.

Me gustaría saber cuál es la mejor práctica en un bucle: usar una variable para evitar calcular varias veces las mismas cosas o no. ¿Depende la respuesta de la complejidad y frecuencia del cálculo?

Almacenar localmente es mejor porque la búsqueda que hace Python es más rápida. Incluso vale la pena almacenar las funciones localmente.

Las preguntas sobre el rendimiento se responden mejor al cronometrarlas: puede medir esto utilizando timeit :

 import timeit def noTempFunc(): for _ in range(200): max([1,4,5,6]) def tempFunc(): m = max for _ in range(200): m([1,4,5,6]) print(timeit.timeit(noTempFunc, number=1000)) # 0.055301458000030834 print(timeit.timeit(tempFunc, number=1000)) # 0.049811941999905684 : 11% faster 

En este caso, el max() del contexto global solo se debe buscar una vez, y las búsquedas adicionales se realizan localmente, lo que es un 11% más rápido en base a estos números.

Se paga si usas tu m() local varias veces.


En su caso, sería sensato almacenar en caché len_list1 = len(list1) , ya que se usa mucho tiempo y no cambia.

Para hacerlo más legible, puedes considerar:

 def isSublist(list1, list2): """Checks if list2 is a sublist of list1""" len_part = len(list2) # reused inside the list comp, only "calulated" once return any( x == list2 for x in (list1[i:i+len_part] for i in range(len(list1)-len_part+1) )) print(isSublist([1,2,3,4],[1])) print(isSublist([1,2,3,4],[2,3])) print(isSublist([1,2,3,4],[1,2,4])) print(isSublist([1,2,3,4],[1,2,3,4])) 

Salida:

 True True False True 

Búsquedas:

  • any () && ¿Cómo funciona exactamente la función python any ()?
  • Dividir una lista de python en otras “listas secundarias”, es decir, listas más pequeñas

Su versión más rápida con longitud en caché también (basada en la respuesta de Scott Mermelstein ):

 def isSublist1a(list1,list2): # cached length as well l1 = len(list1) for i in range(len(list2)): part=list2[i:] if len(part) 

Entrega (2 ejecuciones):

 0.08652938600062043 # cached part 0.08017484299944044 # cached part + cached list1 lenght - slightly faster 0.15090413599955355 # non-cached version 0.8882850420004615 # cached part 0.8294611960000111 # cached part + cached list1 lenght - slightly faster 1.5524438030006422 # non-cached version 

Como complemento a la excelente respuesta de Patrick, probemos con su código real:

 >>> def isSublist1(list1,list2): ... for i in range(len(list2)): ... part=list2[i:] ... if len(part)>> def isSublist2(list1,list2): ... for i in range(len(list2)): ... if len(list2[i:])>> list1=list(range(10000)) >>> list2=list(range(4000,4020)) >>> import timeit >>> timeit.timeit('isSublist1(list2,list1)', globals=globals(),number=100) 6.420147094002459 >>> timeit.timeit('isSublist2(list2,list1)', globals=globals(),number=100) 12.455138996010646 

Entonces, en mi sistema, la cantidad de tiempo necesario con su variable temporal es casi la mitad del tiempo necesario sin ella.

No conozco la naturaleza de sus listas y sublistas; es posible que desee cambiar lo que list1 y list2 deben ser un buen reflection de cómo se usará su código, pero al menos por mi parte, parece que guardar esa variable temporal es una muy buena idea.

Por cierto, hagamos otro experimento interesante:

 >>> def isSublist3(list1,list2): ... ln = len(list1) ... for i in range(len(list2)): ... part=list2[i:] ... if len(part)>> timeit.timeit('isSublist1(list2,list1)',globals=globals(),number=100); timeit.timeit('isSublist3(list2,list1)',globals=globals(),number=100) 6.549526696035173 6.481004184985068 

Lo ejecuté un par de veces más para ver lo que obtendría:

6.470875242026523 6.463623657007702

6.151073662971612 5.787795798969455

5.685607994964812 5.655005165026523

6.439315696014091 6.372227535001002

Tenga en cuenta que cada vez, la longitud almacenada en caché toma menos tiempo que la longitud no almacenada en caché, aunque no obtiene casi la mejora de rendimiento que tenía al almacenar en caché la división.

Tenga en cuenta también que es importante no sacar demasiadas conclusiones de una sola vez en el tiempo. Hay muchas otras variables que afectan el tiempo, (en mi caso, bastante claro, algo sucedió para que se redujera de 6.4 a 5.7, ¡en medio de una sola prueba!), Así que si desea obtener una buena regla Puede contar con la prueba varias veces para asegurarse de obtener resultados consistentes.