【问题标题】:Sort with respect to multi-level key where the order is different for each level对多级键进行排序,其中每个级别的顺序不同
【发布时间】:2021-07-18 11:35:40
【问题描述】:

我有一个由元组表示的具有多个属性的项目集合:

items = [(a11, a12, ..., a1N), (a21, a22, ..., a2N), ...]

现在我想对这些项目进行排序,但是每个级别的顺序可能会有所不同(例如,aX1 升序、aX2 降序、...、aXN 升序)。关键是这些属性不是invertible(例如文本;否则key 可以构造为key=lambda a: (a[0], -a[1], ..., a[N]))。

例如使用 2 元组:

items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
reverse = (True, False)  # first level descending, second level ascending

expected = [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]

其他三元组示例:

items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'), ('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
reverse = (True, False, True)

expected = [('b', 'a', 'b'), ('b', 'a', 'a'), ('b', 'b', 'b'), ('a', 'b', 'c'), ('a', 'b', 'b'), ('a', 'c', 'b')]

我曾想过创建一个伪逆,例如通过str.translate,但显然这也有其缺点,因为需要跟踪所有边缘情况。

【问题讨论】:

    标签: python python-3.x string sorting


    【解决方案1】:

    假设元组中的所有项目都是字符串字符,您可以将它们编码为十六进制字符串,其中如果reverse 不是Trueformat(127 - order(c), 'x') 如果@987654326,则字符c 将设置为format(ord(c), 'x') @ 是 True。现在您有了元组的编码字符串,您可以使用sorted 执行常规字符串排序

    items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'), ('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
    reverse = (True, False, True)
    
    items_sorted = sorted(items, key = lambda x: "".join([format(127 - ord(x[i]), "x") if reverse[i] else format(ord(x[i]), "x") for i in range(len(x))]))
    

    【讨论】:

      【解决方案2】:

      这是一个解决方案,它将reverse 分别应用于TrueFalse-11,应用于每个属性的有序 版本 - 因为很可能该字符串属性多于一个字符。这个想法是创建一个可以传递给sorted()key 函数。

      编辑:这是multilevel_order 应用不同反转规则的版本(注意名称更改):

      def multilevel_order(reverse):
          multipliers = tuple(-1 if trufal else 1 for trufal in reverse)
      
          def key_func(item):
              return tuple(tuple(map(m.__mul__, map(ord, attr))) for attr, m in zip(item, multipliers))
      
          return key_func
      

      结果:

      items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'),
               ('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
      reverse = (True, False, True)
      expected = [('b', 'a', 'b'), ('b', 'a', 'a'), ('b', 'b', 'b'),
                  ('a', 'b', 'c'), ('a', 'b', 'b'), ('a', 'c', 'b')]
      
      print(sorted(items, key=multilevel_order(reverse)) == expected)
      # True
      
      items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
      reverse = (True, False)  # first level descending, second level ascending
      expected = [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]
      
      print(sorted(items, key=multilevel_order(reverse)) == expected)
      # True
      

      原始答案:我还使用了items 的稍微修改的值,以使ordinaled 的效果更清晰。

      items = [('a', 'bb', 'ccc'), ('a', 'cc', 'bbb'), ('a', 'bb', 'bbb'),
               ('b', 'aa', 'aaa'), ('b', 'aa', 'bbb'), ('b', 'bb', 'bbb')]
      reverse = (True, False, True)
      
      multipliers = tuple(-1 if trufal else 1 for trufal in reverse)
      def multi_level_key(item):
          ordinaled = (map(ord, attr) for attr in item)
          # this multiplication step could have been included above
          return tuple(tuple(map(m.__mul__, o)) for m, o in zip(multipliers, ordinaled))
      

      multi_level_key() 的示例输出:

      [multi_level_key(item) for item in items]
      # output:
      [((-97,), (98, 98), (-99, -99, -99)),
       ((-97,), (99, 99), (-98, -98, -98)),
       ((-97,), (98, 98), (-98, -98, -98)),
       ((-98,), (97, 97), (-97, -97, -97)),
       ((-98,), (97, 97), (-98, -98, -98)),
       ((-98,), (98, 98), (-98, -98, -98))]
      

      将其用作keysorted()

      sorted(items, key=multi_level_key)
      # output:
      [('b', 'aa', 'bbb'),
       ('b', 'aa', 'aaa'),
       ('b', 'bb', 'bbb'),
       ('a', 'bb', 'ccc'),
       ('a', 'bb', 'bbb'),
       ('a', 'cc', 'bbb')]
      

      潜在的改进:我将multipliers 移出关键功能,使其只计算一次。理想情况下,它应该放在multi_level_key() 的闭包中,这样它就可以与其他反转规则一起使用,而无需编写新函数。

      【讨论】:

      • 此解决方案最接近原始字符串比较,但我担心较长字符串的性能可能会受到影响,因为即使只需要几个字符,它也会计算完整的ord 转换。无论如何,我认为这可以通过从key 函数返回一个自定义类来解决,该函数实现了序列协议并根据要求懒惰地计算ord 转换(使用记忆)。然后,这将利用也通过字典排序实现的短路行为。我会尝试这个想法。
      【解决方案3】:

      最终我最终使用了以下实现,它使用itertools.groupby 来识别已排序级别内的联系,然后递归地对相应的组进行排序。这样做的好处是它完全依赖于内置的字符串比较方法。

      import itertools as it
      import operator as op
      
      def sort_one_level(data, *, reverse, level=0):
          result = sorted(data, reverse=reverse[level])
          if level + 1 < len(reverse):
              result = it.chain.from_iterable(
                  sort_one_level(group, reverse=reverse, level=level+1)
                  for __, group in it.groupby(result, key=op.itemgetter(level))
              )
          yield from result
      

      【讨论】:

      • 很好,我试图找到一种方法来做到这一点,所以它只依赖于 Python 内置的自然排序,并且知道会有一个 itertools.chain.from_iterable,但看不出是怎么做到的可以正常工作+1。您能否添加一些关于如何创建组和链的解释?
      • 顺便说一句,您还可以为sorted() 添加一个key 函数的元组,这样就可以对包含任何对象类型的任意元组/迭代列表进行排序,只要它们的key是正确的。所以像items = [('a', 1, userobj(7)), ('b', 2, userobj(-15))]reverse = (True, False, True)keys = (None, None, userobj.sortkey) 这样的东西可以正常工作。
      • @aneroid 各个组由当前level 中无法区分的元素组成。先前应用的sorted 根据当前levelreverse 首选项对所有级别进行排序,并且在递归调用期间已修复。实际上,如果两个或多个级别具有相同的reverse 首选项,则可以跳过这些级别而无需应用额外的sorted,因此仍有改进的空间。此外,当前的实现分为只有一个元素的组,这也不是必需的。
      【解决方案4】:

      使用Reverse Alphabet 反转字符串。

      代码

      from string import printable     # all the printable characters
      
      def tuple_sort(items, rev):
          # Create translation table for printable characters
          trans = str.maketrans(printable, printable[::-1], ' ') 
      
          # key function forms list with inverted tuple items based upon rev
          return sorted(items, key = lambda item: [s.translate(trans) if flag else s for s, flag in zip(item, rev)])
      

      测试

      items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
      reverse = (True, False) 
      tuple_sort(items, reverse)
      # Result: [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]
      

      【讨论】:

        猜你喜欢
        • 2018-11-27
        • 1970-01-01
        • 2017-10-20
        • 2021-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-11
        • 2011-05-28
        相关资源
        最近更新 更多