【问题标题】:Sorting with equivalence classes in Python在 Python 中使用等价类进行排序
【发布时间】:2019-07-20 20:56:23
【问题描述】:

假设我有一个自定义数据结构Data,它揭示了两个相关属性:tag 表示该项目属于哪个等价类,rank 表示该项目有多好。

我有一组无序的 Data 对象,并且想要检索具有最高 rankn 对象,但每个等价类最多有一个对象。

(同一等价类中的对象不一定比较相等,也不一定具有相同的rank,但我不希望输出中的任何两个元素来自同一类。在其他换句话说,产生这些等价类的关系不是==。)

我的第一个方法看起来像这样:

  • 按降序排列列表rank
  • 创建一个空集s
  • 对于列表中的每个元素:
    • 检查其tag是否在s中;如果是,请继续
    • 将其tag 添加到s
    • 产生该元素
    • 如果我们产生了n 元素,请停止

但是,这感觉很尴尬,好像应该有更好的方法(可能使用itertools 和高阶函数)。生成的 n 元素的顺序并不重要。

这个问题的 Pythonic 解决方案是什么?

玩具示例:

Data = namedtuple('Data', ('tag', 'rank'))
n = 3

algorithm_input = { Data('a', 200), Data('a', 100), Data('b', 50), Data('c', 10), Data('d', 5) }
expected_output = { Data('a', 200), Data('b', 50), Data('c', 10) }

【问题讨论】:

  • 您可以发布示例输入和预期输出吗?到目前为止有任何代码吗?
  • @AndrejKesely 添加了一个示例。到目前为止,我的代码只是实现了问题中概述的算法,而且效果很好——我只是在寻找一种更好的方法,如果存在的话。

标签: python algorithm sorting equivalence-classes


【解决方案1】:

您可以使用itertools.groupby (doc)。首先,我们按照您的标准对项目进行排序,然后按标签对它们进行分组(并且只存储每个组中的第一个项目):

from itertools import groupby
from collections import namedtuple

Data = namedtuple('Data', ('tag', 'rank'))

n = 3

algorithm_input = { Data('a', 200), Data('a', 100), Data('b', 50), Data('c', 10), Data('d', 5) }

# 1. sort the data by rank (descending) and tag (ascending)
s = sorted(algorithm_input, key=lambda k: (-k.rank, k.tag))

# 2. group the data by tag and store first item from each group to 'out', limit the number of groups to 'n'
out = []
for (_, g), _ in zip(groupby(s, lambda k: k.tag), range(n)):
    out.append(next(g))

print(out)

打印:

[Data(tag='a', rank=200), Data(tag='b', rank=50), Data(tag='c', rank=10)]

编辑:更改了排序键。

【讨论】:

    【解决方案2】:

    将排序后的输入存储在OrderedDict 中(tag 作为键,Data 作为值)。这将导致每个等效类中只有一个 Data 存储在 OrderedDict

    >>> from collections import namedtuple, OrderedDict
    >>> Data = namedtuple('Data', ('tag', 'rank'))
    >>> n = 3
    >>> algorithm_input = { Data('a', 200), Data('a', 100), Data('b', 50), Data('c', 10), Data('d', 5) }
    >>> 
    >>> set(list(OrderedDict((d.tag, d) for d in sorted(algorithm_input)).values())[:n])
    {Data(tag='b', rank=50), Data(tag='a', rank=200), Data(tag='c', rank=10)}
    

    【讨论】:

    • 非常好!因为如果我理解正确,后面的条目会覆盖前面的条目?
    • 是的.. 当我们首先排序时,所有排名较高的条目都会稍后出现,最新的条目将覆盖所有较早的条目
    【解决方案3】:

    我认为取每个组的最大元素(O(|elements|))然后获得 n 个最大的排名(O(|groups|.lg n),堆大小为n)会更快,而不是先排序(@ 987654325@) 并取n 元素(O(|elements|)):

    创建一个字典max_by_tag,按标签存储具有最大排名的项目:

    >>> from collections import namedtuple
    >>> Data = namedtuple('Data', ('tag', 'rank'))
    >>> n = 3
    >>> algorithm_input = { Data('a', 200), Data('a', 100), Data('b', 50), Data('c', 10), Data('d', 5) }
    >>> max_by_tag = {}
    >>> for item in algorithm_input:
    ...     if item.tag not in max_by_tag or item.rank > max_by_tag[item.tag].rank:
    ...         max_by_tag[item.tag] = item
    
    >>> max_by_tag
    {'a': Data(tag='a', rank=200), 'b': Data(tag='b', rank=50), 'c': Data(tag='c', rank=10), 'd': Data(tag='d', rank=5)}
    

    然后使用heapq 模块:

    >>> import heapq
    >>> heapq.nlargest(n, max_by_tag.values(), key=lambda data: data.rank)
    [Data(tag='a', rank=200), Data(tag='b', rank=50), Data(tag='c', rank=10)]
    

    【讨论】:

      【解决方案4】:

      如果是你控制的类定义,我相信最 Pythonic 的方式是这样的:

      from random import shuffle
      
      class Data:
      
          def __init__(self, order=1):
              self.order = order
      
          def __repr__(self):
              return "Order: " + str(self.order)
      
      if __name__ == '__main__':
          import sys
          d = []
          for i in range(0,10):
              d.append(Data(order=i))
          shuffle(d)
      
          print(d)
      
          print(sorted(d, key=lambda data: data.order))
      

      输出:

      [Order: 5, Order: 2, Order: 6, Order: 0, Order: 4, Order: 7, Order: 3, Order: 9, Order: 1, Order: 8]
      [Order: 0, Order: 1, Order: 2, Order: 3, Order: 4, Order: 5, Order: 6, Order: 7, Order: 8, Order: 9]
      

      因此,本质上,为类添加一个排序依据的属性。定义字符串 rep(只是为了更容易看到发生了什么)。然后在带有 lambda 函数的对象列表上使用 python 的 sorted() 来指示每个对象应该被排序的属性。

      注意:必须定义该属性类型的比较 - 这里它是一个 int。如果未定义属性,则必须为该属性实现 gtlet 等。详情请见docs

      【讨论】:

      • 这对排名很有用,但不注意等价类。
      猜你喜欢
      • 2015-02-26
      • 1970-01-01
      • 1970-01-01
      • 2017-12-15
      • 1970-01-01
      • 2020-06-03
      • 2020-10-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多