一种基于迭代的任务这样是最适合的Common Lisp的无比强大loop
宏。您可以在the GigaMonkeys book中阅读关于此宏的所有详细信息,但我们只会在此处查看您需要解决此问题的部分。我们从函数定义开始。
(defun lenguages (names)
...)
在这里面,我们要遍历提供的列表。我们也想收集一些密钥,所以一个哈希表将是有用的。散列表(在许多其他语言中称为映射或字典)以省时的方式将键与值关联起来。
(loop with hash = (make-hash-table)
for entry in names
for name = (car entry)
do ...
finally ...)
loop
宏是非常强大的,并有一个语言所有。子句with
声明了一个局部变量,在本例中是一个哈希表。第一个for
定义了一个迭代变量。循环将以entry
运行,并绑定到names
的每个条目,并在条目用完时停止。第三行是另一个局部变量,但与with
不同,for
变量每次都会反弹,因此在每次迭代中,name
将是entry
的第一个元素。 do
块包含将在每次迭代中执行的任意Lisp代码,并且finally
包含要在循环结束时执行的Lisp代码块。
在do
块内部,我们希望将人员姓名添加到他们认识的每种语言的哈希表项中,因此我们需要另一个loop
来循环已知语言。
(loop for lang in (cdr entry)
do (push name (gethash lang hash)))
该循环进入外部块的do
块内。对于该人员已知语言列表中的每种语言,我们希望将该人员的姓名加入该语言的哈希值中。通常,我们必须考虑散列键不存在的情况,但幸运的是,如果散列键不存在,则Common Lisp默认为nil
,并且将元素前置到nil
会创建一个元素列表,这正是我们想要的。
现在,当这个循环完成时,散列表将包含所有的语言和键以及知道它们作为值的人员列表。这是你想要的数据,但它不是你想要的格式。事实上,如果我们把这个在我们finally
块
(return hash)
我们会得到一些半有用的输出*,它告诉我们,我们是在正确的轨道上。
#S(HASH-TABLE :TEST FASTHASH-EQL ((TOM GERMAN FRENCH) . (TOM TOM))
((AMANDA ITALIAN SPANISH ENGLISH) . (AMANDA AMANDA AMANDA))
((DAVID SPANISH GERMAN) . (DAVID DAVID)))
而是让我们再做一个循环来将此散列表转换为您希望它的列表。以下是finally
区块中我们想要的内容。
(return (loop for key being the hash-keys of hash using (hash-value value)
collect (list key value)))
这使用了相对模糊being
语法的宏loop
,其允许在哈希表容易迭代。您应该将其读作:对于每个键值对,将包含密钥后跟该值的列表收集到列表中,然后返回累积列表。这是另一个有趣的宏功能:它试图为常见用例提供原语,例如将值累加到列表中。它在这种情况下派上用场。
下面是完整的代码块。
(defun lenguages (names)
(loop with hash = (make-hash-table)
for entry in names
for name = (car entry)
do (loop for lang in (cdr entry)
do (push name (gethash lang hash)))
finally (return (loop for key being the hash-keys of hash using (hash-value value)
collect (list key value)))))
链接我刚才提供的是对Common Lisp的,这是available online for free的GigaMonkeys书。我强烈建议阅读它,因为它是Common Lisp所有内容的惊人参考。特别是如果你刚刚开始,那本书可以真正让你朝着正确的方向前进。
*您的输出格式可能不同。实现选择如何输出结构。
它的一个好答案。你可能会考虑使用push来代替setf viz(push name(gethash lang hash'())它更简洁一点。 –
Gah你说得对。我的头脑自动思考“我希望我在这里有'appendf''忘了“推”是一件事。 –
谢谢你的答案,作品完美!我的最后一个疑问是,如果存在一种方式来显示结果,就像我放置的例子。我尝试过格式t但不工作。 –