воскресенье, 8 ноября 2009 г.

Немного рекурсии, процедур, функций и ностальгии о школе

Увы, но даже некоторые учителя информатики не знают различая между процедурой и функцией, а при слове «рекурсия» на них нападает первобытный ужас, и они начинают трястись и плеваться. Больше всего от этого страдают ученики, которые хотят понять и разобраться – инициатива рубится, что говориться, на корню. Что же в стране «полууголовной» романтики – любая инициатива наказуема.
Так же и я, обещая выложить скрипт, генерирующий молнию (смотри комментарии к прошлому посту), опять срулил в диалектическую яму и принялся заниматься своим любимым делом – ругать полуграмотных педагогов информатики, забывши, когда сам крайний раз смотрел на класс. Ругать можно долго – толку только от этого не будет. Да и ясности не прибавится. Поэтому ругать больше никого не стану – ругайте меня, за то, что взялся объяснять самые азы, но кому-то и они пригодятся как для дальнейшего восприятия уже «боевых» скриптов, так и для менее нервного чтения прошлых постов.
Позволю себе еще немного поностальгировать о тех временах, когда возил маркером по доске, рисуя загадочные заклинания на Паскале. Так вот, Паскаль к отступам нечуствительный, на нем всю программу можно писать сплошным текстом, не выделяя ничего – все одно работать будет правильно, если будет, а что в коде потом ничего не понять – так это обычно мало кого волнует, кто смотрит свои программы. Я обычно всегда «гонял» за отсутствие отступов, хотя их не делал почти никто. Python же к отступам чувствителен. Отсутствие или наличие отступов в некоторых местах вызывает критические ошибки – скрипт просто не работает, в других же местах те же самые лишние отступы могут изменить логику выполнения скрипта до неузнаваемости. Вот и получаются ситуации когда все работает, но почему-то не так, как того хочет программист. Вот и начинается баголовительство и непонятные крики – все правильно написано, а работает неправильно, «такого быть не может», «это не возможно», «код верный». Код-то верный. А вот логика не до конца.
Проше всего продемонстрировать работу отступов на уже известном примере, проще, но как тогда связать их с процедурами, функциями и рекурсией?
Итак, код программы поделен на блоки – модули. Некоторые модули импортируются из уже готовых библиотек – их мы разбирали, трогать больше не будем. Некоторые блоки пишутся в самом скрипте, используется ключевое слов def. В python def используется как для процедур так и для функций, а вот в Паскале – нет. Процедура (procedure) и функция (function) разливаются даже в объявлении. Смотрите:

Procedure MyProc(n:integer);
Begin
End;

Function MyFunc(n:integer):integer;
Begin
End;

Разница видна даже в объявлении. Процедура – часть кода что либо делающая, но при этом не возвращающая значения, то есть a := MyProc – в Паскале будет ошибочно, а вот a := MyFunc – вполне работоспособно, так как функция возвращает некоторое значении. Чтобы лучше отложилась эта разница в памяти, вспомним школьное понятие функции (это из алгебры) – некий закон, который ставит в соответствия между числами. Коряво, но понятно. Вот пример: функция – закон f(x)=3*x, вместо х подставляем любое число и получаем его соответствие – вот Вам функция в деле. Теперь посмотрим, как это сделать на python. Закон известен, переменная задана. Выполняем.

Def MyFun(n)
result = 3*n
rerurn result


Что этот код нам дает? Мы можем использовать функцию f(x)=3*x в более сложных выражениях. Например, g(x) = (f(x))^2 – можно поступить так

F = Myfunc(5)
G = f^2


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

Def MyFun(n)
result = 3*n
print( result)


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



Я себе позволил немного порисовать на картинке с кодом, чтобы немного яснее стала структура кода из предыдущего поста. Но поясню теперь по картинке – надеюсь ошибки из-за отсутствия форматирования в листингах сократятся.
Итак, всю работу осуществляет одна функция – main. Дальше, по красной линии переходим в class CalcApp(wx.App), который состоит из одной функции (она помечена цветом) OnInit(self), эта функция отсылает нас к еще одному классу - MyWindow(wx.Frame), который состоит уже из двух функций – их я тоже выделил цветом. Вообще, следовало бы отдельно поговорить о понятии класса и объекта, но к этому при необходимости еще вернемся, а пока продолжим.
Рекурсивный вызов функции означает, что функция вызывает сама себя. И, если не оговорить условия выхода из рекурсии, то возникнет переполнение памяти, а какие проблемы возникнут в этом случае, я Вам сказать не берусь.
Рассмотрим рекурсивный вызов функции на примере подсчета факториала. Помните, что это такое? Это произведение всех чисел, которые идут до заданного, включая заданное. И не улыбайтесь, факториал 5 не равен нулю, так как факториал нуля равен единице. Вот у нас есть все для написания этой не хитрой рекурсивной функции и даже условие выхода. Функция будет работать так:
• Получает некое число – аргумент функции
• Сравнивает его с нулем
o Если число – ноль, то возвращаем значение функции – 1
o В противном случае умножаем это число на результат вызова этой же функции, но с аргументом на единицу меньшим

def fact(n):

if n == 0:
result = 1
else:
result = n*fact(n-1)

return result

Вот все, что мы описывали сделано на языке Python – вызываем функции функцию, хотя бы от пяти:

g = fact(5)
print(g)

Проверяем консоль и видим – 120, а не 1, как можно подумать. Почему так получается? Из-за вызова функции самой себя. Я нарисовал картинку, как функция работает.



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

На этом объяснения можно закончить – пост получился чисто теоретический, никакого отношения к Blender’у не имеющий, но что же, теория тоже бывает полезна, особенно, если работа идет со сложными объектами. Прощу прощения, если рассказал всем известные вещи, но прощаться буду шуткой. У нас в руках функция факториала, никакому человеку, знающему математику, не придет в голову вызывать ее с отрицательным аргументом. Но мы же не теоретики-математики, а практики в свободном поиске…так что, при написании кода следует избегать вот таких результатов работы безобидной функции в 11 строчек с пробелами.

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