Как хранить значения приращения в Common Lisp? - PullRequest
1 голос
/ 01 апреля 2012

Вот мой вопрос: для одного из моих заданий мне было поручено разработать программу lisp, которая принимает 2 списка в качестве входных данных, один из которых символизирует корзину покупок (L1) с названием и количеством товара, а второй - прайс-лист (L2), с названием товара и ценой. Все следует этому формату:

(calcTotal '(shirtA 3 shirtB 1) '(shirtA 25 shirtB 55))

The total is 155.00

Вот мой код ниже:

(defun calcTotal (L1 L2 &aux(Ttl 0))
(cond
    (
        (and (listp L1) (listp L2))

        (do
            ((tLst1 L1 (cddr tLst1)))

            ((equal tLst1 nil) Ttl)

            (do
                ((tLst2 L2 (cddr tLst2)))

                ((equal tLst2 nil) nil)

                (cond
                    (
                        (equal (car tLst1) (car tLst2))

                        (print (+ Ttl (* (cadr tLst1) (cadr tLst2))))
                    )
                )

            )

        )

    )
)
)

По сути, он проверяет имя элемента в первом списке, а затем ищет его во втором списке. Как только он найдет совпадение, умножьте их значения вместе, чтобы получить общую сумму для этого элемента, затем удалите первые два элемента в первом списке и повторите. Проблема в том, что сумма (Ttl) не накапливается. Я могу получить сумму для каждого элемента отдельно, но по какой-то причине Ttl возвращается как 0. Может кто-нибудь сказать мне, почему?

Ответы [ 3 ]

9 голосов
/ 01 апреля 2012

Существует как минимум три подхода:

  1. использовать примитивный Лисп для отработки рекурсии на функциональном языке программирования
  2. использовать функции более высокого порядка: MAP и REDUCE и стиль программирования без побочных эффектов
  3. использовать итерацию и побочные эффекты

Я покажу вам подход 2:

Одним из важных инструментов является функциональная абстракция . Используйте функции для реализации автономных функций, которые можно использовать повторно и которые можно легко протестировать.

Вам нужна цена. Напишите GET-PRICE функцию.

(defun get-price (item price-list)
  (getf price-list item))

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

Применение функции к списку и сбор возвращаемых значений называется * mapping 'в списке. К сожалению, Lisp предоставляет функции отображения, которые принимают один элемент за раз, а не два. Мы пишем один:

(defun map2 (function list)
  (loop for (a b) on list by #'cddr
        collect (funcall function a b)))

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

Выше используется конструкция LOOP. Вы можете повторно реализовать его, используя DO в качестве задачи.

(defun calc-total (cart price-list)
  (reduce #'+
          (map2 (lambda (item n)
                  (* n (get-price item price-list)))
                cart)))

Выше используются две функции высшего порядка: REDUCE и MAP2. Более высокий порядок означает, что они принимают функции в качестве параметров. REDUCE - это библиотечная функция в Common Lisp. Мы используем его для подведения итогов списка чисел. С помощью MAP2 мы вычисляем цену для каждого элемента корзины, умножая количество товаров на цену за товар.

CL-USER > (calc-total '(shirtA 3 shirtB 1) '(shirtA 25 shirtB 55))
130

РЕЗЮМЕ

Вышеуказанный подход имеет несколько преимуществ:

  1. у нас есть небольшие / компактные функции, которые легко понять и протестировать
  2. без видимых побочных эффектов
  3. базовая модель отображения и сокращения - это шаблон, который часто появляется при обработке списков и который легко понять
  4. функции легко комбинируются
  5. функция 'MAP2' - это новый полезный инструмент многократного использования
  6. расположение кода гораздо лучше читать

Для других подходов есть подсказки:

  1. вам нужно использовать рекурсивный вызов
  2. вам нужно использовать переменную для суммы, добавить цены в качестве побочного эффекта и вернуть сумму в конце.
8 голосов
/ 01 апреля 2012

Ваши структуры данных представляют собой списки свойств Лиспа: плоские списки пар чередующихся ключей и значений (называемые свойствами «индикаторы» и значения свойств).

Поэтому вы должны воспользоваться преимуществами функций управления списком свойств Лиспа, таких как getf, который ищет ключ и получает значение.

Также здесь очень полезен макрос loop.Это превосходно при таких задачах суммирования.

(defun calculate-total (cart prices)
  (loop for (item units) on cart by #'cddr
        for unit-price = (getf prices item)
        if unit-price
          sum (* unit-price units)
        else
          do (error "price check on ~s please!" item)))

Обратите внимание, как мы аккуратно закрываем скобки, подобные этой ))).Когда код Lisp правильно отформатирован, вы тренируете свой мозг, чтобы не видеть скобки.Не тратьте время на то, чтобы научиться тому, что вы все равно не увидите.

4 голосов
/ 01 апреля 2012

Вы просто распечатываете значение.Чтобы сохранить его на потом, также установите переменную:

;; (print (+ Ttl (* (cadr tLst1) (cadr tLst2)))) 
   (setf Ttl (+ Ttl (* (cadr tLst1) (cadr tLst2))))
   (print Ttl)
...