【问题标题】:New to Racket: why is the if statement affecting the return?Racket 新手:为什么 if 语句会影响返回?
【发布时间】:2017-01-13 08:33:08
【问题描述】:

我刚开始学习 Racket,所以我仍在尝试找出语言的复杂性。我正在尝试在列表中实现我自己的搜索功能。如果函数找到它,则返回索引,否则返回-1。

(define (find-index list item)
  (if (equal? (length list) 1)
      (if (equal? (first list) item) 0 1)
      (if (equal? (first list) item)
          0
          (+ 1 (my-search (rest list) item)))))

所以 find-index 函数是一个递归函数,它遍历列表以查找与“项目”等效的项目。我这样写,如果列表中有 4 个元素,该函数可以返回 0-4 之间的任何数字。

(define (my-search list item)
  (define length (my-length list))
  (define index (find-index list item))
  (if (= index length) -1 index))

我的想法是,如果 find-index 函数返回一个等于列表长度的数字,则表示该函数没有找到该项目,因此 my-search 函数应该返回 -1。

但是,当我输入

(my-search (list "apple" "barbecue" "child" "demon" "enter") "fire")

我得到的结果是 3,而不是 -1。如果我在 if 语句之前打印索引,则索引是 3 而不是 5。如果

(if (= index length) -1 index))

不是我的搜索功能的一部分,那么一切都很好。

我认为 index 是函数本身的 id,而不是函数的结果。但是,我不明白为什么这会影响 my-search 的返回结果。有人愿意解释一下这个问题吗?

此外,欢迎任何风格批评。我想知道我是否不遵守约定。

【问题讨论】:

    标签: if-statement scheme racket


    【解决方案1】:

    奇怪的行为是由于find-index 正在调用my-searchfind-index(相互递归!)这一事实引起的。在某些时候,额外的if 导致递归过早结束。解决方法:将find-index过程中对my-search的调用替换为find-index

    既然已经解决了,我们可以编写一个过程来在列表中查找元素的索引或发出未找到的信号,如下所示:

    (define (find-index lst item)
      (cond ((empty? lst) #f)
            ((equal? (first lst) item) 0)
            (else
             (let ((result (find-index (rest lst) item)))
               (if result (add1 result) #f)))))
    

    让我们看看上述如何改进您的程序:

    • 构造具有多个条件的过程的首选方法是使用cond
    • 不应使用list 作为参数名称,它会与同名的内置过程冲突
    • 出于同样的原因,您不应将 length 称为本地定义
    • 使用length 来检查我们是否超出了列表并不是一个好主意,构建良好的递归会解决这个问题,而不必再次遍历列表
    • 通常使用#f 表示搜索过程未找到它要查找的内容
    • 在结构良好的列表递归中,您应该检查列表是否为空,通常这是我们编写的第一个基本情况 - 如果传递一个空列表,您的过程将失败
    • 我们使用let 来声明局部变量,在这种情况下,避免调用两次递归是有意义的
    • 使用(add1 x),比(+ 1 x)更惯用

    但是等等,我们可以做得更好!上面的解决方案可以重写为 tail-recursive 样式;通过确保递归调用是我们做的最后一件事,我们的过程将使用常量空间,并且它将与传统编程语言中的循环一样高效。诀窍是传递一个带有要返回的值的额外参数(在本例中为索引)。为简洁起见,我将使用命名为 let

    (define (find-index lst item)
      (let loop ((lst lst) (idx 0))
        (cond ((empty? lst) #f)
              ((equal? (first lst) item) idx)
              (else (loop (rest lst) (add1 idx))))))
    

    您可以验证这两个程序是否如宣传的那样工作:

    (find-index (list "apple" "barbecue" "child" "demon" "enter") "fire")
    => #f
    (find-index (list "apple" "barbecue" "child" "demon" "enter") "child")
    => 2
    

    【讨论】:

    • 这是一个非常有用的回复。我很欣赏帮助我改进代码的要点。我还了解到尾递归是正确的方法。但是,它仍然没有回答我的问题,即为什么 if 语句会影响 index 的结果,即使在 if 语句之前也是如此。
    • 如果我删除 if 语句和 println 索引,则索引为 5(假设在列表中找不到该项目)。如果存在 if 语句,则 println 索引变为 3。不,递归结构正确。如果 my-search 函数的最后一个 if 语句不存在,则如果在列表中找不到该项目,我会得到 5。
    • (define (my-search list item) (define length (my-length list)) (find-index list item)) 如果找不到大小为 4 的列表,将返回 5
    • @VivienK 我发现了这个错误。是的,你的递归肯定是不正确的:find-index 正在调用 my-search,而 find-index 正在调用(相互递归!)。在某些时候,额外的if 导致递归过早结束。解决方案:将find-index过程中对my-search的调用替换为find-index
    • 天哪,谢谢。我觉得自己像个疯子。感谢您花时间调试我的代码。
    【解决方案2】:

    这就是我要解决的问题。

    (define (find-index L item) ; L your list. item the item for which you want the index 
        (define (aux L res) ; Auxiliary function. L your list. item the item for which you want the index 
            (cond ((null? L) -1) ; No thing was found, return -1.
                  ((eq? (car L) item) res) ; If the item is equal to the current item then return the position.
                  (else (aux (cdr L) (add1 res))))) ; Move on to the next item in the list and increment the position.
    (aux L 0)) ; Call of the auxiliary function that will be doing the job
    

    试运行...

    (define L '(a b c d))
    

    元素不在列表中

    (find-index L 'e)
    

    输出:-1

    元素“d”

    (find-index L 'd)
    

    输出:3

    【讨论】:

      【解决方案3】:

      这是find-index 的一个版本,它尝试使用与原始示例相同的样式。而不是list,我使用xs(“xes 列表”的缩写)。

      请注意,最好使用 false 值#f 表示“未找到”。

      (define (find-index xs item)
        (if (empty? xs)
            -1                                                      ; not found
            (if (equal? (first xs) item)
                0                                                   ; found at beginning
                (let ([index-in-rest (find-index (rest xs) item)])  ; index in rest of list
                  (if (= index-in-rest -1)
                      -1                                            ; not found in rest of list
                      (+ 1 index-in-rest))))))                      ; add 1 because we skipped 
                                                                      the first element
      

      【讨论】: