Схема рекурсии (от десятичной до восьмеричной) - PullRequest
2 голосов
/ 30 января 2012

Итак, нашему классу было дано задание для преобразования десятичных чисел в их восьмеричные представления. Мне удалось просто повозиться с ним, пока он не заработал, но у меня возникли проблемы с пониманием, почему это работает. Есть ли возможность объяснить рекурсию более упрощенным способом? Спасибо.

(define octify
 (lambda (n)
  (cond
   ((zero? n) 0)
   ((zero? (quotient n 8)) n)
   (else  (+ (* 10 (octify (quotient n 8))) (remainder n 8))))))

Ответы [ 3 ]

3 голосов
/ 30 января 2012

Прежде всего, «число» не является десятичным или восьмеричным. «Число» - это математическая концепция, которая хранится в компьютере в каком-то формате с кучей битов. Десятичные и восьмеричные относятся к различным строковым представлениям числа. То есть «десятичные» и «восьмеричные» и т. Д. Имеют смысл только в том случае, если речь идет о строках, и конкретное число может быть преобразовано в строку как десятичное или восьмеричное или как угодно.

Создание восьмеричного (или некоторого другого базового) строкового представления целого числа является распространенной базовой задачей в программировании. Алгоритм, который вы в основном выяснили: возьмите оставшуюся часть числа за основание, чтобы получить последнюю цифру, затем вернитесь к частному числа по основанию, чтобы получить остаток (все, кроме последней цифры) числа.

Что странно в том, что вы делаете, так это то, что вы не производите строку, как обычно делают для этой задачи. Вместо этого вы пытаетесь упаковать его обратно в число таким образом, чтобы десятичное представление полученного числа выглядело бы так, как будет выглядеть восьмеричное представление исходного числа. (Это возможно, поскольку любое восьмеричное представление также является допустимым десятичным представлением некоторого числа. Это невозможно, например, с помощью шестнадцатеричного числа.) Другими словами, вы конвертируете число в его восьмеричное строковое представление, а затем анализируете эта строка в число, как если бы это было десятичное представление. Например, возьмем число 42, десятичное представление которого представляет собой строку «42», а восьмеричное представление - строку «52». Ваша программа возвращает число 52 (восьмеричное представление которого представляет собой строку «64»).

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

1 голос
/ 31 января 2012

Основным представлением числа, которое захватило мир штурмом, является позиционная запись .Это представление тесно связано с понятием частных и остаточных операций, которое вы несколько видите из определения своей рекурсивной функции.Почему это так?

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

Number :== Zero
         | Successor(n) where n is a number

. Считайте, что это число «Ноль или преемник другого числа».Или, чтобы закодировать его в Схеме, которая поддерживает структурированные представления (например, Racket), мы можем написать это:

(define-struct Zero ())
(define-struct Successor (n))

Например, представление three с этой нотацией будет (Successor (Successor (Successor (Zero))).(Это представление называется Peano, если я правильно помню.)

Функции, которые имеют дело с этим типом структурированного типа данных, часто имеют такую ​​же форму , как и у самого типа данных.То есть функция, которая работает с представлением в Peano, будет выглядеть примерно так:

 ;; a peano-eating-function-template: peano-number -> ???
(define (a-peano-eating-function-template a-num)
   (cond [(Zero? a-num)
          ...]
         [(Successor? a-num)
          ...
          (a-peano-eating-function-template (Successor-n a-num)) 
          ...] 

, где ... будет чем-то конкретным для конкретной проблемы, которую вы пытаетесь решить с помощью чисел Peano.Речь идет о функциях, следующих структуре данных, над которыми они работают.В качестве конкретного примера функции поедания Пеано, вот тот, который превращает пианино в кучу звезд:

;; peano->stars: peano-number -> string
;; Turn a peano in a string of stars.  We are all made of stars.
(define (peano->stars a-num)
  (cond [(Zero? a-num)
         ""]
        [(Successor? a-num)
         (string-append "*"
                        (peano->stars (Successor-n a-num)))]))

В любом случае, типы данных естественным образом приводят к функциям с определенными формами.Это приводит нас к возвращению к позиционной записи.Можем ли мы захватить позиционную запись как тип данных?

Оказывается, мы можем!Позиционная запись, такая как десятичная запись, может быть описана аналогично тому, как работает описание числа Пеано.Давайте назовем это представление Base10 , где оно выглядит следующим образом:

Base10 :== Zero
         | NonZero(q, r) where q is a Base10, and r is a digit.

Digit :== ZeroD | OneD | TwoD | ... | NineD

И если мы хотим получить конкретное представление о программировании на языке со структурами,

(define-struct Zero ())
(define-struct NonZero(q r))

(define-struct ZeroD ())
(define-struct OneD ())
(define-struct TwoD ())
(define-struct ThreeD ())
(define-struct FourD ())
;; ...

Например, число сорок два может быть представлено в Base10 как:

(NonZero (NonZero (Zero) (FourD)) (TwoD))

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

;; a-number-eating-function-template: Base10 -> ??? 
(define (a-number-eating-function-template a-num)
  (cond
    [(Zero? a-num)
     ...]
    [(NonZero? a-num)
     ... (a-number-eating-function-template (NonZero-q a-num))
     ... (NonZero-r a-num)]))

То есть мы можем получить форму рекурсивной функции, которая работает на Base10 практически бесплатно, простоследуя структуре самой Base10.

... Но это сумасшедший способ иметь дело с числами, верно?Хорошо ... помните, что дурацкое представление для сорок два :

(NonZero (NonZero (Zero) (FourD)) (TwoD))

Вот еще один способ представить то же число.

((0 * 10 + 4) * 10 + 2)

Почти то же самоеидея.Здесь давайте избавимся от еще нескольких скобок.Мы можем представить сорок два с помощью следующих обозначений:

42

Наши языки программирования жестко закодированы, чтобы знать, как обращаться с этим обозначением для чисел.

Что нашиэквивалент для проверки нуля?Мы знаем это.

(= n 0)   ;; or (zero? n)

Каков наш эквивалент для проверки NonZero?Легко!

(> n 0)

Каковы наши эквиваленты для NonZero-q и NonZero-r?

(quotient n 10)
(remainder n 10)

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

(define (a-decimal-eating-function-template n)
  (cond [(= n 0)
         ...]
        [(> n 0)
         ... (a-decimal-eating-function-template (quotient n 10))
         ... (remainder n 10)]))

Выглядит знакомо?:)

Подробнее об этом см. Учебник, подобный Как разрабатывать программы .

0 голосов
/ 02 февраля 2012

Очевидно, (octify 0) должно быть 0 и (octify n) для n, такого что 0 (octify (quotient n 8))?». Для чисел с основанием 10 деление на 10 удаляет самую правую цифру - 145/10 = 14 (при условии целочисленного деления). С числами base-8 деление на 8 работает аналогично. Следовательно, (octify (quotient n 8)) возвращает число со всеми, кроме последней цифры n, при условии, что octify определено правильно (что мы должны предположить). Теперь нам нужно взять это число и «нажать» его цифрой слева. Умножив это на 10, мы сделаем это, так как принтер будет печатать в base-10. (remainder n 8) получает последнюю цифру n. Теперь у нас есть первая, но много цифр (с нулем для последней цифры) и последняя цифра, поэтому мы можем объединить их, добавив их. На этом этапе функция завершена, и возвращается правильное значение.

...