【问题标题】:Homework: Lisp items that appear more than once in a list作业:在列表中出现多次的 Lisp 项目
【发布时间】:2009-12-08 15:15:34
【问题描述】:

给定一个列表,我试图返回一个新列表,其中仅包含在我作为参数收到的第一个列表中出现多次的项目。

我做了以下事情:

(defun myf (lista)
  (if (endp lista)
      nil
      (if (member (first lista) (rest lista))
          (append (list (first lista)) (myf (rest lista)))
          (myf (rest lista))))) 

如果我运行以下命令:(myf '(A A B A B C)),它将返回 (A A B)。 我怎样才能让它只返回一次项目(即没有双“A”)?

【问题讨论】:

  • 链式 IF 形式最好转换成 COND。

标签: lisp duplicates


【解决方案1】:

一旦您发现一个字母不止一次出现在列表中,您就不需要再次检查它,因此您不需要它在列表的其余部分中。所以你可以修改剩余的列表...

注意:这个答案故意有点含糊,因为它是用于家庭作业的。 :)

【讨论】:

    【解决方案2】:

    问题似乎是您正在检查列表的第一个元素是否在列表的尾部并将其附加到结果列表中。当您到达A的第二个实例时,问题出现了,当检查时,它在尾部处于尾部,因为列表中有第3个实例。如果输入列表有 4 个 A,那么它将返回其中 3 个。

    所以要解决这个问题,需要检查 A 是否已经是结果列表的成员。也就是说,如果 first lista 不是 list 的成员,则将 first lista 附加到 list。

    【讨论】:

      【解决方案3】:

      Vincent、Gishu 和 Jerry 暗示您需要在添加之前检查该项目是否已经在结果列表中,而 Derek 暗示您可以在看到某个项目重复时修改原始列表。

      在此处阅读函数 adjoinremove 的文档:

      http://www.lispworks.com/documentation/HyperSpec/Body/f_adjoin.htm

      http://www.lispworks.com/documentation/HyperSpec/Body/f_rm_rm.htm

      HyperSpec 是一个非常有用的参考,请将其添加到您的书签中。

      这两个函数不会修改它们的参数,而是将结果作为新列表返回。还有其他函数会修改它们的参数,因此可能会更有效,但此时也许你不应该担心它们。

      好的,我希望在我写这篇文章的时候你已经明白了。如果没有,请继续尝试,这是您真正学习的唯一方法。

      现在我想和你谈谈另一种方法,那就是通过递归调用传递结果。

      我们的函数 repeated 只会调用一个辅助函数 repeatedr 来完成实际工作,并将初始空结果 '() 传递给它:

      (defun repeated (lst)
        (repeatedr lst '()))
      

      现在让我们定义辅助函数,它接收两个参数:搜索重复项的列表,以及我们将累积重复项的结果列表。

      (defun repeatedr (lst result)
        (if (null lst)
          result
          (if (member (first lst) (rest lst))
            (repeatedr (rest lst) (adjoin (first lst) result))
            (repeatedr (rest lst) result))))
      

      当条件(member (first lst) (rest lst)) 成立时,我们将邻接第一项到result,并且邻接的结果将作为第二个参数传递给递归调用;否则我们只需将result 原样传递给递归调用。

      当列表最终为空(if (null lst) 时,我们将返回result

      > (repeated '(a a b a b c))
      (B A)
      

      请注意,结果是(B A),也许您期望它是(A B)。尝试用笔和纸跟踪递归调用的执行和每次调用的参数值,这将是一个很好的练习,您必须使用adjoin 来了解它的行为。如果你想得到相反的结果,你可以像这样修改函数:

      (defun repeatedr (lst result)
        (if (null lst)
          (reverse result)
          (if (member (first lst) (rest lst))
            (repeatedr (rest lst) (adjoin (first lst) result))
            (repeatedr (rest lst) result))))
      

      反转是递归结束时的结果。

      现在,在继续之前从列表中删除重复元素的建议怎么样?我们可以这样写我们的函数:

      (defun repeatedr (lst result)
        (if (null lst)
          result
          (if (member (first lst) (rest lst))
            (repeatedr (remove (first lst) lst) (cons (first lst) result))
            (repeatedr (rest lst) result))))
      

      尝试在 REPL 中使用remove

      > (remove 'a '(a b c d a e f b a d))
      (B C D E F B D)
      

      请注意,我们不再连接结果,而是使用(cons (first lst) result) 创建一个新的“cons cell”consadjoin 做同样的事情,除了 adjoin 只有当它不在列表中时才会添加该值。

      希望这能给你带来一些乐趣。

      【讨论】:

      • 感谢您的精彩解释
      【解决方案4】:

      问题似乎是您在添加之前没有检查该元素是否存在于输出列表中。

      【讨论】:

      • 是的,这就是我不明白的,如何在不使用变量的情况下检查它(即:set、let 等)
      • 选择 A 作为副本后,如何从“列表的其余部分”中删除 A 的所有实例?
      【解决方案5】:

      现在,对于列表中的每个元素,如果该元素不包含在列表的其余部分中,则您将其添加到结果中。

      要获得您显然想要的结果,如果列表中的每个元素尚未包含在结果中,您需要将其添加到结果中。

      【讨论】:

        【解决方案6】:

        如果订单不是问题,那么您也可以这样做:

        (defun my-fun-no-order (lst)
           (loop for (item . rest) on lst
            when (= (count item rest) 0)
            collect item))
        

        在这里,我们迭代一个列表,将其吐入当前正在迭代的项目和其余待检查的项目。 when 子句决定了我们收集物品的情况。

        【讨论】:

          猜你喜欢
          • 2020-10-29
          • 1970-01-01
          • 2015-03-30
          • 2019-06-13
          • 2020-04-19
          • 1970-01-01
          • 1970-01-01
          • 2011-10-22
          • 1970-01-01
          相关资源
          最近更新 更多