Лисп - Сплит Рекурсивный - PullRequest
0 голосов
/ 22 мая 2018

Я пытался создать рекурсивную функцию, чтобы разделить список на два списка в соответствии с количеством элементов, которые он хочет.

Пример:

(split 3 '(1 3 5 7 9)) ((1 3 5) (7 9))

(сплит 7 '(1 3 5 7 9)) ((1 3 5 7 9) ноль)

(разделение 0 '(1 3 5 7 9)) (NIL (1 3 5 7 9))

Мой код такой:

(defun split (e L) 
  (cond ((eql e 0) '(() L))
        ((> e 0) (cons (car L) (car (split (- e 1) (cdr L))))))))

Я не могу найти способ присоединиться к первым элементам списка и вернуть второй список.

Ответы [ 3 ]

0 голосов
/ 22 мая 2018

Хорошо, давайте попробуем вывести рекурсию, которую мы должны делать.

(split 0 l) = (list () l)

Так что это наш базовый случай.Теперь мы знаем

(split 1 (cons a b)) = (list (list a) b)

Но мы немного подумаем и создадим первый аргумент слева, и способ составления списков таким способом будет с CONS, поэтому мы записываем

(split 1 (cons a b)) = (list (cons a ()) b)

А потом мы немного подумаем и подумаем о том, что такое (split 0 l), и можем записать для n>=1:

(split n+1 (cons a b)) = (list (cons a l1) l2) where (split n b) = (list l1 l2)

Итак, давайте запишем это в Лиспе:

(defun split (n list)
  (ecase (signum n)
    (0 (list nil list))
    (1 (if (cdr list)
           (destructuring-bind (left right) (split (1- n) (cdr list))
             (list (cons (car list) left) right))
           (list nil nil)))))

Самое идиоматическое решение будет выглядеть примерно так:

(defun split (n list)
  (etypecase n
    ((eql 0) (list nil list))
    (unsigned-integer
      (loop repeat n for (x . r) on list
        collect x into left
        finally (return (list left r))))))
0 голосов
/ 22 мая 2018

Хвост рекурсивного решения

(defun split (n l &optional (acc-l '()))
  (cond ((null l) (list (reverse acc-l) ()))
        ((>= 0 n) (list (reverse acc-l) l))
        (t (split (1- n) (cdr l) (cons (car l) acc-l)))))

Улучшенная версия

(в этой версии гарантируется, что acc-l находится наначало '()):

(defun split (n l)
  (labels ((inner-split (n l &optional (acc-l '()))
             (cond ((null l) (list (reverse acc-l) ()))
                   ((= 0 n)  (list (reverse acc-l) l))
                   (t (inner-split (1- n) (cdr l) (cons (car l) acc-l))))))
    (inner-split n l)))

Проверьте это:

(split 3 '(1 2 3 4 5 6 7))
;; returns: ((1 2 3) (4 5 6 7))
(split 0 '(1 2 3 4 5 6 7))
;; returns: (NIL (1 2 3 4 5 6 7))
(split 7 '(1 2 3 4 5 6 7))
;; returns ((1 2 3 4 5 6 7) NIL)
(split 9 '(1 2 3 4 5 6 7))
;; returns ((1 2 3 4 5 6 7) NIL)
(split -3 '(1 2 3 4 5 6 7))
;; returns (NIL (1 2 3 4 5 6 7))

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

Как я пришелк решению:

Каким-то образом ясно, что первый список в результате должен быть результатом последовательного следования одного элемента за другим с начала l.Однако consing добавляет элемент к существующему списку в его начале, а не в конце.Итак, последовательная сдача машины в список приведет к обратному порядку.Таким образом, ясно, что на последнем этапе, когда возвращается первый список, он должен быть reverse d.Второй список - это просто (cdr l) последнего шага, поэтому его можно добавить к результату на последнем шаге, когда результат будет возвращен.

Итак, я подумал, что хорошо бы накопить первый список в (acc-l) - аккумулятор в основном является последним элементом в списке аргументов хвостовых рекурсивных функций, компонентами первого списка.Я назвал это acc-l - список-аккумуляторов.

При написании рекурсивной функции, часть cond начинается с тривиальных случаев.Если входными данными являются число и список, наиболее тривиальные случаи - и последние шаги рекурсии - это случаи, когда

  1. список пуст (equal l '()) ---> (null l)
  2. и число равно нулю ----> (= n 0) - на самом деле (zerop n).Но позже я изменил его на (>= n 0), чтобы отлавливать также случаи, когда в качестве входных данных приводится отрицательное число.

(Таким образом, очень часто рекурсивные cond части имеют null или zerop вих условия.)

Когда список l пуст, тогда два списка должны быть возвращены - тогда как второй список является пустым списком, а первый список - неинтуитивно - reverse d acc-l.Вы должны построить их с (list ), так как аргументы list вычисляются незадолго до возврата (в отличие от quote = '(...), где результат не может быть оценен как sth на последнем шаге.)

Когда n равно нулю (и позже: когда n отрицательно), тогда ничего не нужно делать, кроме как вернуть l в качестве второго списка и то, что было накоплено для первого списка до сих пор - но в обратном порядке.

Во всех остальных случаях (t ...), машина списка l cons отправляется в список, который был накоплен до сих пор (для первого списка): (cons (car l) acc-l), и это я даю в качестве списка аккумулятора (* 1071)*) до split и остальной список как новый список в этом вызове (cdr l) и (1- n).Это уменьшение в рекурсивном вызове очень типично для определений рекурсивных функций.

Тем самым мы рассмотрели все возможности для одного шага в рекурсии.И это делает рекурсию такой мощной: покорите все возможности в ОДНОМ шаге - и тогда вы определили, как обрабатывать почти бесконечное число случаев.

Нерекурсивное решение (по вдохновению Дэна РобертсонаРешение - Спасибо, Дэн! Особенно его решение с destructuring-bind мне понравилось.

(defun split (n l)
  (cond ((null l) (list '() '()))
        ((>= 0 n) (list '() l))
        (t (destructuring-bind (left right) (split (1- n) (cdr l))
                    (list (cons (car l) left) right)))))

И решение только с очень элементарными функциями (только null, list,>=, let, t, cons, car, cdr, cadr)

(defun split (n l)
  (cond ((null l) (list '() '()))
        ((>= 0 n) (list '() l))
        (t (let ((res (split (1- n) (cdr l))))
              (let ((left-list (car res))
                    (right-list (cadr res)))
                (list (cons (car l) left-list) right-list))))))
0 голосов
/ 22 мая 2018

Помните: split возвращает список из двух списков.

(defun split (e L) 
  (cond ((eql e 0)
         '(() L))       ; you want to call the function LIST
                        ;  so that the value of L is in the list,
                        ;  and not the symbol L itself

        ((> e 0)

         ; now you want to return a list of two lists.
         ; thus it probably is a good idea to call the function LIST

         ; the first sublist is made of the first element of L
         ;  and the first sublist of the result of SPLIT

         ; the second sublist is made of the second sublist
         ;  of the result of SPLIT

         (cons (car L)
               (car (split (- e 1)
                           (cdr L)))))))
...