Little Schemer: напишите функцию, которая поддерживает только списки длины ≤ 2

В книге Маленький интриган мы находим эту функцию, которая поддерживает только списки, длина которых меньше или равна 1:

 (((lambda (mk-length)                                  ; A.
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l ) 0)
              (else (add1 ((mk-length  eternity ) (cdr l))))))))
 '(1))

Я хочу учиться шаг за шагом и хочу написать аналогичную функцию, которая поддерживает только списки длиной меньше или равной 2.

Пожалуйста, не отвечайте на этот вопрос, предлагая код вида:

(((lambda (mk-length)                                   ; B.
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l) 0 )
              (else (add1((mk-length mk-length) (cdr l))))))))
 '(a b c d))

потому что эта функция поддерживает любую длину.

И я уже знаю, как написать такую ​​функцию:

(((lambda (mk-length)                                   ; C.
      (mk-length
          (mk-length (mk-length eternity))))
  (lambda (length)
      (lambda (l)
          (cond
              ((null? l) 0)
              (else (add1 (length (cdr l))))))))
 '(1 2)) ;;

для достижения моей цели. Но этот код более чем в одном шаге от первого фрагмента.

Может быть, мне не стоит меняться:

(lambda (mk-length)                                     ; D.
      (mk-length mk-length)

person jiamo    schedule 03.04.2015    source источник
comment
В чем именно вопрос? Кроме того, вы можете найти ответ в ответе (или связанных вопросах) на: Маленький интриган: length0 и mk-length. Я пока не буду помечать как дубликат, потому что я точно не знаю, о чем вы здесь спрашиваете.   -  person Joshua Taylor    schedule 03.04.2015
comment
Я имею в виду сделать то же самое, что и третий код, который я вставляю здесь. Но должно исходить из идеи 1-го кода. что означает не менять 4-й код в 1-м коде.   -  person jiamo    schedule 03.04.2015
comment
Можете ли вы объяснить, почему вы это ищете? На самом деле это нетривиальная проблема, потому что смысл этого типа рекурсивного упражнения в том, что, поскольку список является рекурсивной структурой данных, вам нужно обрабатывать только два случая: пустой список и непустой список. Реализация для случая ‹=1 обрабатывает только первый случай (пустой список). Вы обрабатываете случай =1, обрабатывая непустой случай, но это также позволяет обрабатывать случаи =2, =3 и т. д., поскольку они обрабатываются так же, как и случай =1.   -  person Joshua Taylor    schedule 03.04.2015
comment
2-й код просто измените (mk-length eternity ) на (mk-length mk-length), можно получить любую длину. Трудно думать, поэтому я хочу реализовать только <=2 версию. на базе 1-го кода   -  person jiamo    schedule 04.04.2015
comment
это дубликат этого вопроса но ответ там дает length≤∞, а не length≤2.   -  person Will Ness    schedule 20.01.2018
comment
@jiamo отличный вопрос! важно (в математике) действовать осторожно, четко осознавая каждый маленький шаг в выводе (и следующий шаг здесь).   -  person Will Ness    schedule 20.01.2018


Ответы (2)


TL;DR: (mk-length A) (внутри формы cond) вычисляет функцию length, которая работает для списков длиной 0, и будет использовать (A A) для вычисления length, которая будет использоваться для обработки хвоста (то есть результата (cdr ...)) списка аргументов.


Ваш первый фрагмент кода (;A.) работает только для списков длиной 0 и 1. Чтобы это работало и для 2, замените

                               (mk-length eternity)               ; length≤1

с участием

                               (mk-length   ; (2)                 ; length≤2
                                  (lambda (x) (mk-length eternity)))

работает.

(Примечание: (mk-length eternity) сама вычисляет length≤0, но общая функция становится length≤1; это — это то, на что ссылаются все дальнейшие length≤i комментарии.)

Глядя внимательно на

     (((lambda (mk-length)
          (mk-length mk-length))            
      (lambda (mk-length)                   ; (1)
          (lambda (l)                       
              (cond
                  ((null? l ) 0)
                  (else (add1 ((mk-length   ; (2)                 ; length≤2
                                  (lambda (x) (mk-length eternity)) )
                               (cdr l))))))))
     '(1 2))

мы видим, что результат (mk-length ...) в ;(2) используется для обработки (cdr l), в то время как argument в mk-length в ;(2) заменит mk-length в этом вызове при обработке (cddr l).

Если используется (mk-length eternity) (как в вашем первом коде), (cdr l) обрабатывается нормально, но ((eternity eternity) (cddr l)), естественно, терпит неудачу.

Если используется (mk-length (lambda (x) (mk-length eternity))), (cdr l) обрабатывается нормально, а затем ((lambda (x) (mk-length eternity)) (lambda (x) (mk-length eternity))) = (mk-length eternity) используется для обработки (cddr l), что также нормально (таким образом, длина 2 обрабатывается правильно), а затем ((eternity eternity) (cdddr l)), естественно, терпит неудачу (для длин 3 и выше).

Таким образом, чтобы обработать списки до трех элементов,

                              ((mk-length   ; (2)                 ; length≤3
                                  (lambda (x) (mk-length 
                                                (lambda (x) (mk-length eternity)))) )

может быть использован:

    (define (eternity x) (- 1))    ; to get an error instead of looping

    (((lambda (mk-length)
          (mk-length mk-length))
      (lambda (mk-length)
          (lambda (l)
              (cond
                  ((null? l ) 0)
                  (else (add1 ((mk-length   ; (2)                 ; length≤3
                                  (lambda (x) (mk-length
                                               (lambda (x) (mk-length eternity)))) )
                               (cdr l))))))))

      '(1 2 3))        ; => 3

    ; ...........
    ; '(1 2 3 4))      ; => **error**

Как вы правильно догадались, это — промежуточный шаг на пути к использованию

                     (mk-length (lambda (x) (mk-length x)))   ; (2)       ; length≤∞

что превращает обработку следующего элемента списка в

                     ((lambda (x) (mk-length x)) (lambda (x) (mk-length x)))
    =
                     (mk-length (lambda (x) (mk-length x)))

и, таким образом, работает для каждого списка, независимо от его длины.

А по эта-конверсии это всего (mk-length mk-length).

person Will Ness    schedule 18.01.2018
comment
Этот ответ действительно стоил внимания для меня. =) Спасибо за сам ответ и за то, что уведомили меня об этом! - person ; 23.01.2018
comment
@ФилиппВ. добро пожаловать, и спасибо за стимул сделать это (т. е. ваши ответы и изменения в этом кластере проблем). Re: ваше редактирование, это не опечатки. Я имел в виду начальный l, поэтому вызов (... (cdr l)) во второй итерации, когда этот l на самом деле является (cdr l) для исходного l, равен (.... (cddr l)). И это (cdddr l) в третьем. это немного небрежно и запутанно, но простое использование (cdr l) тоже не поможет. Я придумаю как-то прояснить эту вещь, позже. Спасибо! - person Will Ness; 23.01.2018
comment
О, мой плохой я не понял это сразу. Однако спасибо за объяснение! - person ; 01.02.2018

Списки в Лиспе (Scheme, Common Lisp и т.д.) строятся из cons-ячеек и специального (пустого списка). То есть всякий раз, когда у вас есть что-то, являющееся списком, это либо другое:

  • Пустой список ()
  • Cons-ячейка (также называемая парой), где car пары является первым элементом списка, а cdr пары является остальной частью списка.

Поскольку существует два типа списков, рекурсивные алгоритмы для списков обычно отвечают только на два вопроса:

  • Каков результат для пустого списка?
  • Каков результат для остальной части списка и как он превратился в результат для всего списка?

Для length это означает:

  • Длина пустого списка равна 0.
  • Когда длина остатка списка равна n, длина всего списка составляет n + 1.

Тип решения, представленный в Маленьком интригане, сначала разрабатывает решение, отвечающее первому случаю, что означает, что оно работает для списков длины 0. Как только у вас есть решение, которое обрабатывает оба случая (правильно ), у вас есть решение, которое работает для списков любой длины. Не существует реального инкрементного решения, которое расширило бы функцию для работы со списками длины 2.

Полагаю, вы могли сделать что-то вроде:

 (((lambda (mk-length)
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l ) 0)
              (else 1)))))

Это будет работать для списков длины 0 и списков длины 1 и будет некорректным для всех остальных списков, но для всех остальных списков будет возвращено 1. Если вы не измените структуру рекурсии, я думаю, что это действительно лучшее, что вы можете сделать. Если вы хотите изменить структуру рекурсии, вы можете сделать следующее:

 (((lambda (mk-length)
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l) 0)
              ((null? (cdr l)) 1)
              (else (add1 ((mk-length  eternity ) (cdr l))))))))

но вы на самом деле не должны использовать этот подход, потому что теперь вы обрабатываете списки в трех разных случаях вместо двух, что (почти) никогда не является тем, что вы хотите делать.

Перевод на Python

Можно ли переписать это мышление с помощью Python или другого языка обработки? До сих пор сложно представить автоматически созданное развлечение в рекурсии.

Тот же принцип работает в Python или любом другом языке, поддерживающем функции более высокого порядка. В Python более идиоматично локально определять функцию, а затем вызывать ее, а не напрямую вызывать лямбда-выражение, поэтому это может быть более читаемым для вас:

def length(l):
    def driver(recurse):
        "Returns the result of calling `recurse` with itself."
        return recurse(recurse);
    def zeroForEmptyListOrElseOnePlusRecurse(recurse):
        """Returns a function that returns 0 if its input is the
        empty list, or else returns one plus whatever calling 
        `recurse(recurse)` with the tail of the list returns."""
        def length(l):
            """Returns 0 if l is the empty list, and one plus
            the results of `(recurse(recurse))(l)` otherwise."""
            if l == []:
                return 0;
            else:
                _, *rest = l
                return 1+(recurse(recurse))(rest)
        return length
    return (driver(zeroForEmptyListOrElseOnePlusRecurse))(l)

print(length([1,2,3])) # 3
print(length([1,2]))   # 2
print(length([1]))     # 1
print(length([]))      # 0
person Joshua Taylor    schedule 06.04.2015
comment
Я думаю, вы уже ответили на мой вопрос! Можете ли вы объяснить больше о том, как использовать ваш ответ, чтобы заставить код (mk-length mk-length) работать для любой длины списка? Легко думать о замене mk-length на первую лямбду, однако трудно думать о повторной подстановке внутри. Для меня не природа думать об этом. Однако это легко для кого-то другого. - person jiamo; 07.04.2015
comment
Я не уверен, что вы спрашиваете. Если вы замените (mk-length eternity) на (mk-length mk-length) в моем ответе, это должно сработать. Но у вас все еще будет избыточный пункт в вашем договоре. - person Joshua Taylor; 07.04.2015
comment
Может быть, я спрашиваю, как работает mk-length в (add1 ((mk-lenght mk-lenght) (cdr l))? Можно ли переписать это мышление с помощью Python или чего-то еще на языке процессов? Все еще трудно представить автоматически созданное развлечение в рекурсии. - person jiamo; 07.04.2015
comment
@jiamo Я добавил перевод на Python. - person Joshua Taylor; 07.04.2015