【问题标题】:Retrieve keys from hash-table, sorted by the values, efficiently从哈希表中检索键,按值排序,有效
【发布时间】:2013-06-12 12:59:54
【问题描述】:

我正在使用 Emacs Lisp,但已加载 cl 包,用于一些常见的 lisp 功能。

我有一个包含多达 50K 条目的哈希表,整数键映射到三元组,类似这样(但实际上是 lisp):

{
  8   => '(9  300 12)
  27  => '(5  125 9)
  100 => '(10 242 14)
}

三元组中的第二个值是在构建哈希表的复杂算法期间计算的分数。我需要收集一个常规的 lisp 列表,其中包含哈希中的所有键,按分数排序(即所有键按值的 cadr 排序)。

所以对于以上内容,我需要这个列表:

'(27 100 8)

我目前分两个阶段来做这件事,感觉效率比需要的要低。

有什么好办法吗?

我当前的解决方案使用maphash 将键和值收集到两个新列表中,然后以正常方式执行sort,引用谓词中的分数列表。然而,感觉就像我可以将集合和排序结合在一起。

编辑 |我也不喜欢使用哈希表,尽管我确实需要整数键的恒定访问时间,它们不是线性间隔的。

编辑 2|看起来实现二叉树排序可以工作,其中树中的标签是分数,值是键......这样我在映射哈希时进行排序。

...继续阅读关于排序算法的维基百科页面

【问题讨论】:

  • 你是用 Elisp 还是 Common Lisp 编程?
  • 我正在使用 elisp,但我确实加载了 cl(通用 lisp)包,以实现一些(非常)基本的通用 lisp 兼容性。
  • 请注意 cl 已弃用;它给人一种 CL 兼容性的错觉,但存在一些细微的错误和不兼容性。当心!
  • EmacsWiki 有一个例子:emacswiki.org/emacs/ElispCookbook#toc45

标签: emacs lisp elisp


【解决方案1】:

基本上,您的解决方案是正确的:您需要将密钥收集到一个列表中:

(defun hash-table-keys (hash-table)
  (let ((keys ()))
    (maphash (lambda (k v) (push k keys)) hash-table)
    keys))

然后对列表进行排序:

(sort (hash-table-keys hash-table)
      (lambda (k1 k2)
        (< (second (gethash k1 hash-table))
           (second (gethash k2 hash-table)))))

可以将键收集与排序结合起来:您需要将键收集到树中,然后“展平”树。但是,这仅在您处理非常大的表时才有意义。此外,由于 Emacs Lisp 编译为字节码,您可能会发现使用内置的 sort 仍然比使用树更快。还要考虑开发成本 - 您将需要编写其价值主要用于教育的代码。

深入研究,收集密钥会分配密钥列​​表(无论如何,您肯定需要它来获得结果),sort“就地”运行,因此“简单方法”几乎是最好的。

“树”方式将分配树(与所需的键列表相同的内存占用)并填充和展平它将与“收集+排序”方式相同的O(n*log(n)) 过程。然而,保持树平衡,然后“就地”展平它(即,不分配新列表)并不是一个简单的练习。

底线是:KISS

【讨论】:

  • 嗯,这绝对比我正在做的简单,而且现在我在代码中看到它非常明显(显然我需要睡眠),尽管我确实想知道我是否可以避免最初的 50K 操作收集键并将排序与这些键的集合结合起来。
  • 感谢您在编辑中关于树排序的注释。我以前在 lisp 中做过二叉树,只是从来没有为了排序。我会试一试,并将结果与​​仅使用排序进行比较。你说得对,理论复杂性的权衡可能会被 C 语言和解释语言中的另一个权衡所抵消。
  • 理论上是没有胜利的。最多你会赢得n 的密钥收集操作,但与n*log(n) 的总成本相比相形见绌。您将花费数小时编写和调试代码,而不会提高性能。不过,这可能是一项有价值的教育练习。
猜你喜欢
  • 2014-01-02
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 2012-03-06
  • 2010-10-20
  • 2011-07-07
  • 1970-01-01
  • 2011-10-30
相关资源
最近更新 更多