Как суммировать список, содержащий цифры и буквы, в Lisp работает

#lisp #common-lisp #clisp

#lisp #common-lisp #clisp

Вопрос:

Я пытаюсь определить функции sum, которые принимают список в качестве параметра. Проблема в том, что мой список может содержать не только цифры, но и буквы. Итак, мой вопрос в том, как я могу избежать букв и продолжить проверку остальной части списка?

Пример : (sum ‘(a 2 4 b d)) = 6

 (defun sum (l) 
    ( if (numberp (CAR l)) (sum (CDR l)) (  (CAR l) (sum (CDR l))) )
)
  

Все, что у меня есть, это ошибка «Переполнения стека».

Заранее спасибо.

Ответ №1:

Ваш код пытается применить рекурсивный подход. Вот он, правильно отформатированный, с более длинными именами:

 (defun sum (list) 
  (if (numberp (car list))
      (sum (cdr list))
      (  (car list) (sum (cdr list)))))
  

У вас есть переполнение стека, потому что вы не предоставили базовый вариант, когда список пуст.
Что произойдет, если вы передадите список с нулевым значением в свою функцию?

  1. (car list) возвращает NIL, который не является числом.
  2. Вы переходите к ветке else.
  3. Вы пытаетесь добавить (car list) , который по-прежнему равен НУЛЮ, к рекурсивному вызову SUM with (cdr list) , который также равен НУЛЮ.
  4. Вы вернулись к первому шагу рекурсивного вызова: бесконечная рекурсия, которая в конечном итоге вызывает ошибку, потому что вы постоянно выделяете кадры стека при каждом вызове.

Итак, вы должны определить, что происходит при предоставлении пустых списков. Это типичный вопрос домашнего задания. Мой любимый шаблон включает etypecase в себя, потому что он обеспечивает защитный подход к кодированию, который рано отклоняет ошибки и довольно удобен для чтения:

 (defun foo (list)
  (etypecase list
    (null <base-case>)
    (cons <general-case>)))
  

Однако вы также часто находите это или что-то эквивалентное с cond :

 (defun foo (list)
  (if (null list)
      <base-case>
      <general-case>))
  

Вероятно, это то, что ожидается от студентов. Лучшим подходом является использование endp вместо null , потому что первый проверяет, является ли аргумент фактически списком.

В вашей проблеме:

  • Базовый вариант, очевидно, возвращает 0.
  • Общий случай вычисляет сумму car (ноль, если не число) с суммой cdr , полученной рекурсивно.

Но тогда у вас будет рекурсивная функция, которая должна сохранять промежуточные результаты в стеке вызовов, что является расточительным. Чтобы иметь хвостовую рекурсивную функцию, вам нужно передать результирующую сумму в качестве аргумента вашей функции: сначала вы передаете 0, затем каждый рекурсивный вызов сначала вычисляет сумму, прежде чем вызывать себя рекурсивно. Базовый вариант возвращает сумму. Это гарантирует вам, что промежуточный результат не должен сохраняться между рекурсивными вызовами, которые при правильных обстоятельствах (это зависит от вашей реализации) могут быть оптимизированы как цикл. Но поскольку Common Lisp уже предоставляет итерационные конструкции, вы должны использовать их вместо этого на практике:

 (loop for e in list when (numberp e) sum e)
  

Если вас интересуют функции более высокого порядка, следующее тоже работает, но выделяет промежуточный список (приемлемо это или нет, зависит от ожидаемого размера вашего списка):

 (reduce #'  (remove-if-not #'numberp list))
  

Комментарии:

1. Привет, coredump, большое спасибо за ваше объяснение! я пробовал метод «cond», но все равно получаю сообщение об ошибке. Моя функция заключается (defun sum (liste) (cond ((null liste)nil) ((numberp (car liste))( (car liste) (sum (cdr liste)))) ((sum (cdr liste))) )) в сообщении об ошибке: «Аргументы In of (2 NIL) должны иметь тип NUMBER» . for (sum ‘(2 2))

2. Сумма пустого списка должна быть равна нулю. Здесь вы пытаетесь добавить число к нулю, которое не определено.

Ответ №2:

Не самый эффективный способ, но проверьте функции remove-if (или delete-if ) вместе с numberp .

Комментарии:

1. Привет, Лео, Наконец, я использовал remove-if, и он работает хорошо! спасибо, ребята