【问题标题】:How to filter a set of (int, str) tuples, to return only tuple with min value in first element?如何过滤一组(int,str)元组,只返回第一个元素中具有最小值的元组?
【发布时间】:2019-04-01 05:57:43
【问题描述】:

假设我有一组用“分数”表示 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()}

...但我觉得可能有一些更简单,更有效的方法可以做到这一点,而无需使用中间 dict 来跟踪最大元素,然后从中重新生成集合。按第一个元素的最小值/最大值过滤一组元组的最佳方法是什么?

【问题讨论】:

  • 我找到了a duplicate,但它很糟糕,我不想将它编辑成形状。因此,除非其他人想要这样做,否则我会等待答案发布在这里,然后将旧问题作为骗子关闭。

标签: python filter set tuples min


【解决方案1】:

您已经实现了我能想到的最简单的方法。我要做的唯一改变是循环——一个稍微简洁一点的版本是使用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)]

【讨论】:

  • 您的最后一段代码依赖于如何从元组列表初始化dict 的实现细节,每次都覆盖键的最后一个值。这可能经不起时间的考验,并且有隐含的假设。
  • @guyarad 这不是一个实现细节。字典总是使用最后一个值。只要您传递有序集合(例如列表),就可以了。
  • @guyarad 这是记录在案的行为,而不是实现细节:“如果键出现多次,则该键的最后一个值将成为新字典中的对应值。”(来自here。)
  • 啊很好 - 我喜欢使用 min() 而不是条件。我花了一分钟来理解你的最后一个解决方案,我将使用第一个,既是为了可读性,也是因为我想避免排序(URLS 集可能相当大)。但这是一种有趣的技术——我不知道可以以这种方式使用 dict 初始化来过滤掉重复的键。非常感谢。 +1
【解决方案2】:

另一种解决方案:

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)

【讨论】:

    【解决方案3】:

    没有任何技巧或额外的代码可以重用,你已经很接近了。我想出了一些类似的东西,在我看来更简洁:

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

    您还可以使用其他库,例如boltons。您可以像这样使用unique 方法:

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

    更新:如果元组将所有相关评分作为第一个元素,则此解决方案适用于任意长度的元组(假设您更改了键函数)

    【讨论】:

      【解决方案4】:

      一个非常简单的方法:

      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]]
      

      【讨论】:

        猜你喜欢
        • 2022-11-02
        • 1970-01-01
        • 1970-01-01
        • 2011-07-12
        • 1970-01-01
        • 2020-04-20
        • 2016-10-21
        • 2016-09-03
        相关资源
        最近更新 更多