LISP Напишите функцию с именем cut-in-half, которая получает список, и создайте новый список, элементами которого являются первая половина и вторая. - PullRequest
0 голосов
/ 23 октября 2019

У меня проблемы с этой функцией. Сначала нас спросили: «Напишите функцию lisp, которая принимает список и целое число n и возвращает первые n элементов списка как новый список. В случае, если n меньше 1, возвращается NIL. В случае nпомимо длины, функция возвращает копию исходного списка. "

(defun take-n (list n)
     (cond 
     ((= n 0) ())
     ((null list) list)
     ((cons(car list)(take-n(cdr list)(- n 1)))))
    )

Что я так далеко от вопроса, который я получил, так это:

(defun cut-in-half(list)
    (let (x (ceiling(/ (length list) 2)))(let* (reverse list))
    (list (take-n list x)(cdr(take-n y x))))
)

что я делаю неправильно?

Ответы [ 2 ]

2 голосов
/ 23 октября 2019

Посмотрите, сможете ли вы построить домашнее задание на основе этой идеи:

[1]> (loop with l = '(1 2 3 4 5 6 7)
           for x on l
           for y = x then (cddr y)
           when (null y)
            return (values (ldiff l x) x))
(1 2 3 4) ;
(5 6 7)

[2]> (loop with l = '(1 2 3 4 5 6 7 8)
           for x on l
           for y = x then (cddr y)
           when (null y)
            return (values (ldiff l x) x))
(1 2 3 4) ;
(5 6 7 8)

Это то, что я делал в прошлом, реализовывая двоичную сортировку слиянием (для разрезания списка пополам, сортировкидве половинки рекурсивно, слияние). По сути, у нас есть два курсора, бегущих по списку;один шаг в два раза, ударяя конец списка в два раза быстрее (используя cddr шагов вместо cdr), что оставляет другой курсор в середине.

Обратите внимание, что в loop,синтаксис for x on l примерно такой же, как при выполнении for x = l then (cdr l), плюс тест завершения для завершения цикла, когда x становится нулевым. Мы могли бы сделать это таким образом, поскольку нам не нужен тест завершения на x. Т.е.

[9]> (loop with l = '(1 2 3 4 5 6 7 8)
           for x = l then (cdr x)
           for y = x then (cddr y)
           when (null y)
            return (values (ldiff l x) x))
(1 2 3 4) ;
(5 6 7 8)

Это хорошо, поскольку предложения x и y имеют одинаковую форму, а контраст между cdr и cddr сделан явным.

КомуЧтобы вернуть список из двух списков, используйте list вместо values. Поскольку Common Lisp имеет несколько значений, идиоматично использовать это вместо выделения дополнительных ячеек списка.

1 голос
/ 23 октября 2019

Вот несколько версий, которые являются концептуально чистыми (без разрушительных операций, даже неявно), но, возможно, слегка непостижимыми. Оба выполняют больше работы, чем любая из явных (неявно) деструктивных версий. Оба предпринимают некоторую попытку не использовать никаких функций, которые имеют непостоянную сложность по времени, кроме функций, которые они определяют сами (так, например, нет вызовов reverse или length). Оба выражают итерацию как хвостовую рекурсию.

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

Для меня цель также состоит в том, чтобы показать, что эти «чистые» варианты проблем зачастую намного сложнее понять, чем «грязные» варианты. Это, однако, мнение.

Оба возвращают две половины списка в виде нескольких значений. Ни один из них, вероятно, не подходит в качестве ответов на домашнее задание.

Первая версия работает следующим образом:

  • перемещение по списку, вычисление его длины и построение перевернутой копии;
  • перемещение внизэта перевернутая копия, накапливающая ее в одной из двух дополнительных перевернутых копий, которые являются результатом.

Эта версия фактически дважды проходит по списку и создает полную перевернутую копию, а затем полную перевернутую копию , которая.

(defun halfify (list)
  ;; Return two values: a copy of the first half of LIST and a copy of
  ;; the second half of LIST.  'half' is defined as by (round (length
  ;; list) 2).
  ;;
  ;; This works by walking down the list to accumulate a reversed copy
  ;; of it and its length: half/accum does this.  When this is done,
  ;; rev/split then walks down the reversed copy accumulating a
  ;; further reversed copy into one of two accumulators.
  ;;
  ;; This walks the list twice, and conses essentially two copies of
  ;; it.
  (labels ((half/accum (tail n accum)
             (if (null tail)
                 (rev/split accum (round n 2) '() '())
               (half/accum (rest tail) (1+ n) (cons (first tail) accum))))
           (rev/split (tail n h1 h2)
             (cond ((null tail)
                    (values h1 h2))
                   ((> n 0)
                    (rev/split (rest tail) (1- n) (cons (first tail) h1) h2))
                   (t
                    (rev/split (rest tail) n h1 (cons (first tail) h2))))))
    (half/accum list 0 '())))

Вторая версия работает следующим образом:

  • спускаясь по списку, чтобы вычислить его длину;
  • разбивая список в halg, используявычисленная длина, накапливающая разбиение (ведущая часть списка) в обратном направлении;
  • реверсирование ведущего чанка с использованием аккумулятора.

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

Обратите внимание, что хвост списка, возвращаемого этой функцией, разделяет структуру с хвостом исходного списка: это не так для первой функции.

(defun halfify (list)
  ;; Return two values: a copy of the first half (rounded) of the
  ;; list, and the remainder of it.  
  ;;
  ;; This does essentially two walks down the list (once to compute
  ;; the length, half to build a reversed of the first half and then
  ;; half again to reverse it, and conses as much as the whole list
  ;; (half for the reversed half-copy, half to reverse it).  I don't
  ;; think you can do better than this without code which is
  ;; implicitly destructive, or not tail-recursive.
  (labels ((half (tail n)
             (if (null tail)
                 (split list (round n 2) '())
               (half (rest tail) (1+ n))))
           (split (tail m results)
             (if (zerop m)
                 (values (rev results '()) tail)
               (split (rest tail) (1- m) (cons (first tail) results))))
           (rev (tail result)
             (if (null tail)
                 result
               (rev (rest tail) (cons (first tail) result)))))
    (half list 0)))

Наконец, я прочитал хитрый совет Каз, и вот версия, которая использует этот трюк. Эта версия всегда будет сокращать список до середины, если его длина нечетна.

(defun halfify (list)
  (labels ((half/step (fast slow a)
             (if (null fast)
                (values (rev a '()) slow)
               (let ((fast-tail (rest fast)))
                 (if (null fast-tail)
                     (values (rev a '()) slow)
                   (half/step (rest fast-tail) (rest slow)
                              (cons (first slow) a))))))
           (rev (tail result)
             (if (null tail)
                 result
               (rev (rest tail) (cons (first tail) result)))))
    (half/step list list '())))
...