Создание простейших рекурсивных программ. Функции работы со строками и множествами. Сообщения об ошибках и преобразования типов.



 

Создание простейших рекурсивных программ.

В функциональных (и логических) языках программирования основным вычислительным средством является рекурсия. Понятие рекурсии легко усвоится теми, кто хорошо знаком с математическим методом индукции. Давайте разберем подробно простую рекурсивную функцию, которая находит сумму элементов числового списка. В Haskell стандартная функция sum определена через функцию высшего порядка foldl, которую мы будем изучать позже, поэтому напишем здесь свою функцию следующим образом:

sum [] = 0

sum (x:xs) = x + sum xs

Почему у нас две строки с именем функции и что они означают? В первой строке записана база рекурсии – для пустого списка сумма элементов будет равна нулю. Базу рекурсии при работе со списками обычно пишут для пустого или одноэлементного списка. В нашем случае можно было бы записать базу и так:

sum [x] = x

то есть сумма элементов списка с одним единственным элементом равна этому элементу. 

Разберем далее вторую строку. Выражение (x:xs) не следует принимать за ошибочно написанный кортеж, а трактовать так – «аргумент-список представимый в виде головы (первого элемента) x и хвоста (всех остальных элементов) xs». После знака равенства записана основная часть функции – шаг рекурсии. Разберем подробно как именно её следует составлять. Пусть у нас есть простой список [1,2,3], в качестве результата применения нашей функции к нему должно получиться значение 6. При первом обращении к функции с аргументом [1,2,3] у нас произойдет сравнение с образцом нашего списка [1,2,3] с аргументом в первой строке – с пустым списком. Сравнение успехом не увенчается, и компилятор перейдет ко второй строке, при этом у нас будет считаться x=1 и xs=[2,3] . Для написания рекурсивного выражения следует задать себе вопрос – пусть нам известен результат применения нашей функции к хвосту списка, как получить результат для самого списка? Результатом для хвоста списка [2,3] является число 5. Итак, у нас есть голова списка x=1, результат для хвоста 5 и мы хотим из этого получить результат 6, что следует сделать? Конечно же, сложить голову x=1 со значением 5, а это значение, как мы помним, результат применения нашей функции к хвосту списка, то есть вызов нашей функции с аргументом xs.

   Давайте рассмотрим подробно как компилятор выполняет нашу рекурсивную функцию. В таблице 2.1 показан каждый шаг вызовов функции и состояние стека.

 

Таблица 2.1 Вычисление функции sum

  Функция Вычисление тела функции Результат Стек
1 sum [1,2,3] 1+sum [2,3] - -
2 sum [2,3] 2+sum [3] - sum [1,2,3]
3 sum [3] 3+sum [] - sum [2,3] sum [1,2,3]
4 sum [] 0 0 sum [3] sum [2,3] sum [1,2,3]
5 sum [3] 3+0 3 sum [2,3] sum [1,2,3]
6 sum [2,3] 2+3 5 sum [1,2,3]
7 sum [1,2,3] 1+5 6 -

 

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

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

sum1 [] s = s

sum1 (x:xs) s = sum1 xs (s+x)

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

sum a = sum1 a 0

sum1 [] s = s

sum1 (x:xs) s = sum1 xs (s+x)

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

sum (x:xs) = x + sum xs

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

       В примере выполнения лабораторной работы 2 ниже разобраны еще несколько задач с использованием рекурсии, но не хвостовой. Рекомендуется самостоятельно попробовать написать варианты  хвостовой рекурсии.

 

 


Дата добавления: 2019-03-09; просмотров: 342; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!