在Emacs Lisp中,如何获得单个哈希键?

时间:2017-08-07 01:11:56

标签: lisp hashtable elisp

我想解决的问题是在非有向图中找到最少数量的子树,其中每个节点都在一个子树中。

我的算法如下:

make a hash as follows
     key= each node, 
     value= all nodes directly accessible from the key node
   if a node has no edges it still has a key/value pair in the hash
   each edge will be represented twice, once each direction
loop until hash is empty
  get any hash key/value pair
  save the value to the working-list
  remove the key/value from the hash
  in a loop until the working-list is empty
     (when (gethash (car working-list) hash)
        concatenate value to the end of the working-list
        (remhash (car working-list) hash))
     (pop working-list)
  When the loop is finished you have removed all nodes and edges in a single subtree from the hash.
  Increment number of subtrees.
end loop until the hash is empty.
report number of subtrees

以下是代码:

(defun count-subtrees (hash)
; hash
;     key= each node, 
;     value= all nodes directly accessible from the key node
;   if a node has no edges it still has a key/value pair in the hash
;   each edge will be represented twice, once each direction

(let ((number-of-trees 0))
  (while (setf key (anykey-in-hash hash))     ; this is where I need any key in the hash
    (setf working-list (gethash key hash))
    (remhash key hash)
    (while (gethash (car working-list) hash)
      (setf working-list (append working-list 
                                 (gethash (car working-list hash))))
      (remhash (car working-list) hash)
      (pop working-list))
    (incf number-of-trees))
  number-of-trees))

我不想迭代键,我想只得到一个。

注意:

谢谢大家的回复。我正在指导这些意见。

编辑改变了我的问题,添加了“随机”一词。我不在乎它是否是随机的。回复:

(defun anykey-in-hash (hash-table)
    (block nil
       (maphash (lambda (k v) (return (values k v)))

是一个完美的解决方案。

我无意中使用了'while'(我从Paul Graham的书中得到的)而没有定义它。我希望它不会让任何人感到困惑。

我也使用setf代替let来定义变量。愚蠢的错误,我永远不会那样做。

最初我有一个所有键的列表。但在算法期间,键/值对被删除。这就是为什么我需要让任何人离开。

我也使用工作列表作为列表。 working-list包含子树中的一些节点。需要从散列中删除所有这些节点(及其子节点*和它们的子节点......)。附加到此列表是为了简化代码。在实际的应用程序中,我会使用其他一些数据结构,可能是列表列表。但我觉得这样做会增加复杂性而不会在问题的含义上添加任何内容。

*当我说孩子时,我的意思是使用节点作为哈希的关键,值是孩子的列表。

1 个答案:

答案 0 :(得分:3)

您正在设置全局变量而不事先声明它们,这是不可移植的,并且由于副作用通常不安全;更喜欢让绑定。

至于获取“随机”键,您可以像这样简单地实现anykey-in-hash

(defun anykey-in-hash (hash-table)
  (block nil
    (maphash (lambda (k v) (return (values k v)))
             hash-table)))

存储在散列表中的元素的顺序取决于实现,因此maphash将以任意(但可能是确定的)顺序迭代条目。如果你真的需要一个随机顺序,即一个可以用相同参数的函数调用进行更改的顺序,你必须收集密钥并随机选择一个。

在您的示例中,您似乎只需要计算一次键列表,而不是在while循环的每次迭代中。我个人会将所有密钥放在一个列表中, shuffle 一次,然后根据需要 pop 元素。

我最初的代码是在Common Lisp中。在CL中,Alexandria library定义shuffle(和random-elt),以及hash-table-keys(或hash-table-alist,如果您同时需要密钥和值)。

在Emacs Lisp中,您也可以轻松地使用hash-table-keys实现maphash(您可以从maphash调用的函数内部推送列表前面的键)。如果您有Emacs 24.4,则可(require 'subr-x)并致电get-hash-keys。可以像在CL中那样进行改组,你可以从alexandria(https://gitlab.common-lisp.net/alexandria/alexandria/blob/master/sequences.lisp#L90)调整算法,知道你只关心参数是列表的情况。