Как отфильтровать набор (int, str) кортежей, чтобы вернуть только кортеж с минимальным значением в первом элементе? - PullRequest
3 голосов
/ 01 апреля 2019

Предположим, у меня есть набор кортежей, представляющих URL-адреса с "оценками":

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

Какой краткий способ для меня отфильтровать повторяющиеся URL-адреса, возвращая только URL с наименьшим количеством баллов? То есть из приведенного выше примера я хочу получить следующий набор, где каждый URL-адрес появляется только один раз, с наименьшим соответствующим счетом из исходного набора:

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

Я придумал следующее решение:

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()}

... но я чувствую, что, возможно, есть какой-то более простой и эффективный способ сделать это без использования промежуточного запроса, чтобы отслеживать элемент max, а затем восстановить набор из этого. Каков наилучший способ фильтрации набора кортежей по мин / макс первого элемента?

Ответы [ 4 ]

3 голосов
/ 01 апреля 2019

Вы уже реализовали самый простой подход, который я могу придумать.Единственное изменение, которое я бы сделал, - это цикл - чуть более краткая версия использует 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')}

Если вы действительно хотите более короткое решение, как я уже сказал, это не так.Это не самый простой подход, но это один вкладыш.Большая часть проблемы заключается в обмене URL-адресом и оценкой, чтобы вы могли использовать URL-адрес в качестве ключа при удалении дубликатов.Само собой разумеется, что сортировка является предварительным условием (вот почему мне не нравится это решение так же, как приведенное выше).

{(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')}

Это решение становится намного короче, если выглядит sкак это:

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

Вам нужно только тогда сделать

list(dict(sorted(s2, reverse=True)).items())
# [('http://www.foo.com', 0.5), ('http://www.bar.com', 0.33)]
1 голос
/ 01 апреля 2019

Очень простой подход:

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]]
1 голос
/ 01 апреля 2019

Другое решение:

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)
1 голос
/ 01 апреля 2019

Без каких-либо хитростей или дополнительного кода для повторного использования вы довольно близки.Я придумал нечто похожее, что, на мой взгляд, немного чище:

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

Вы также можете использовать другие библиотеки, такие как boltons .Вы можете использовать уникальный метод следующим образом:

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

Обновление : если кортежи имеют все релевантные оценки в качестве первых элементов, это решение будет работать для произвольныхдлина кортежей (при условии изменения ключевой функции)

...