代码之家  ›  专栏  ›  技术社区  ›  Leandro Zanarkand

显示列表

  •  0
  • Leandro Zanarkand  · 技术社区  · 7 年前

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

    (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 回复  |  直到 7 年前
        1
  •  3
  •   Silvio Mayolo    7 年前

    loop 宏。您可以在中阅读有关此宏的所有详细信息 the GigaMonkeys book ,但我们将在这里讨论解决此问题所需的部分。让我们从函数定义开始。

    (defun lenguages (names)
      ...)
    

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

    (loop with hash = (make-hash-table)
          for entry in names
          for name = (car entry)
          do ...
          finally ...)
    

    宏非常强大,有自己的语言。这个 with for 定义迭代变量。循环将以 entry 绑定到的每个条目 names 当条目用完时将停止。第三行是另一个局部变量,但与 对于 变量每次都会反弹,所以每次迭代时 name 将是 do 块包含将在每次迭代中执行的任意Lisp代码,以及 finally 包含要在循环结束时执行的Lisp代码块。

    块,我们想将该人的名字添加到他们知道的每种语言的哈希表条目中,因此我们需要另一个

    (loop for lang in (cdr entry)
          do (push name (gethash lang hash)))
    

    这个循环进入 nil 如果哈希键不存在,则将元素前置到

    现在,当这个循环完成时,哈希表将包含所有语言和键,以及知道它们是值的人的列表。这是您想要的数据,但不是您想要的格式。事实上,如果我们把这个放在 最后

    (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)))
    

    相反,让我们再进行一次循环,将此哈希表转换为您想要的列表。这是我们想要的 最后 立即阻止。

    (return (loop for key being the hash-keys of hash using (hash-value value)
                  collect (list key value)))
    

    这使用了相对模糊的 being 的语法 宏有趣的特性:它试图为常见用例提供原语,例如将值累积到列表中。在这种情况下它很有用。

    这是完整的代码块。

    (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)))))
    

    available online for free


        2
  •  1
  •   user5920214 user5920214    7 年前

    另一个答案很好:这是一个不使用 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)))))))))
    

    (另请注意 ,这可能会更清楚一些。)