提出一份清单

时间:2017-07-18 01:54:50

标签: lisp common-lisp

我有这个名字和不同语言的列表

(setq l '((david spanish german)
          (amanda italian spanish english)
          (tom german french)))

我想用函数做下一个:对于每种语言,我需要与每种语言相关的每个名字。

例如,如果我使用列表L:

调用该函数
(lenguages L)

我想表明这一点:

  ( (english (amanda))
    (spanish (david amanda))
    (italian (amanda))
    (german(david tom))
    (french(tom))
  )

我知道如何做到这一点,但它只显示了一个项目。

(defun lenguages(names)
   (cond((null names) nil)
    ((list (cadar names) (list (caar names))))))

这最后一个功能只显示(spanish (david))

2 个答案:

答案 0 :(得分:3)

像这样的基于迭代的任务最适合Common Lisp非常强大的loop宏。您可以在the GigaMonkeys book中阅读有关此宏的所有详细信息,但我们会在此处查看此问题所需的部分。让我们从函数定义开始。

(defun lenguages (names)
  ...)

在此内容中,我们希望迭代提供的列表。我们还想收集一些密钥,因此哈希表会很有用。散列表(在许多其他语言中称为映射或dicts)以时间有效的方式将键与值相关联。

(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语法,允许在哈希表上轻松迭代。您应将其读作:对于每个键值对,收集包含键的列表,然后将值放入列表中,然后返回累积列表。这是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的GigaMonkeys书籍,available online for free。我强烈鼓励通读它,因为它是所有Common Lisp的惊人参考。特别是如果你刚刚开始,那本书真的可以让你朝着正确的方向前进。

*您的输出格式可能因此而异。实现选择如何输出结构。

答案 1 :(得分:1)

另一个答案很好:这是一个不使用loop或中间哈希表的版本,而是直接构建所需的关联列表。值得将它的效率与基于散列表的效率进行比较:它在搜索列表中做了更多的搜索,但实际上,对于少量数据,这些事情通常更快(哈希表在很多方面都有很大的开销)实现)并且它总是使用更少的存储,因为它不构建它不返回的结构。

请注意:

  • 这将以不同的顺序返回结果(它不依赖于哈希顺序);
  • 这将确保每种语言每个人只出现一次:(languages '((david german german)))((german (david)))而非((german (david david))) - 它会支付一些性能成本(对于大数据可能会有所改善)这样做会使用更多哈希表。

所以,这是:

(defun languages (people)
  (let ((langs '()))                    ;the map we are building
    (dolist (pl people langs)
      (destructuring-bind (person . person-languages) pl
        (dolist (lang person-languages)
          (let ((entry (assoc lang langs)))
            (if (not (null entry))
                ;; there's an entry for lang: add the person to it
                (pushnew person (second entry))
              ;; there is no entry, create one with person in it
              (setf langs `((,lang (,person)) ,@langs)))))))))

(另请注意,基于loop的版本可以使用loop的解构,这可能会更清晰一点。)