¿Cómo filtrar un conjunto de tuplas (int, str) para devolver solo tuplas con valor mínimo en el primer elemento?

Supongamos que tengo un conjunto de tuplas que representan URLS con “puntuaciones”:

{(0.75, 'http://www.foo.com'), (0.33, 'http://www.bar.com'), (0.5, 'http://www.foo.com'), (0.66, 'http://www.bar.com')} .

¿Cuál es una forma concisa para que yo filtre las URL duplicadas, devolviendo solo la URL con la puntuación más baja? Es decir, del ejemplo anterior, deseo obtener el siguiente conjunto, donde cada URL aparece solo una vez, con la puntuación más baja correspondiente del conjunto original:

{(0.5, 'http://www.foo.com'),(0.33, 'http://www.bar.com')}

Se me ocurrió la siguiente solución:

 from collections import defaultdict seen = defaultdict(lambda:1) for score, url in s: if score < seen[url]: seen[url] = score filtered = {(v,k) for k,v in seen.items()} 

… pero creo que probablemente haya una forma más sencilla y eficiente de hacerlo sin usar el dictamen intermedio para realizar un seguimiento del elemento max, y luego regenerar el conjunto a partir de eso. ¿Cuál es la mejor manera de filtrar un conjunto de tuplas por el mínimo / máximo del primer elemento?

Ya has implementado el enfoque más simple que se me ocurre. El único cambio que haría sería en el bucle: una versión un poco más concisa usa min .

 seen = defaultdict(lambda: 1) # `lambda: float('inf')` if scores can be > 1 for score, url in s: seen[url] = min(seen[url], score) {(v,k) for k,v in seen.items()} # {(0.33, 'http://www.bar.com'), (0.5, 'http://www.foo.com')} 

Si realmente desea una solución más corta, como dije, no es el enfoque más simple, pero es un forro. La mayor parte del desafío es intercambiar la URL y la puntuación para que pueda utilizar la URL como una clave al eliminar duplicados. No hace falta decir que la clasificación es una condición previa aquí (por eso no me gusta esta solución tanto como la anterior).

 {(v, k) for k, v in dict(sorted(((v, k) for k, v in s), reverse=True)).items()} # {(0.33, 'http://www.bar.com'), (0.5, 'http://www.foo.com')} 

Esta solución se vuelve mucho más corta si s ve así:

 s2 = {(v,k) for k, v in s} s2 # {('http://www.bar.com', 0.33), ('http://www.bar.com', 0.66), ...} 

Solo entonces deberías hacer

 list(dict(sorted(s2, reverse=True)).items()) # [('http://www.foo.com', 0.5), ('http://www.bar.com', 0.33)] 

Otra solución:

 seen = {} for score, url in s: if seen.setdefault(url, score) > score: seen[url] = score filtered = {(v,k) for k,v in seen.items()} print(filtered) 

Sin ningún truco o código adicional para reutilizarlo estás bastante cerca. Se me ocurrió algo similar que es un poco más limpio en mi opinión:

 seen = set() filtered = [] for score, url in sorted(urls): if url in seen: continue filtered.append((score, url)) seen.add(url) 

También puede utilizar otras bibliotecas, como boltons . Puedes usar el método único así:

 import operator from boltons.iterutils import unique filtered = unique(sorted(urls), key=operator.itemgetter(1)) 

Actualización : si las tuplas tienen todas las puntuaciones relevantes como los primeros elementos, esta solución funcionaría para tuplas de longitud arbitraria (suponiendo que cambie la función clave)

Un enfoque muy simple:

 L=sorted(s,key=lambda t: (t[1],t[0])) [L[0]] + [L[i] for i in range(1,len(L)) if L[i][1]!=L[i-1][1]]