2017-07-18 101 views
0

我有名字的列表和不同的语言呈现列表

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

我希望做下一个功能:为每一种语言,我需要用每一种语言relationed每一个名字。

例如,如果我打电话与列表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))

回答

3

一种基于迭代的任务这样是最适合的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所有内容的惊人参考。特别是如果你刚刚开始,那本书可以真正让你朝着正确的方向前进。


*您的输出格式可能不同。实现选择如何输出结构。

+2

它的一个好答案。你可能会考虑使用push来代替setf viz(push name(gethash lang hash'())它更简洁一点。 –

+0

Gah你说得对。我的头脑自动思考“我希望我在这里有'appendf''忘了“推”是一件事。 –

+0

谢谢你的答案,作品完美!我的最后一个疑问是,如果存在一种方式来显示结果,就像我放置的例子。我尝试过格式t但不工作。 –

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的解构,这可能是一个更清晰一点。)

+0

感谢您的帮助!,有很多不同版本的解决方案! –