Как add1 используется в этой программе Racket?

Я пытаюсь понять, как add1 используется в этом примере рекурсии:

(define (my-length a-list)
  (if (empty? a-list)
      0
      (add1(my-length (rest a-list)))))

Если задано (my-list '(1 2 3 4)), программа вернет число 4.

Я понимаю, что после каждой итерации функции my-lenght код будет разбирать список до тех пор, пока список не станет пустым. Чего я не понимаю, так это того, как (add1) добавляет каждую итерацию к функции.


person Alex_adl04    schedule 16.06.2014    source источник
comment
Я думаю, что другие ответы отвечают на это достаточно, но, основываясь на ваших комментариях, мне интересно, может ли это помочь: (add1 ...) === (+ 1 ...). Итак, (length l) === (+ 1 (length (rest l))), если l — непустой список, и 0, если l — пустой список. Итак, (length '(x y z)) === (+ 1 (+ 1 (+ 1 0))) === 3.   -  person Joshua Taylor    schedule 16.06.2014


Ответы (4)


Ради другого объяснения:

Здесь мы начинаем

(my-length '(a b c d))

Заполняя аргумент в функцию, это превращается в

(if (empty? '(a b c d))
  0
  (add1 (my-length (rest '(a b c d)))))

Очевидно, что '(a b c d) не пусто. Таким образом, оценивается "else" случай if:

(add1 (my-length (rest '(a b c d))))
(add1 (my-length '(b c d)))

Теперь, что означает (my-length '(b c d))? Что ж, если мы скопируем и вставим тело функции в нашу последнюю строку, мы получим:

(add1 (if (empty? '(b c d))
        0
        (add1 (my-length (rest '(b c d))))))

Продолжая...

(add1 (add1 (my-length (rest '(b c d)))))
(add1 (add1 (my-length '(c d))))

Так продолжается до тех пор, пока мы не получим пустой список:

(add1 (add1 (add1 (add1 (if (empty? '())
                              0
                              (add1 (my-length (rest '()))))))))

'() пусто, поэтому оператор if возвращает 0:

(add1 (add1 (add1 (add1 0))))
(add1 (add1 (add1 1)))
(add1 (add1 2))
(add1 3)
4

Как будто мы погружаемся в функцию все глубже и глубже, потому что на каждый шаг, который мы делаем, нам нужно сделать еще один, чтобы действительно оценить то, что у нас есть.


Чем это отличается от (add1 '(a b c d))? Из того, как проводилась оценка, видно, что add1 на самом деле никогда не применялось к a, b, c или d. Мы даже не проверили, что было в списке. У вас мог бы быть список из четырех списков, каждый из которых содержал бы списки списков, и он все равно оценивал бы.

В частности, сказать (add1 '(a b c d)) все равно, что сказать «Сколько будет 1 плюс клубника?», а ваша функция больше похожа на:

«Сколько вещей в этом списке?»

«Ну, если ты ничего не видишь сверху ((first my-list)), то точно ничего нет».

"Хорошо, хорошо... Я вижу "а" сверху!"

"Хорошо! Убери это. Это как минимум 1 в списке."

"Хорошо, но сколько в остальных?"

«Как насчет того, чтобы попробовать то же самое еще раз? Просто продолжайте вынимать элементы из верхней части списка, пока список не станет пустым. Затем мы соберем его обратно и будем считать по мере продвижения».

«Хорошо, я вынул их все. Теперь я собираюсь вставить их обратно: d — это 1, c — это 2, b — это 3 и a — это 4!»

"Ну вот, 4 в списке!"

person ಠ_ಠ    schedule 17.06.2014

Если ваша проблема связана с рекурсией, я предлагаю вам trace повторяющиеся вызовы my-length. Например в DrRacket пропишите в окне определения следующее:

#lang racket

(require racket/trace)

(define (my-length a-list)
  (if (empty? a-list)
      0
      (add1(my-length (rest a-list)))))

(trace my-length)
(my-length '(a b c d))

Нажмите Run и наблюдайте:

>(my-length '(a b c d))
> (my-length '(b c d))
> >(my-length '(c d))
> > (my-length '(d))
> > >(my-length '())
< < <0
< < 1
< <2
< 3
<4
4

Вы можете видеть, что (my-length '(a b c d)) вызывает (my-length '(b c d)) (потому что это и есть (rest '(a b c d))), который вызывает (my-length '(c d)) и так далее. Когда вызывается (my-length '()), он возвращает 0 (первая ветвь if). Это возвращаемое значение передается (возвращается) предыдущему вызову, (my-length '(d)), который add1s ему, и, таким образом, возвращает 1. Это последнее возвращаемое значение возвращается (my-length '(c d)), который возвращает 2, и так далее до (my-length '(a b c d)), который, наконец, возвращает 4.

Важным моментом здесь является то, что add1 применяется к возвращаемому значению рекурсивного вызова и, следовательно, должен дождаться возврата этого вызова, прежде чем сможет что-то сделать с этим возвращаемым значением. Другими словами, add1 применяются в обратном порядке по сравнению с тем, как обрабатывается список.

Дополнительную информацию и Примеры.

person Metaxal    schedule 16.06.2014

add1 добавляет 1 к своему аргументу и возвращает эту сумму. Таким образом, если длина остатка списка равна 3, (add1 (my-length (rest a-list))) добавит 1 к 3 и вернет 4.

В базовом случае пустого списка my-length возвращает 0.

(my-length '(4)) вызывает (my-length '()) в рекурсии, которая возвращает 0, затем вызывает add1 для возврата 1.

(my-length '(3 4)) вызывает (my-length '(4)) в рекурсии. Как указано выше, это возвращает 1, затем вызывает add1 для этого, которое возвращает 2.

и так далее.

person Barmar    schedule 16.06.2014
comment
Мне все еще трудно представить, что хранит счетчик количества итераций (add1). В своем последнем примере вы сказали, что он возвращает 1 из-за возвращенного 0, как (мой-список) получает 0? Если код повторяется до тех пор, пока список, указанный в my-list, не станет пустым, не будет ли это означать, что then(empty? a-list) вернет 0, что приведет к выходу из кода? - person Alex_adl04; 16.06.2014
comment
Похоже, вы не понимаете рекурсию. Каждый подсписок — это отдельный вызов my-length. Он возвращает значение вызову с более длинным списком. - person Barmar; 16.06.2014
comment
Я действительно не знаю, я новичок в программировании. Обычно я объясняю это в своих первых сообщениях, но иногда это редактируется, поэтому я перестал этого делать. В настоящее время я использую Realm of Racket. Я прошел несколько базовых курсов по программированию, но до сих пор не могу понять некоторые концепции, такие как рекурсия, которая для меня очень нова. - person Alex_adl04; 16.06.2014
comment
Если вы все еще не поняли, я предлагаю вам вернуться к учебнику и перечитать раздел о рекурсии. Это важная концепция, которую должны понимать все программисты. - person Barmar; 16.06.2014
comment
Я в настоящее время. Обычно я останавливаюсь на концепциях, которых не понимаю, пока не получу в них полезное представление. По какой-то причине мне невероятно трудно понять это конкретное. Он ценит усилия в попытке помочь новичку. - person Alex_adl04; 16.06.2014

Оценка выглядит следующим образом:

   (my-list '(1 2 3 4))
-> (add1 (my-list '(2 3 4)))
-> (add1 (add1 (my-list '(3 4))))
-> (add1 (add1 (add1 (my-list '(4)))))
-> (add1 (add1 (add1 (add1 (my-list '())))))
-> (add1 (add1 (add1 (add1 0))))
-> (add1 (add1 (add1 1)))
-> (add1 (add1 2))
-> (add1 3)
-> 4
person uselpa    schedule 16.06.2014
comment
Это то же самое объяснение, представленное в книге. Мне все еще трудно понять, как получается, что (add1) повторяется на каждой итерации my-list, не выдавая ошибку в начале при попытке добавить число в список. Если я создам список, такой как (define a-lista '(1 2 3 4)) и затем попытаюсь добавить к нему add1 как (add1 a-lista), это выдаст мне только ошибку. Как же тогда он не оценивает в то же время, когда мой-список оценивается на его внутреннем уровне? - person Alex_adl04; 17.06.2014
comment
@ Alex_adl04 Alex_adl04 Вы правы, что (add1 '(1 2 3 4)) будет ошибкой. Однако, к счастью, этого никогда не происходит. Если аргумент равен (1 2 3 4), вы будете оценивать else часть if, то есть (add1 (my-length (rest '(1 2 3 4)))). Не беспокоясь о том, как он это делает, вы должны увидеть, что (my-length (rest '(1 2 3 4))) должно вернуть 3. Если это так, то вы в конечном итоге вызываете (add1 3), что равно 4. add1 никогда не вызывается со списком; это только называется с цифрами. - person Joshua Taylor; 17.06.2014