【问题标题】:Is there a standard way to partition an interable into equivalence classes given a relation in python?给定python中的关系,是否有标准方法将可迭代划分为等价类?
【发布时间】:2016-08-12 18:33:57
【问题描述】:

假设我在X 上有一个有限可迭代X 和一个等价关系~。我们可以定义一个函数my_relation(x1, x2),如果x1~x2返回True,否则返回False。我想编写一个将X 划分为等价类的函数。也就是说,my_function(X, my_relation) 应该返回~ 的等价类列表。

在 python 中有没有标准的方法来做到这一点?更好的是,是否有专门用于处理等价关系的模块?

【问题讨论】:

  • 您是否已经尝试过自己编写一些代码?你用 Google 搜索过 Python 等价类 吗?
  • 如果您可以从任意值计算每个等价类的规范元素,itertools.groupby 很有用。
  • @chepner 我认为这就是问题的有趣之处 - 等价类的规范。顺便说一句,如您所知,groupby 要求对它们进行排序 - 当前公式的另一个困难。
  • 定义这样一个函数当然是关键,但是同样的函数可以与sorted函数一起使用来获得所需的顺序。
  • @chepner 但是如果有这样一个规范的键功能,那么就复杂性而言,排序是矫枉过正的,不是吗?

标签: python equivalence-classes


【解决方案1】:

我找到了 John Reid 的 this Python recipe。它是用 Python 2 编写的,我将它改编为 Python 3 来测试它。该配方包括一个测试,用于根据关系 lambda x, y: (x - y) % 4 == 0 将整数集 [-3,5) 划分为等价类。

它似乎做你想做的事。这是我制作的改编版本,以防你在 Python 3 中需要它:

def equivalence_partition(iterable, relation):
    """Partitions a set of objects into equivalence classes

    Args:
        iterable: collection of objects to be partitioned
        relation: equivalence relation. I.e. relation(o1,o2) evaluates to True
            if and only if o1 and o2 are equivalent

    Returns: classes, partitions
        classes: A sequence of sets. Each one is an equivalence class
        partitions: A dictionary mapping objects to equivalence classes
    """
    classes = []
    partitions = {}
    for o in iterable:  # for each object
        # find the class it is in
        found = False
        for c in classes:
            if relation(next(iter(c)), o):  # is it equivalent to this class?
                c.add(o)
                partitions[o] = c
                found = True
                break
        if not found:  # it is in a new class
            classes.append(set([o]))
            partitions[o] = classes[-1]
    return classes, partitions


def equivalence_enumeration(iterable, relation):
    """Partitions a set of objects into equivalence classes

    Same as equivalence_partition() but also numbers the classes.

    Args:
        iterable: collection of objects to be partitioned
        relation: equivalence relation. I.e. relation(o1,o2) evaluates to True
            if and only if o1 and o2 are equivalent

    Returns: classes, partitions, ids
        classes: A sequence of sets. Each one is an equivalence class
        partitions: A dictionary mapping objects to equivalence classes
        ids: A dictionary mapping objects to the indices of their equivalence classes
    """
    classes, partitions = equivalence_partition(iterable, relation)
    ids = {}
    for i, c in enumerate(classes):
        for o in c:
            ids[o] = i
    return classes, partitions, ids


def check_equivalence_partition(classes, partitions, relation):
    """Checks that a partition is consistent under the relationship"""
    for o, c in partitions.items():
        for _c in classes:
            assert (o in _c) ^ (not _c is c)
    for c1 in classes:
        for o1 in c1:
            for c2 in classes:
                for o2 in c2:
                    assert (c1 is c2) ^ (not relation(o1, o2))


def test_equivalence_partition():
    relation = lambda x, y: (x - y) % 4 == 0
    classes, partitions = equivalence_partition(
        range(-3, 5),
        relation
    )
    check_equivalence_partition(classes, partitions, relation)
    for c in classes: print(c)
    for o, c in partitions.items(): print(o, ':', c)


if __name__ == '__main__':
    test_equivalence_partition()

【讨论】:

  • +1 用于调整整个配方,包括测试和一切。也许你可以在你的帖子中添加一个链接到 AS 食谱?
  • 哎呀,改编代码可能比这里的其他人在他们的答案中付出的努力要少。几个iteritems() -> items(),对print进行了一些调整......但是,是的,linked
【解决方案2】:

以下函数采用可迭代的a 和等价函数equiv,并按照您的要求执行:

def partition(a, equiv):
    partitions = [] # Found partitions
    for e in a: # Loop over each element
        found = False # Note it is not yet part of a know partition
        for p in partitions:
            if equiv(e, p[0]): # Found a partition for it!
                p.append(e)
                found = True
                break
        if not found: # Make a new partition for it.
            partitions.append([e])
    return partitions

例子:

def equiv_(lhs, rhs):
    return lhs % 3 == rhs % 3

a_ = range(10)

>>> partition(a_, equiv_)
[[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]

【讨论】:

    【解决方案3】:

    我不知道任何处理等价关系的 python 库。

    也许这个 sn-p 有用:

    def rel(x1, x2):
       return x1 % 5 == x2 % 5
    
    data = range(18)
    eqclasses = []
    
    for x in data:
         for eqcls in eqclasses:
             if rel(x, eqcls[0]):
                 # x is a member of this class
                 eqcls.append(x)
                 break
         else:
             # x belongs in a new class
             eqclasses.append([x])
    
    
    eqclasses
    => [[0, 5, 10, 15], [1, 6, 11, 16], [2, 7, 12, 17], [3, 8, 13], [4, 9, 14]]
    

    【讨论】:

      【解决方案4】:

      这行得通吗?

      def equivalence_partition(iterable, relation):
          classes = defaultdict(set)
          for element in iterable:
              for sample, known in classes.items():
                  if relation(sample, element):
                      known.add(element)
                      break
              else:
                  classes[element].add(element)
          return list(classes.values())
      

      我试过了:

      relation = lambda a, b: (a - b) % 2
      equivalence_partition(range(4), relation)
      

      返回:

      [{0, 1, 3}, {2}]
      

      编辑:如果您希望它尽可能快地运行,您可以:

      • 将其包装在 Cython 模块中(删除 defaultdict,没有太多需要更改的内容)
      • 想尝试用 PyPy 运行它
      • 找到一个专用模块(没找到)

      【讨论】:

      • 您能否详细说明如何将其包装在 cython 模块中?
      • 好吧,让这段代码适应 Cython 不会太难,因为除了 defaultdict 它不使用任何不能作为 Cython 类型的东西。您必须将其重写为.pyx 文件,使用cdef 声明变量,然后将其编译为可以在常规 Python 应用程序中导入的 Python 模块。
      【解决方案5】:
      In [1]: def my_relation(x):
         ...:     return x % 3
         ...:
      
      In [2]: from collections import defaultdict
      
      In [3]: def partition(X, relation):
         ...:     d = defaultdict(list)
         ...:     for item in X:
         ...:         d[my_relation(item)].append(item)
         ...:     return d.values()
         ...:
      
      In [4]: X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
      
      In [5]: partition(X, my_relation)
      Out[5]: [[3, 6, 9, 12], [1, 4, 7, 10], [2, 5, 8, 11]]
      

      对于二元函数:

      from collections import defaultdict
      from itertools import combinations
      
      def partition_binary(Y, relation):
          d = defaultdict(list)
          for (a, b) in combinations(Y):
              l = d[my_relation(a, b)]
              l.append(a)
              l.append(b)
          return d.values()
      

      你可以这样做:

      partition_binary(partition(X, rank), my_relation)
      

      哦,如果 my_relation 返回布尔值,这显然不起作用。我想说想出一些抽象的方式来表示每个同构,尽管我怀疑这是首先尝试这样做的目标。

      【讨论】:

      • 我没有看到与问题的联系。你的my_relation其实是一个关键函数,根本不是二元函数。
      • 关系的关键函数是equivalence kernel
      • 最佳答案!让我删除了我的,这是更好的考虑。
      • @pistache 我同意这是一个很好的答案......但它只有在我已经拥有关键功能时才有效。总的来说,找到关键函数就相当于解决了我原来的问题。
      • 啊,是的,这是真的。为了以防万一,我取消了我的答案。无论如何,它与 Tagc 的非常相似。
      猜你喜欢
      • 1970-01-01
      • 2017-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-29
      • 1970-01-01
      相关资源
      最近更新 更多