【问题标题】:Python Sorting Dictionary by values can be unstable? (in Hackerrank)Python按值排序字典可能不稳定? (在 Hackerrank 中)
【发布时间】:2017-11-03 09:10:45
【问题描述】:

我正在为 Python 3 学习在 Hackerrank 中做一些练习。

在任务Most Common 中,你得到一个只包含小写英文字符的字符串,你需要找到该字符串中最常见的三个字符。

我遇到了一些问题。

我对这个问题的解决方案如下:

#!/bin/python3
import sys

if __name__ == "__main__":
  s = input().strip()
  ch_dict = {}
  for ch in s:
      if ch in ch_dict : ch_dict[ch] +=1
      else: ch_dict[ch] = 1

  result = sorted(ch_dict.items(),key=lambda d:d[1],reverse=True)
  for i in result:
      if i[1] != 1:
          print(" ".join(map(str,i)))

当我在本地环境中测试这段代码时,它可以工作!

但是在在线测试中,它可能会失败!

对于这个输入:

aabbbccde

我提交了很多次,有时会得到这样的正确答案:

b 3
a 2
c 2

也可以得到这个:

b 3
c 2
a 2

似乎排序可能不稳定?或者我的代码有什么问题?或者在 Hackerrank 环境中有什么问题吗?

我如何保证我的输出?

【问题讨论】:

  • 字典是无序的,你只对值进行排序。所以当你得到相等的值时,它们的顺序和输入的顺序是一样的。哪个是实现定义的,看起来可能是任意的。见Why is the order in dictionaries and sets arbitrary?
  • 第二个答案和第一个答案一样正确,都是按值排序的。稳定和不稳定在这里不适用,因为没有以前的订单要保留。
  • @Goyo:嗯,有,但是由于随机哈希种子,这个顺序会改变解释器的每次新调用。
  • @MartijnPieters 我的意思是代码没有定义订单。但你是对的,在特定的执行中会有一个顺序,在某些情况下稳定性很重要。
  • Hackerrank 任务描述还包括一个你可能忽略的规范:“如果出现次数相同,则按升序对字符进行排序。

标签: python sorting dictionary


【解决方案1】:

Python 字典是无序的。当您遍历它们的内容时,顺序取决于实现,请参阅Why is the order in dictionaries and sets arbitrary?

仅按值对项目进行排序,因此如果您以任意顺序列出项目,有时('a', 2) 对将排在第一位,有时('c', 2) 对排在第一位。

如果您想稳定顺序,也可以通过对键进行排序来打破值之间的联系。

你的挑战状态:

按出现次数的降序对输出进行排序。
如果出现次数相同,则按升序对字符进行排序。

所以需要先按值排序,再按键,而这两个之间的方向不同

您可以通过两次排序或按分数排序来实现:

# Sort forward by key, to produce a stable order between keys
by_key = sorted(ch_dict.items(), key=lambda d: d[0])
# Sort backward by value, ties are left in the original order, so by key
result = sorted(by_key, key=lambda d: d[1], reverse=True)

或一步到位:

sorted(ch_dict.items(), key=lambda d: (-d[1], d[0]))

所以按负数个数排序,然后按key排序,不要倒序。

请注意,挑战实际上只要求前三个字符。挑战不使用大量输入,但如果有,那么使用排序实际上是低效的。您不需要对 所有 键值对进行排序,只需要对前 3 个进行排序。您将如何只获得前 3 个?您可以使用heap queue,它可以有效地为您提供任何序列的前 N ​​个:

import heapq

result = heapq.nsmallest(3, ch_dict.items(), key=lambda d: (-d[1], d[0]))

排序需要 O(NlogN) 时间(N 是字典的大小),heapq 需要 O(NlogK) 时间,N 是相同的值,但 K 是顶部项目的计数;这里是 3。对于一个包含 10.000 个项目的字典,完成排序大约需要 133k 步,但堆队列只需要 16k 步。这将快 10 倍!

【讨论】:

    【解决方案2】:

    问题出在这里:

    key=lambda d:d[1]
    

    键只考虑第二个值,而不是同时使用两个值。

    【讨论】:

      【解决方案3】:

      字典是无序的。您仅按值对输出进行排序,但由于原始字典中不保证键的顺序,因此输出中每个值的顺序可能会有所不同。

      您可以通过同时订购来解决此问题:

      sorted(ch_dict.items(), key=lambda d: (d[1], d[0]), reverse=True)
      

      【讨论】:

      • 这会产生错误的挑战顺序,需要按升序而不是降序对平局进行排序。
      【解决方案4】:

      dict.items 可以按任何顺序返回(键,值)对,具体取决于实现或键插入顺序等细节。 sorted 然后以 dict.items 返回它们的顺序遍历这些对。

      如果您想要确定性的输出,请使用key=lambda d: (d[1], d[0]) 以便在值恰好相同时按字典顺序按键对 (key, value) 对进行排序。

      (如果您使用的是 Python 2,key=lambda key, value: (value, key) 看起来更好。)

      【讨论】:

        【解决方案5】:

        sorted() is actually stable,因为它保留了与您提供的键函数提取的键相同的项目的顺序——在这种情况下,键就是值。但是由于dicts 是无序的,因此对于具有相同值的项目,保留的顺序是未定义的。

        一种解决方案是按(value, key) tuples 排序:

        result = sorted(ch_dict.items(), key=lambda d: (-d[1], d[0]))
        

        注意删除的反转参数,取而代之的是否定值,因为您似乎希望按升序对键进行排序,按降序对值进行排序。

        【讨论】:

          【解决方案6】:

          在 Hackerrank 层次结构中,您位于 Collections 部分。所以解决方案可能是:

          #!/bin/python3
          import sys,collections
          if __name__ == "__main__":
              s = 'abcdebbca' # input().strip()
              res=collections.Counter(s).items(s)
              sortres= sorted ( res, key=(lambda x : (-x[1],x[0])))
              for k,v in sortres[:3] : print k,v
          

          sortres= sorted ( res, key=(lambda x : (-x[1],x[0]))) 行是必要的,正如@Martijn Pieters 所解释的那样。

          编辑

          由于问题来自dict,另一个答案仅使用listssetssorted 稳定性:

          import sys
          
          if __name__ == "__main__":
              s = raw_input().strip()
              set_k, list_kv = set() , list() 
              for x in sorted(s):
                  if x not in set_k:
                      if set_k : list_kv.append((-count,val))
                      set_k.add(x)
                      count , val = 0 , x
                  count+=1
              for k,v in sorted(list_kv)[:3] : print v,-k
          

          【讨论】:

          • Counter.most_common() 不包括排序中的键,它很容易出现同样的问题。根据当前的随机哈希种子,ca 将首先列出(在 Python
          • 那仍然行不通。 'aaabbbcccddde' 呢?现在您有 四个 最常见的字符,但只需要列出 a、b 和 c。您的代码最终会得到一个随机子集。
          • 再次感谢。它现在通过了 Hackerrank 测试。
          • 是的,现在您的答案与这里的其他几个人相同。 :-) 计数器不再是那么大的差异化因素(除了快速计数代码)。
          猜你喜欢
          • 1970-01-01
          • 2019-02-08
          • 2021-05-01
          • 1970-01-01
          • 2014-06-24
          • 2021-08-25
          • 1970-01-01
          • 1970-01-01
          • 2017-12-17
          相关资源
          最近更新 更多