【问题标题】:Find position of atom - return nil if not present查找原子的位置 - 如果不存在则返回 nil
【发布时间】:2017-02-05 01:30:51
【问题描述】:

我正在尝试查找原子在列表中的位置。

预期结果:

(position-in-list 'a '(a b c d e)) 给出 0

(position-in-list 'b '(a b c d e)) 给出 1

(position-in-list 'Z '(a b c d e)) 给出零。

当项目在列表中时,我有一个函数可以正确给出位置:

(defun position-in-list (letter list)
  (cond
    ((atom list)            nil)
    ((eq (car list) letter) 0)
    (t                      (+ 1 (position-in-list letter (cdr list))))))

问题是当item不存在时它不会返回nil,好像它到达(atom list) nil它会给出这个错误:*** - 1+: nil is not a number当它unstacks时,它会尝试将值添加到nil.

有没有办法调整这个函数(保持相同的结构),以便在项目不在列表中时正确返回nil

注意事项:

  • 我知道库中有一个position函数,但我不想使用它。

  • 我知道我的问题类似于this one,但我上面提到的问题没有得到解决。

* 编辑 * 感谢大家的回答。虽然我没有必要的知识来理解你提到的所有建议,但它很有帮助。

我找到了另一个解决我的问题的方法:

(defun position-in-list (letter liste)
   (cond
      ((atom liste) nil)
      ((equal letter (car liste)) 0)
      ((position-in-list letter (cdr liste)) (+ 1 (position-in-list letter (cdr liste)))) ) )

【问题讨论】:

  • 这既晦涩又低效。既然你正在努力学习,我希望你能找到最有效、最有效的方法。 (sds已经提供了,

标签: recursion position lisp common-lisp


【解决方案1】:

一种可能的解决方案是使递归函数成为另一个函数的局部函数。最后,将 从周围的函数返回 - 因此您不需要从每个递归调用中返回 NIL 结果。

局部递归函数从函数返回

局部递归函数可以用LABELS定义。

(defun position-in-list (letter list)
  (labels ((position-in-list-aux (letter list)
             (cond
              ((atom list)               (return-from position-in-list nil))
              ((eql (first list) letter) 0)
              (t                         (+ 1 (position-in-list-aux
                                               letter (cdr list)))))))
    (position-in-list-aux letter list)))

这个RETURN-FROM 是可能的,因为从本地函数可以看到返回的函数。

递归函数返回另一个函数

还可以使用CATCHTHROW 将控制权返回给另一个函数:

(defun position-in-list (letter list)
  (catch 'position-in-list-catch-tag 
    (position-in-list-aux letter list)))

(defun position-in-list-aux (letter list)
  (cond
   ((atom list)               (throw 'position-in-list-catch-tag nil))
   ((eql (first list) letter) 0)
   (t                         (+ 1 (position-in-list-aux
                                    letter (cdr list))))))

测试函数 EQL

另请注意,按照约定,默认测试函数是EQL,而不是EQ。这也允许使用数字和字符。

【讨论】:

    【解决方案2】:

    需要检查递归调用返回的值:

    (defun position-in-list (letter list)
      (cond
        ((atom list)            nil)
        ((eq (car list) letter) 0)
        (t
         (let ((found (position-in-list letter (cdr list))))
           (and found
                (1+ found))))))
    

    请注意,此实现不是尾递归的。

    【讨论】:

      【解决方案3】:

      一般来说,提供:test 关键字参数来选择我们应该使用的相等函数很有用,所以我们这样做了。让编译器能够进行尾调用优化也很方便(注意,在 Common Lisp 中不需要 TCO,但大多数编译器会通过正确的优化设置来这样做,请查阅您的编译器手册),因此我们使用另一个关键字参数为了那个原因。这也意味着我们从最里面的调用返回的任何东西都完全按原样返回,所以我们返回一个数字还是nil都没有关系。

      (defun position-in-list (element list &key (test #'eql) (position 0))
         (cond ((null list) nil)
               ((funcall test element (car list)) position)
               (t (position-in-list element 
                                    (cdr list) 
                                    :test test :position (1+ position)))))
      

      当然,将 TCO 友好的递归封装在一个内部函数中可能会更好,所以我们(正如 Rainer Joswig 正确指出的那样)不公开内部实现细节。

      (defun position-in-list (element list &key (test #'eql)
        (labels ((internal (list position)
                    (cond ((null list) nil)
                          ((eql element (car list)) position)
                          (t (internal (cdr list) (1+ position))))))
          (internals list 0)))
      

      【讨论】:

      • 你可能想要修复错误::key -> &key, ilst -> list, :postion -> position
      • style:像这样暴露内部变量通常不是一个好习惯——尤其是当它们在其他方面无用或暴露内部实现细节时。如果我们提供关键字参数,通常的期望是调用者可以通过使用它来做一些有用的事情。
      猜你喜欢
      • 2021-11-19
      • 2021-04-04
      • 2014-06-17
      • 1970-01-01
      • 2019-05-23
      • 2017-05-19
      • 2022-01-05
      • 2012-11-21
      • 1970-01-01
      相关资源
      最近更新 更多