【问题标题】:Sorting a list: numbers in ascending, letters in descending排序列表:数字升序,字母降序
【发布时间】:2018-01-14 10:01:55
【问题描述】:

本题其实改编自one previously asked by Mat.Simage)。虽然被删了,但我觉得这个问题问得很好,所以我把它的要求和我自己的解决方案重新发布。


给定一个字母和数字的列表,说

['a', 2, 'b', 1, 'c', 3]

要求是数字升序,字母降序,不改变字母和数字的相对位置。我的意思是如果未排序的列表是:

[L, D, L, L, D]    # L -> letter; # D -> digit 

那么,排序后的列表也一定是

[L, D, L, L, D] 
  1. 字母和数字不一定以规则模式交替出现 - 它们可以以任意顺序出现

  2. 排序后 - 数字升序,字母降序。

所以对于上面的例子,输出是

['c', 1, 'b', 2, 'a', 3]

另一个例子:

 In[]: [5, 'a', 'x', 3, 6, 'b']
Out[]: [3, 'x', 'b', 5, 6, 'a']

什么是这样做的好方法?

【问题讨论】:

  • @cᴏʟᴅsᴘᴇᴇᴅ:为了阻止那些不理解本网站鼓励自我回答的人,我过去曾在我的问题下发表评论:PS。有些人可能认为我在发布后立即回答我自己的问题是错误的。在投票之前,请阅读It's OK to Ask and Answer Your Own Questions
  • @Jean-FrançoisFabre 哈哈,依稀记得那个。是的,它有点相似。猜猜我无意中从那里学到了另一个技巧。
  • @MartijnPieters 好吧,我不想给人留下我不想从社区获得进一步意见的印象,所以我没有发布答案。但是你帮助我更好地理解了,所以谢谢你!

标签: python list sorting list-comprehension


【解决方案1】:

你看,不是iter

lst = ['a', 2, 'b', 1, 'c', 3]
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
lst = [(letters if isinstance(elt,str) else numbers).pop()for elt in lst]

我正在寻找一种方法将其变成(可怕的)单线,但到目前为止还没有运气 - 欢迎提出建议!

【讨论】:

    【解决方案2】:

    我通过创建两个生成器然后有条件地从它们中提取来解决这个问题:

    f1 = iter(sorted(filter(lambda x:     isinstance(x, str), lst), reverse=True))
    f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
    
    [next(f1) if isinstance(x, str) else next(f2) for x in lst]
    # ['c', 1, 'b', 2, 'a', 3]
    

    【讨论】:

      【解决方案3】:

      一行:

      list(map(list, sorted(zip(lst[::2], lst[1::2]), key=lambda x: x[1] if hasattr(x[0], '__iter__') else x[0])))
      

      【讨论】:

        【解决方案4】:

        这是使用defaultdict()bisect() 的优化方法:

        In [14]: lst = [5, 'a', 'x', 3, 6, 'b']
        In [15]: from collections import defaultdict       
        In [16]: import bisect
        
        In [17]: def use_dict_with_bisect(lst):
                     d = defaultdict(list)
                     for i in lst:
                         bisect.insort(d[type(i)], i)
                     # since bisect doesn't accept key we need to reverse the sorted integers
                     d[int].sort(reverse=True)
                     return [d[type(i)].pop() for i in lst]
           .....:  
        

        演示:

        In [18]: lst
        Out[18]: [5, 'a', 'x', 3, 6, 'b']
        
        In [19]: use_dict_with_bisect(lst)
        Out[19]: [3, 'x', 'b', 5, 6, 'a']
        

        如果您要处理更大的列表,使用 bisect 删除会更优化,它的复杂度约为 O(n2),只需使用 python 内置的 sort() 函数Nlog(n) 复杂度。

        In [26]: def use_dict(lst):
                     d = defaultdict(list)
                     for i in lst:
                         d[type(i)].append(i)
                     d[int].sort(reverse=True); d[str].sort()
                     return [d[type(i)].pop() for i in lst]
        

        其他答案的基准测试显示使用dict 和内置sort 的最新方法比其他方法快快1ms

        In [29]: def use_sorted1(lst):
                      letters = sorted(let for let in lst if isinstance(let,str))
                      numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
                      return [letters.pop() if isinstance(elt,str) else numbers.pop() for elt in lst]
           .....: 
        
        In [31]: def use_sorted2(lst):
                      f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
                      f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
                      return [next(f1) if isinstance(x, str) else next(f2) for x in lst]
           .....: 
        
        In [32]: %timeit use_sorted1(lst * 1000)
        100 loops, best of 3: 3.05 ms per loop
        
        In [33]: %timeit use_sorted2(lst * 1000)
        100 loops, best of 3: 3.63 ms per loop
        
        In [34]: %timeit use_dict(lst * 1000)   # <-- WINNER
        100 loops, best of 3: 2.15 ms per loop
        

        这是一个基准,显示使用 bisect 如何减慢长列表的处理速度:

        In [37]: %timeit use_dict_with_bisect(lst * 1000)
        100 loops, best of 3: 4.46 ms per loop
        

        【讨论】:

        • 声称插入排序是一种“优化的方法”有点过分,即使使用二进制搜索来查找插入点也是如此。显然,它适用于小输入。
        • @SteveJessop 当然,实际上使用 bisect 并不是我称之为优化的唯一原因。这是因为它与字典相结合。否则,创建列表并使用使用 Tim 排序算法的 sorted(使用 Nlog(N) 顺序)对于较长的列表会稍微快一些。
        【解决方案5】:

        完全不推荐,但我在编写代码时玩得很开心。

        from collections import deque
        from operator import itemgetter
        
        lst = ['a', 2, 'b', 1, 'c', 3]
        is_str = [isinstance(e, str) for e in lst]
        two_heads = deque(map(itemgetter(1), sorted(zip(is_str, lst))))
        [two_heads.pop() if a_str else two_heads.popleft() for a_str in is_str]
        

        【讨论】:

          【解决方案6】:

          我们为什么不按升序对列表进行排序,但要确保数字在字母之前:

          [D, D, L, L, L]    # L -> letter; # D -> digit 
          

          我们可以这样实现:

          >>> lst = [5, 'a', 'x', 3, 6, 'b']
          >>> sorted(lst, key=lambda el: (isinstance(el, str), el))
          [3, 5, 6, 'a', 'b', 'x']
          

          然后我们从左到右查看原始数组,如果遇到数字,我们从排序数组的开头选择元素,否则从结尾。完整的详细解决方案将是:

          def one_sort(lst):
              s = sorted(lst, key=lambda el: (isinstance(el, str), el))
              res = []
              i, j = 0, len(s)
              for el in lst:
                  if isinstance(el, str):
                      j -= 1
                      res.append(s[j])
                  else:
                      res.append(s[i])
                      i += 1
              return res
          
          lst = [5, 'a', 'x', 3, 6, 'b']
          print(one_sort(lst)) # [3, 'x', 'b', 5, 6, 'a']
          

          更短但神秘的解决方案将是:

          def one_sort_cryptic(lst):
              s = sorted(lst, key=lambda el: (isinstance(el, str), el))
              return [s.pop(-isinstance(el, str)) for el in lst]
          
          lst = [5, 'a', 'x', 3, 6, 'b']
          print(one_sort_cryptic(lst)) # [3, 'x', 'b', 5, 6, 'a']
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-10-12
            • 2022-01-09
            • 2021-12-19
            • 2019-07-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-10
            相关资源
            最近更新 更多