【问题标题】:Sorting tuples with a custom sorting function using a range?使用范围使用自定义排序功能对元组进行排序?
【发布时间】:2017-01-15 22:25:34
【问题描述】:

我想根据最后两列对元组列表进行排序:

mylist = [(33, 36, 84), 
          (34, 37, 656), 
          (23, 38, 42)]

我知道我可以这样做:

final = sorted(mylist, key:lambda x: [ x[1], x[2]])

现在我的问题是我想将列表的第二列与一个特殊条件进行比较:如果两个数字之间的差异小于偏移量,则它们应该被视为相等(36 == 37 == 38)并且第三列应用于对列表进行排序。我希望看到的最终结果是:

mylist = [(23, 38, 42)
          (33, 36, 84), 
          (34, 37, 656)]

我正在考虑创建自己的整数类型并覆盖等号运算符。这可能吗?是不是矫枉过正?有没有更好的方法来解决这个问题?

【问题讨论】:

  • 我不明白你所说的偏移量是什么意思。但是不要创建自己的类型,您可以使 lambda 函数更复杂。让它从将所有这些考虑在内的元组的最后两个数字中计算出一个数字。
  • 偏移量是指一个小数字。例如如果 offset = 2 那么 (36 + offset) == 38
  • 我会发布,但这会破坏传递性。在使用此比较时要小心。
  • 如果你有 [(1,6), (2,5), (3,4), (4,3), (5,2), (6,1)] 会发生什么偏移量为 1?
  • 但是例如第一项 (1,6) 不与项 (3,4) 的偏移量,所以 (1,6) 应该在 (3,4) 之前,因为我们正在按第一项排序。但是如果你取 1 == 2 == 3,那么它们应该按第二项排序,并且 (3,4) 应该在 (1,6) 之前。这是模棱两可的。你需要做出更精确的定义。

标签: python sorting tuples custom-function


【解决方案1】:

我认为最简单的方法是创建一个新的类来进行比较:

mylist = [(33, 36, 84), 
          (34, 37, 656), 
          (23, 38, 42)]

offset = 2

class Comp(object):
    def __init__(self, tup):
        self.tup = tup

    def __lt__(self, other):  # sorted works even if only __lt__ is implemented.
        # If the difference is less or equal the offset of the second item compare the third
        if abs(self.tup[1] - other.tup[1]) <= offset:
            return self.tup[2] < other.tup[2]
        # otherwise compare them as usual
        else:
            return (self.tup[1], self.tup[2]) < (other.tup[1], other.tup[2])

示例运行显示您的预期结果:

>>> sorted(mylist, key=Comp)
[(23, 38, 42), (33, 36, 84), (34, 37, 656)]

我认为它比使用 functools.cmp_to_key 更干净,但这是个人喜好问题。

【讨论】:

    【解决方案2】:

    有时,基于cmp 函数的旧式排序比基于key 的排序更容易。所以——写一个cmp函数,然后用functools.cmp_to_key把它转换成key:

    import functools
    
    def compare(s,t,offset):
        _,y,z = s
        _,u,v = t
        if abs(y-u) > offset: #use 2nd component
            if y < u:
                return -1
            else:
                return 1
        else: #use 3rd component
            if z < v:
                return -1
            elif z == v:
                return 0
            else:
                return 1
    
    mylist = [(33, 36, 84), 
              (34, 37, 656), 
              (23, 38, 42)]
    
    mylist.sort(key = functools.cmp_to_key(lambda s,t: compare(s,t,2)))
    
    for t in mylist: print(t)
    

    输出:

    (23, 38, 42)
    (33, 36, 84)
    (34, 37, 656)
    

    【讨论】:

      【解决方案3】:

      https://wiki.python.org/moin/HowTo/Sorting 中查找“使用 cmp 参数的旧方法”。这允许您编写自己的比较函数,而不仅仅是设置键和使用比较运算符。

      进行这样的排序是有危险的。查找“严格的弱排序”。您可以有多个不同的有效订单。这可能会破坏其他假设有一种正确的排序方式的代码。

      现在来回答你的问题:

      mylist = [(33, 36, 84), 
                (34, 37, 656), 
                (23, 38, 42)]
      
      def custom_sort_term(x, y, offset = 2):
          if abs(x-y) <= offset:
              return 0
          return x-y
      
      def custom_sort_function(x, y):
          x1 = x[1]
          y1 = y[1]
          first_comparison_result = custom_sort_term(x1, y1)
          if (first_comparison_result):
              return first_comparison_result
          x2 = x[2]
          y2 = y[2]
          return custom_sort_term(x2, y2)
      
      final = sorted(mylist, cmp=custom_sort_function)
      print final
      

      [(23, 38, 42), (33, 36, 84), (34, 37, 656)]

      【讨论】:

      • 仅适用于 python 2。在 python 3 中 cmp 参数消失了。
      • 无赖,但如果 OP 在 python3 上,看起来还有很多其他方法可以做同样的事情。我将把它留在这里,因为没有其他人提到他的排序违反了严格的弱排序(可能不是破坏交易,但需要考虑)。
      【解决方案4】:

      不漂亮,但我尝试在对 OP 问题陈述的解释中保持一般性

      我扩展了测试用例,然后施加了简单的钝力

      # test case expanded
      mylist = [(33, 6, 104),
                (31, 36, 84),
                (35, 86, 84),
                (30, 9, 4),
                (23, 38, 42),
                (34, 37, 656),          
                (33, 88, 8)]
      
      threshld = 2    # different final output can be seen if changed to 1, 3, 30
      
      def collapse(nums, threshld):
          """
          takes sorted (increasing) list of numbers, nums
          replaces runs of consequetive nums
          that successively differ by threshld or less
          with 1st number in each run
          """
          cnums = nums[:]
          cur = nums[0]
          for i in range(len(nums)-1):
              if (nums[i+1] - nums[i]) <= threshld:
                  cnums[i+1] = cur
              else:
                  cur = cnums[i+1]
          return cnums
      
      mylists = [list(i) for i in mylist] # change the tuples to lists to modify
      
      indxd=[e + [i] for i, e in enumerate(mylists)] # append the original indexing    
      #print(*indxd, sep='\n')
      im0 = sorted(indxd, key=lambda x: [ x[1]])      # sort by  middle number   
      cns = collapse([i[1] for i in im0], threshld)   # then collapse()
      #print(cns)
      for i in range(len(im0)):                       # overwrite collapsed into im0
          im0[i][1] = cns[i]   
      #print(*im0, sep='\n')
      im1 = sorted(im0, key=lambda x: [ x[1], x[2]])  # now do 2 level sort
      #print(*sorted(im0, key=lambda x: [ x[1], x[2]]), sep='\n')
      final = [mylist[im1[i][3]] for i in range(len(im1))] # rebuid using new order 
                                                           # of original indices       
      print(*final, sep='\n')
      
      (33, 6, 104)
      (30, 9, 4)
      (23, 38, 42)
      (31, 36, 84)
      (34, 37, 656)
      (33, 88, 8)
      (35, 86, 84)
      

      【讨论】:

        猜你喜欢
        • 2015-11-01
        • 2022-01-05
        • 1970-01-01
        • 2018-08-26
        • 1970-01-01
        • 2021-07-25
        • 1970-01-01
        • 2013-05-08
        • 2014-04-19
        相关资源
        最近更新 更多