Мне нужна помощь, чтобы понять программу на лиспе, которая находит глубину списка

#list #recursion #lisp #common-lisp #map-function

Вопрос:

Мне нужна помощь, чтобы понять мой код теоретически. Вот моя программа на лиспе:

 (defun depth (lst)

  (if (or (null lst) (atom lst)) 0 

    (  1 (apply 'max (mapcar #'depth lst)))

  ))
 

Я знаю, что это работает с этим примером:

 (write (depth '((a (b c) d r ((t))))) -> 3
 

Я просто не могу понять утверждение » еще IF » в заявлении, которое я пробовал.

Если вы сможете мне помочь, я буду вам очень признателен. Заранее спасибо.

Ответ №1:

Вот ваш код, слегка переформатированный:

 (defun depth (value)
  (if (or (null value) (atom value))
      0
      (  1 (apply 'max (mapcar #'depth value)))))
 

Я переименовал lst (кстати , вы могли бы написать это list ) в value , потому что название сбивает с толку, поскольку предполагает, что переменная всегда является списком, что неверно. Функция depth может быть вызвана при любом значении:

 (depth "hello")
=> 0

(depth 100)
=> 0
 

Затем ветвь if вычисляется, когда value равно НУЛЮ или любому atom . Поскольку NIL также является atom , тестовое выражение может быть упрощено как (atom value) . Когда значение равно атому, глубина равна нулю.

Ветвь else if оценивается, когда value не является атомом, что по определению atom означает value здесь a cons . Функция также предполагает, что это правильный список, а не какой-то круговой список.

Поскольку value в этой ветви есть список, мы можем вызвать mapcar его: (mapcar #'depth value) ; здесь функция предполагает, что список является правильным. Это вычисляется (depth v) для каждого v входа value . Точнее, если value это список длины n, то этот вызов mapcar оценивается как список чисел (D1 ... Dn) , где Di (depth Vi) для всех i от 1 до n .

Итак, мы знаем, что (apply 'max (mapcar ...)) это (apply 'max depths) для некоторого списка depths номеров. В общем:

 (apply fn v1 ... vn list)
 

… является способом вызова объекта функции, обозначаемого fn выражением, по крайней мере n , с элементами ( v1 to vn ), а также произвольным количеством дополнительных элементов, хранящихся в list . Когда вы цитируете функцию, как 'max или когда вы пишете #'max , вы ссылаетесь на функцию по ее имени в пространстве имен функций.

Сравните это с обычным способом вызова функции:

 (f x y z)
 

Имя функции и количество передаваемых аргументов фиксированы: как только форма прочитана, мы знаем, что есть вызов f с 3 аргументами.

apply Функция является встроенной, которая позволяет передавать дополнительные аргументы в списке в последнем аргументе вызова. Вышеуказанный вызов может быть написан:

 (apply #'f x y z ()) ;; note the empty list as a last argument
 

Это также можно было бы написать:

 (apply #'f (list x y z)) ;; all arguments in the last list
 

Единственное различие, вероятно, заключается в эффективности выполнения (а с хорошими компиляторами, возможно, разницы нет).

В вашем примере вы делаете:

 (apply max depths)
 

Что было бы то же самое, что писать (псевдокод):

 (max d1 d2 d3 ... dn)
 

… где depths находится список (list d1 d2 ... dn) .
Но мы не можем буквально записать их все напрямую, так как содержимое списка известно только во время выполнения.

Таким образом, вызов apply вычисляет максимальную глубину среди всех глубин, вычисленных рекурсивно. Обратите внимание , что вышеизложенное является несколько неправильным использованием apply , так apply как не следует вызывать списки произвольного размера: в названном стандарте есть ограничение CALL-ARGUMENTS-LIMIT , теоретически допустимое до 50, максимальный размер такого списка (мы увидим альтернативу ниже).

Наконец, depth оценивает ( 1 ...) этот результат. Другими словами, все выражение можно резюмировать следующим образом: глубина списка равна 1, добавленной к максимальной глубине всех его элементов.

С помощью reduce

Вместо apply этого вы можете использовать REDUCE для max последовательных вычислений по списку. Это предпочтительнее apply , чем потому, что:

  • нет никаких ограничений на количество элементов, таких как apply
     (reduce 'max depths) ;; works the same, but more reliably
     
  • нет необходимости создавать промежуточный список глубин, вы перебираете список значений, вызываете depth и напрямую используете результат для вычисления максимума. Скелет-это:
     (reduce (lambda (max-so-far item) ...)
            value
            :initial-value 0)
     

Декларативный подход

Вместо reduce loop этого макрос можно использовать в качестве более удобочитаемой альтернативы для выражения тех же вычислений. Я также использую typecase то, что, на мой взгляд, делает намерение более ясным:

 (defun depth (value)
  (typecase value
    (atom 0)
    (cons (1  (loop for v in value maximize (depth v))))))