Una tupla anidada más rápida para listar y regresar

Estoy intentando realizar una tupla para enumerar y enumerar para convertir tuplas en secuencias anidadas de profundidad y forma desconocidas. Las llamadas se hacen cientos de miles de veces, por lo que estoy tratando de exprimir la mayor velocidad posible.

Cualquier ayuda es muy apreciada.

Esto es lo que tengo hasta ahora …

def listify(self, seq, was, toBe): temp = [] a = temp.append for g in seq: if type(g) == was: a(self.listify(g, was, toBe)) else: a(g) return toBe(temp) 

Y una llamada a la tupla para listar se vería así:

 self.listify((...), tuple, list) 

Edit: Sí, me perdí totalmente tanto el enumeración (de una implementación anterior) como el olvido de escribir la parte else.

Gracias por la ayuda de ambos. Probablemente iré con los coroutines.

Últimamente he estado trabajando mucho con los coroutines. La ventaja sería que reduce la sobrecarga de las llamadas de método. Enviar un nuevo valor a un coroutine es más rápido que llamar a una función. Si bien no puede hacer una rutina recursiva, lanzará un ValueError: generator already executing pero podría hacer un grupo de trabajadores de la rutina: necesita un trabajador para cada nivel del árbol. He hecho un código de prueba que funciona, pero todavía no he examinado los problemas de tiempo.

 def coroutine(func): """ A helper function decorator from Beazley""" def start(*args, **kwargs): g = func(*args, **kwargs) g.next() return g return start @coroutine def cotuple2list(): """This does the work""" result = None while True: (tup, co_pool) = (yield result) result = list(tup) # I don't like using append. So I am changing the data in place. for (i,x) in enumerate(result): # consider using "if hasattr(x,'__iter__')" if isinstance(x,tuple): result[i] = co_pool[0].send((x, co_pool[1:])) @coroutine def colist2tuple(): """This does the work""" result = None while True: (lst, co_pool) = (yield result) # I don't like using append so I am changing the data in place... for (i,x) in enumerate(lst): # consider using "if hasattr(x,'__iter__')" if isinstance(x,list): lst[i] = co_pool[0].send((x, co_pool[1:])) result = tuple(lst) 

Alternativa a la python pura de la publicación de HYRY:

 def list2tuple(a): return tuple((list2tuple(x) if isinstance(x, list) else x for x in a)) def tuple2list(a): return list((tuple2list(x) if isinstance(x, tuple) else x for x in a)) 

Haz un grupo de coroutines, esto es un truco de un grupo, pero funciona:

 # Make Coroutine Pools colist2tuple_pool = [colist2tuple() for i in xrange(20) ] cotuple2list_pool = [cotuple2list() for i in xrange(20) ] 

Ahora haz un poco de tiempo – comparando con:

 def make_test(m, n): # Test data function taken from HYRY's post! return [[range(m), make_test(m, n-1)] for i in range(n)] import timeit t = make_test(20, 8) %timeit list2tuple(t) %timeit colist2tuple_pool[0].send((t, colist2tuple_pool[1:])) 

Resultados: observe la ‘u’ junto a la ‘s’ en la segunda línea 🙂

 1 loops, best of 3: 1.32 s per loop 1 loops, best of 3: 4.05 us per loop 

Realmente parece demasiado rápido para creer. ¿Alguien sabe si el tiempo funciona con coroutines? Aquí está la manera antigua:

 tic = time.time() t1 = colist2tuple_pool[0].send((t, colist2tuple_pool[1:])) toc = time.time() print toc - tic 

resultado:

 0.000446081161499 

Las versiones más recientes de Ipython y% timit dan una advertencia:

La carrera más lenta tomó 9.04 veces más que la más rápida. Esto podría
significa que un resultado intermedio se está almacenando en caché 1000000 bucles, lo mejor de 3: 317 ns por bucle

Después de una investigación adicional, los generadores de Python no son mágicos y el envío sigue siendo una llamada a la función. La razón por la que mi método basado en el generador pareció ser más rápido es que estaba haciendo una operación in situ en las listas, lo que resultó en menos llamadas a funciones.

Escribí todo esto con muchos detalles adicionales en una charla reciente.

Espero que esto ayude a alguien que busca jugar con generadores.

Define dos funciones por separado:

 def list2tuple(a): return tuple((list2tuple(x) if isinstance(x, list) else x for x in a)) def tuple2list(a): return list((tuple2list(x) if isinstance(x, tuple) else x for x in a)) 

alguna prueba

 t = [1, 2, [3, 4], [5, [7, 8]], 9] t2 = list2tuple(t) t3 = tuple2list(t2) print t2 print t3 

Los resultados:

 (1, 2, (3, 4), (5, (7, 8)), 9) [1, 2, [3, 4], [5, [7, 8]], 9] 

Edición: para la versión rápida:

 def list2tuple2(a, tuple=tuple, type=type, list=list): return tuple([list2tuple2(x) if type(x)==list else x for x in a]) def tuple2list2(a, tuple=tuple, type=type): return [tuple2list2(x) if type(x)==tuple else x for x in a] 

Para comparar también incluyo la versión cython:

 %%cython def list2tuple3(a): return tuple([list2tuple3(x) if type(x)==list else x for x in a]) def tuple2list3(a): return [tuple2list3(x) if type(x)==tuple else x for x in a] 

Crear una lista anidada:

 def make_test(m, n): return [[range(m), make_test(m, n-1)] for i in range(n)] t = make_test(20, 8) t2 = list2tuple2(t) 

Luego compara la velocidad:

 %timeit listify(t, list, tuple) %timeit listify(t2, tuple, list) %timeit list2tuple(t) %timeit tuple2list(t2) %timeit list2tuple2(t) %timeit tuple2list2(t2) %timeit list2tuple3(t) %timeit tuple2list3(t2) 

El resultado es:

 listify 1 loops, best of 3: 828 ms per loop 1 loops, best of 3: 912 ms per loop list2tuple generator expression version 1 loops, best of 3: 1.49 s per loop 1 loops, best of 3: 1.67 s per loop list2tuple2 list comprehension with local cache 1 loops, best of 3: 623 ms per loop 1 loops, best of 3: 566 ms per loop list2tuple3 cython 1 loops, best of 3: 212 ms per loop 10 loops, best of 3: 232 ms per loop 

Dado que las respuestas anteriores no tratan con tuplas o listas dentro de los valores del diccionario, publico mi propio código:

 def tuple2list(data): if isinstance(data, dict): return { key: tuple2list(value) for key, value in data.items() } elif isinstance(data, (list, tuple)): return [ tuple2list(item) for item in data ] return data def list2tuple(data): if isinstance(data, dict): return { key: list2tuple(value) for key, value in data.items() } elif isinstance(data, (list, tuple)): return tuple( list2tuple(item) for item in data ) return data