python - example - Reemplace la lista de la lista con la lista "condensada" de la lista mientras mantiene el orden
product() python (7)
Tengo una lista de la lista como en el código que adjunto. Quiero vincular cada sub lista si hay valores comunes. Entonces quiero reemplazar la lista de la lista con una lista condensada de la lista. Ejemplos: si tengo una lista [[1,2,3],[3,4]]
quiero [1,2,3,4]
. Si tengo [[4,3],[1,2,3]]
quiero [4,3,1,2]
. Si tengo [[1,2,3],[a,b],[3,4],[b,c]]
quiero [[1,2,3,4],[a,b,c]]
o [[a,b,c],[1,2,3,4]]
No me importa cuál.
Ya casi estoy allí...
Mi problema es cuando tengo un caso como [[1,2,3],[10,5],[3,8,5]]
Quiero [1,2,3,10,5,8]
pero con mi código actual que obtengo [1,2,3,8,10,5]
Aquí está mi código:
import itertools
a = [1,2,3]
b = [3,4]
i = [21,22]
c = [88,7,8]
e = [5,4]
d = [3, 50]
f = [8,9]
g= [9,10]
h = [20,21]
lst = [a,b,c,i,e,d,f,g,h,a,c,i]*1000
#I have a lot of list but not very many different lists
def any_overlap(a, b):
sb = set(b)
return any(itertools.imap(sb.__contains__, a))
def find_uniq(lst):
'''''' return the uniq parts of lst''''''
seen = set()
seen_add = seen.add
return [ x for x in lst if x not in seen and not seen_add(x)]
def overlap_inlist(o_lst, lstoflst):
''''''
Search for overlap, using "any_overlap", of a list( o_lst) in a list of lists (lstoflst).
If there is overlap add the uniq part of the found list to the search list, and keep
track of where that list was found
''''''
used_lst =[ ]
n_lst =[ ]
for lst_num, each_lst in enumerate(lstoflst):
if any_overlap(o_lst,each_lst):
n_lst.extend(each_lst)
used_lst.append(lst_num)
n_lst= find_uniq(n_lst)
return n_lst, used_lst
def comb_list(lst):
''''''
For each list in a list of list find all the overlaps using ''ovelap_inlist''.
Update the list each time to delete the found lists. Return the final combined list.
''''''
for updated_lst in lst:
n_lst, used_lst = overlap_inlist(updated_lst,lst)
lst[:] = [x for i,x in enumerate(lst) if i not in used_lst]
lst.insert(0,n_lst)
return lst
comb_lst = comb_list(lst)
print comb_lst
La salida de este script es:
[[88, 7, 8, 9, 10], [1, 2, 3, 4, 50, 5], [21, 22, 20]]
Lo quiero así que las llaves están en el orden original como:
[[88, 7, 8, 9, 10], [1, 2, 3, 4, 5, 50,], [21, 22, 20]]
El 5 y el 50 se cambian en nuevo lst [2]
Soy algo nuevo en python. Agradecería cualquier solución al problema o comentarios sobre mi código actual. No soy un experto en informática, me imagino que puede haber algún tipo de algoritmo que haga esto rápidamente (¿tal vez de la teoría de conjuntos?). Si existe un algoritmo de este tipo, indíqueme la dirección correcta.
Puede que sea más complicado de lo que es ... ¡Gracias!
Aquí está mi enfoque. Utiliza el concepto de un "conjunto disjunto" para identificar primero qué sublistas se superponen entre sí, luego las une, eliminando los duplicados.
from collections import OrderedDict
def disjoint_set_find(djs, node): # disjoint set find, with path compression
if node not in djs: # base case, node is a root of a set
return node
djs[node] = disjoint_set_find(djs, djs[node]) # recurse, caching results
return djs[node]
def disjoint_set_union(djs, first, second):
first = disjoint_set_find(djs, first) # find root of first set
second = disjoint_set_find(djs, second) # and of second set
if first < second: # make smaller root the root of the new combined set
djs[second] = first
elif second < first:
djs[first] = second
# deliberately ignore the case where first == second (same set)
def condenseBK(*master_list):
values = OrderedDict() # maps values to the first sublist containing them
overlaps = {} # maps sublist indexes to each other to form a disjoint set
for i, sublist in enumerate(master_list):
for v in sublist:
if v not in values: # no overlap, so just store value
values[v] = i
else: # overlap detected, do a disjoint set union
disjoint_set_union(overlaps, values[v], i)
output = [] # results
output_map = {} # map from original indexes to output indexes
for v, i, in values.items(): # iterate over values in order
root = disjoint_set_find(overlaps, i)
if root not in output_map:
output_map[i] = len(output)
output.append([]) # create new output sublists as necessary
output[output_map[root]].append(v)
return output
Salida de muestra:
>>> a = [1,2,3]
>>> b = [3,4]
>>> c = [88,7,8]
>>> d = [3, 50]
>>> e = [5,4]
>>> f = [8,9]
>>> g = [9,10]
>>> h = [20,21]
>>> i = [21,22]
>>> lst = [a,b,c,i,e,d,f,g,h,a,c,i]*1000
>>> condenseBK(*lst)
[[1, 2, 3, 4, 5, 50], [88, 7, 8, 9, 10], [21, 22, 20]]
Una explicación del algoritmo:
Por solicitud, aquí hay una explicación de cómo funciona mi código.
Las dos primeras funciones implementan las operaciones de find
y union
de un conjunto desunido . La estructura de datos se implementa con un diccionario que asigna nodos a sus nodos principales. Cualquier nodo que no sea una clave del diccionario es la root
de un conjunto. La operación de find
devuelve el nodo raíz del conjunto que contiene un node
determinado. Para ayudar un poco al rendimiento, he implementado la "compresión de ruta", que reduce el número de pasos recursivos necesarios con el tiempo. La operación de union
combina los conjuntos que contienen sus argumentos first
y second
.
La función de condense
principal tiene dos partes. Primero, configura un par de estructuras de datos, luego las usa para construir la salida.
values
es un OrderedDictionary que se asigna de cada valor al índice de la primera lista secundaria en la que se encuentra. El orden en que se agrega cada valor se utiliza para producir la salida en el orden correcto.
overlaps
es el diccionario utilizado como para el conjunto disjoint. Sus nodos son los índices enteros de sublistas superpuestas.
Los primeros bucles llenan esas dos estructuras de datos. Recorren los sublistas y sus contenidos. Si un valor no se ha visto anteriormente, se agrega al diccionario de values
. Si se ha visto, la sublista actual está superponiendo una sublista anterior que contiene ese valor.
Para resolver la superposición, el código hace una unión de los conjuntos disjuntos que contienen las dos sublistas.
La salida está incorporada en la lista de output
. Debido a que es probable que haya menos sublistas de salida de las que había en la entrada, necesitamos un diccionario adicional para asignar entre los índices antiguos (de la entrada) a los nuevos índices que se aplican a la lista de salida.
Para completar la lista de resultados, iteramos sobre los valores, lo que sucede en el orden en que se agregaron gracias al uso de la clase OrderedDict. Usando el conjunto disjoint, puede combinar las listas superpuestas correctamente.
Este algoritmo tiene un rendimiento muy bueno cuando hay muchas listas para procesar que no se superponen inmediatamente. Por ejemplo, este conjunto de 200 listas de tres elementos en última instancia, todas se superponen, pero solo comienza a ver las superposiciones después de que se hayan procesado las primeras 100:
lst2 = [list(range(4*i, 4*(i+1)-1)) for i in range(100)] + /
[list(range(4*i+2, 4*(i+1)+1)) for i in range(100)]
Aquí hay un enfoque de fuerza bruta (podría ser más fácil de entender):
from itertools import chain
def condense(*lists):
# remember original positions
positions = {}
for pos, item in enumerate(chain(*lists)):
if item not in positions:
positions[item] = pos
# condense disregarding order
sets = condense_sets(map(set, lists))
# restore order
result = [sorted(s, key=positions.get) for s in sets]
return result if len(result) != 1 else result[0]
def condense_sets(sets):
result = []
for candidate in sets:
for current in result:
if candidate & current: # found overlap
current |= candidate # combine (merge sets)
# new items from candidate may create an overlap
# between current set and the remaining result sets
result = condense_sets(result) # merge such sets
break
else: # no common elements found (or result is empty)
result.append(candidate)
return result
Ejemplo
>>> condense([1,2,3], [10,5], [3,8,5])
[1, 2, 3, 10, 5, 8]
>>> a = [1,2,3]
>>> b = [3,4]
>>> i = [21,22]
>>> c = [88,7,8]
>>> e = [5,4]
>>> d = [3, 50]
>>> f = [8,9]
>>> g= [9,10]
>>> h = [20,21]
>>> condense(*[a,b,c,i,e,d,f,g,h,a,c,i]*1000)
[[1, 2, 3, 4, 5, 50], [88, 7, 8, 9, 10], [21, 22, 20]]
>>> condense([1], [2, 3, 2])
[[1], [2, 3]]
Comparación de rendimiento
condense_*()
funciones de condense_*()
provienen de las respuestas a esta pregunta. lst_OP
lista de entrada de la pregunta (tamaño diferente), lst_BK
- la lista de prueba de la respuesta de @ Blckknght (tamaño diferente). Vea la fuente .
Las mediciones muestran que las soluciones basadas en conceptos de "conjuntos disjuntos" y "componentes conectados de gráficos no dirigidos" se comportan de manera similar en ambos tipos de entrada.
name time ratio comment
condense_hynekcer 5.79 msec 1.00 lst_OP
condense_hynekcer2 7.4 msec 1.28 lst_OP
condense_pillmuncher2 11.5 msec 1.99 lst_OP
condense_blckknght 16.8 msec 2.91 lst_OP
condense_jfs 26 msec 4.49 lst_OP
condense_BK 30.5 msec 5.28 lst_OP
condense_blckknght2 30.9 msec 5.34 lst_OP
condense_blckknght3 32.5 msec 5.62 lst_OP
name time ratio comment
condense_blckknght 964 usec 1.00 lst_BK
condense_blckknght2 1.41 msec 1.47 lst_BK
condense_blckknght3 1.46 msec 1.51 lst_BK
condense_hynekcer2 1.95 msec 2.03 lst_BK
condense_pillmuncher2 2.1 msec 2.18 lst_BK
condense_hynekcer 2.12 msec 2.20 lst_BK
condense_BK 3.39 msec 3.52 lst_BK
condense_jfs 544 msec 563.66 lst_BK
name time ratio comment
condense_hynekcer 6.86 msec 1.00 lst_rnd
condense_jfs 16.8 msec 2.44 lst_rnd
condense_blckknght 28.6 msec 4.16 lst_rnd
condense_blckknght2 56.1 msec 8.18 lst_rnd
condense_blckknght3 56.3 msec 8.21 lst_rnd
condense_BK 70.2 msec 10.23 lst_rnd
condense_pillmuncher2 324 msec 47.22 lst_rnd
condense_hynekcer2 334 msec 48.61 lst_rnd
Para reproducir los resultados, clone gist y ejecute measure-performance-condense-lists.py
Esta solución utiliza solo un diccionario ordenado.
deepcopy () es necesario si uno quiere que la copia original permanezca sin cambios.
from collections import OrderedDict
from copy import deepcopy
def treat(passed_list):
L = deepcopy(passed_list)
dic = OrderedDict()
for subl in L:
for x in subl:
if x not in dic:
dic[x] = subl
print ''dic at start''
print ''/n''.join(''%-3s : %s'' % (a,dic[a])
for a in dic) + ''/n''
for sublist in L:
short = []
short.extend(el for el in sublist
if el not in short)
seen = []
for k,val in dic.iteritems():
if val is sublist:
break
if k in short:
if val not in seen:
seen.append(val)
sumseen = []
for elseen in seen:
for y in elseen:
sumseen.append(y)
dic[y] = sumseen
if seen:
for el in sublist:
if el not in sumseen:
sumseen.append(el)
dic[el] = sumseen
sublist[:] = short
cumul = []
cumul.extend(lu for lu in dic.itervalues()
if lu not in cumul)
return cumul
plus = [[1,2,3,2,1],[10,5,5,5,10],
[8,5,3,3,5],[45,50,12,45,40,12]]
lst = [[1,2,3], [10,5], [3,8,5]]
for one_list in (plus,lst):
print ''one_list before == %r/n'' % one_list
print ''treat(one_list) == %r/n'' % treat(one_list)
print ''one_list after == %r/n'' % one_list
print ''====================================''
resultado
one_list before == [[1, 2, 3, 2, 1], [10, 5, 5, 5, 10], [8, 5, 3, 3, 5], [45, 50, 12, 45, 40, 12]]
dic at start
1 : [1, 2, 3, 2, 1]
2 : [1, 2, 3, 2, 1]
3 : [1, 2, 3, 2, 1]
10 : [10, 5, 5, 5, 10]
5 : [10, 5, 5, 5, 10]
8 : [8, 5, 3, 3, 5]
45 : [45, 50, 12, 45, 40, 12]
50 : [45, 50, 12, 45, 40, 12]
12 : [45, 50, 12, 45, 40, 12]
40 : [45, 50, 12, 45, 40, 12]
treat(one_list) == [[1, 2, 3, 10, 5, 8], [45, 50, 12, 40]]
one_list after == [[1, 2, 3, 2, 1], [10, 5, 5, 5, 10], [8, 5, 3, 3, 5], [45, 50, 12, 45, 40, 12]]
====================================
one_list before == [[1, 2, 3], [10, 5], [3, 8, 5]]
dic at start
1 : [1, 2, 3]
2 : [1, 2, 3]
3 : [1, 2, 3]
10 : [10, 5]
5 : [10, 5]
8 : [3, 8, 5]
treat(one_list) == [[1, 2, 3, 10, 5, 8]]
one_list after == [[1, 2, 3], [10, 5], [3, 8, 5]]
====================================
Esta solución tiene un inconveniente: es 2 a 3 veces más lenta que la solución de JF Sebastian.
Estoy seguro de que hay una forma más limpia de hacerlo, pero empecé por un cierto camino e hice lo que tenía que hacer para que funcionara sin ninguna refactorización.
lookup = {}
out = []
index = 0
for grp in lst:
keys = [lookup.get(num, None) for num in grp]
keys = [key for key in keys if key is not None]
if len(keys):
if len(set(keys)) != 1:
for num in grp:
out[keys[0]].append(num)
seen = set()
keys = [key for key in keys if key not in seen and not seen.add(key)]
for key in keys[1:]:
out[keys[0]].extend(out[key])
del out[key]
seen = set()
out[keys[0]] = [item for item in out[keys[0]] if item not in seen and not seen.add(item)]
else:
for num in grp:
lookup[num] = keys[0]
out[keys[0]].extend(grp)
seen = set()
out[keys[0]] = [item for item in out[keys[0]] if item not in seen and not seen.add(item)]
else:
out.append(grp)
for num in grp:
lookup[num] = index
index += 1
print out
Gracias a @Steven por la técnica de reducción de lista con el set.
SALIDA
[[1, 2, 3, 4, 5, 50], [88, 7, 8, 9, 10], [21, 22, 20]]
Intenté escribir una solución rápida y legible . Nunca sé que es mucho más lento que otras soluciones, pero a veces puede ser mucho más rápido porque además está optimizado para sublistas más largas o para muchos sublistas que son subconjuntos de cualquier grupo existente. (Esto está motivado por el texto de la pregunta "Tengo mucha lista pero no muchas listas diferentes"). El código utiliza menos memoria solo para datos condensados que pueden ser mucho menos que los datos originales. Puede funcionar, por ejemplo, con un generador que recopila datos de un proceso en tiempo real. La estimación de complejidad es O (n log n). Creo que ningún algoritmo que usa ordenación puede ser de complejidad lineal.
def condense(lists):
groups = {} # items divided into groups {id(the_set): the_set,...}
members = {} # mapping from item to group
positions = {} # mapping from item to sequential ordering
iposition = 0 # counter for positions
for sublist in lists:
if not sublist or members.get(sublist[0], set()).issuperset(sublist):
continue # speed-up condition if all is from one group
common = set([x for x in sublist if x in members])
if common: # any common group can be a destination for merge
dst_group = members[common.pop()]
common = common - dst_group # are more groups common for sublist?
while common:
grp = members[common.pop()]
if len(grp) > len(dst_group): # merge shorter into longer grp
grp, dst_group = dst_group, grp
dst_group.update(grp)
for item in grp:
members[item] = dst_group
del groups[id(grp)]
common = common - dst_group
else: # or create a new group if nothing common
dst_group = set()
groups[id(dst_group)] = dst_group
newitems = []
for item in sublist: # merge also new items
if item not in positions:
positions[item] = iposition
iposition += 1
newitems.append(item)
members[item] = dst_group
dst_group.update(newitems)
return [sorted(x, key=positions.get) for x in groups.values()]
Es más rápido que pillmuncher2 para subslists más largas que aproximadamente 8 elementos porque puede trabajar en más elementos juntos. También es muy rápido para listas con muchas sublistas similares o muchas sublistas que son subconjuntos de cualquier grupo existente. Es más rápido en un 25% sobre pillmuncher2 para lst_OP, pero más lento en un 15% para lst_BK.
Un ejemplo de datos de prueba con sublistas largos es [list(range(30)) + [-i] for i in range(100)]
.
Intencionalmente escribí "common = common - dst_group" en lugar de usar el operador set -=
o "set.difference_update", porque la actualización en el lugar no es efectiva si el conjunto del lado derecho es mucho más grande que el lado izquierdo.
Solución modificada de la pastilla para facilitar la lectura. La modificación es un poco más lenta que la original debido a la sustitución de un generador por list.append. Probablemente la solución rápida más legible.
# Modified pillmuncher''s solution
from collections import defaultdict
def condense(lists):
neighbors = defaultdict(set) # mapping from items to sublists
positions = {} # mapping from items to sequential ordering
position = 0
for each in lists:
for item in each:
neighbors[item].update(each)
if item not in positions:
positions[item] = position
position += 1
seen = set()
see = seen.add
for node in neighbors:
if node not in seen:
unseen = set([node]) # this is a "todo" set
next_unseen = unseen.pop # method alias, not called now
group = [] # collects the output
while unseen:
node = next_unseen()
see(node)
unseen |= neighbors[node] - seen
group.append(node)
yield sorted(group, key=positions.get)
Su problema es esencialmente una gráfica teórica, el problema de los componentes conectados, con un requisito adicional con respecto al orden de los elementos de cada componente.
En su programa, el conjunto de todas las listas forma un gráfico no dirigido, donde cada lista es un nodo en el gráfico. Decimos que dos listas están conectadas directamente si tienen elementos comunes y conectadas indirectamente, si existe una tercera lista a la que ambas están conectadas, directa o indirectamente. Dados, por ejemplo, tres listas [a, b], [b, c] y [c, d], entonces [a, b] y [b, c] están conectados directamente, así como [b, c] y [c, d], pero [a, b] y [c, d] están conectados indirectamente, ya que si bien no comparten elementos comunes, ambos comparten elementos con la misma lista [b, c].
Un grupo de nodos es un componente conectado si todos los nodos del grupo están conectados (directa o indirectamente) y ningún otro nodo en el gráfico está conectado a ningún nodo del grupo.
Existe un algoritmo de tiempo lineal bastante simple que genera todos los componentes conectados en un gráfico no dirigido. Usando eso, podemos definir una función que genere todas las listas de listas disjuntas condensadas, mientras mantenemos el orden de sus elementos:
from itertools import imap, combinations_with_replacement
from collections import defaultdict
def connected_components(edges):
neighbors = defaultdict(set)
for a, b in edges:
neighbors[a].add(b)
neighbors[b].add(a)
seen = set()
def component(node, neighbors=neighbors, seen=seen, see=seen.add):
unseen = set([node])
next_unseen = unseen.pop
while unseen:
node = next_unseen()
see(node)
unseen |= neighbors[node] - seen
yield node
for node in neighbors:
if node not in seen:
yield component(node)
def condense(lists):
tuples = combinations_with_replacement(enumerate(imap(tuple, lists)), 2)
overlapping = ((a, b) for a, b in tuples
if not set(a[1]).isdisjoint(b[1]))
seen = set()
see = seen.add
for component in connected_components(overlapping):
yield [item for each in sorted(component)
for item in each[1]
if not (item in seen or see(item))]
print list(condense([[1, 2, 3], [10, 5], [3, 8, 5], [9]]))
print list(condense([[1, 2, 3], [5, 6], [3, 4], [6, 7]]))
Resultado:
[[1, 2, 3, 10, 5, 8], [9]]
[[5, 6, 7], [1, 2, 3, 4]]
La complejidad de tiempo de condense()
es cuadrática, ya que cada lista debe compararse con todas las demás para descubrir si tienen elementos comunes. Por lo tanto, el rendimiento es horrible. ¿Podemos mejorarlo de alguna manera? Sí.
Dos listas se conectan directamente si tienen elementos comunes. Podemos cambiar esta relación: dos elementos se conectan directamente si pertenecen a la misma lista, y se conectan indirectamente si existe un tercer elemento que está conectado (directa o indirectamente) a ambos. Dados, por ejemplo, dos listas [a, b] y [b, c], entonces a y b están conectados directamente, así como b y c, y por lo tanto a y c están conectados indirectamente. Si también adaptamos la idea de JFSebastians de almacenar la posición de la primera aparición de cada elemento, podemos implementarlo de la siguiente manera:
def condense(lists):
neighbors = defaultdict(set)
positions = {}
position = 0
for each in lists:
for item in each:
neighbors[item].update(each)
if item not in positions:
positions[item] = position
position += 1
seen = set()
def component(node, neighbors=neighbors, seen=seen, see=seen.add):
unseen = set([node])
next_unseen = unseen.pop
while unseen:
node = next_unseen()
see(node)
unseen |= neighbors[node] - seen
yield node
for node in neighbors:
if node not in seen:
yield sorted(component(node), key=positions.get)
Todavía utiliza el algoritmo de componentes conectados, pero esta vez vemos los elementos como conectados, no como listas. Los resultados son los mismos que antes, pero como la complejidad del tiempo ahora es lineal, se ejecuta mucho más rápido.
class List(list): pass
rv = dict()
def condense_step():
"""find and merge one overlapping pair in rv"""
for i, iv in rv.items():
for j, jv in rv.items():
if i != j and i.intersection(j):
m = i.union(j)
del rv[i]
del rv[j]
rv.setdefault(m, [])
rv[m] += iv
rv[m] += jv
return True
def unique(l):
"""flatten list-of-lists excluding duplicates"""
seen = set()
for i in sum(l, []):
if i not in seen:
seen.add(i)
yield i
def condense(inp):
rv.clear()
inp = map(List, inp)
for i in range(len(inp)):
inp[i].order = i
rv.setdefault(frozenset(inp[i]), [])
rv[frozenset(inp[i])].append(inp[i])
while condense_step():
pass
for v in rv.values():
v.sort(key=lambda x: x.order)
return [list(unique(i)) for i in rv.values()]