среда, 24 августа 2011 г.

Факториал в 1 строку.

После реализации сложного и интересного проекта наступает Хандра и кризис идей. Так произошло и после эмулятора SkyNet на питоне(скоро появится статья, о том что это, и вылажу исходники, там много интересных и полезных решений). Сначала решил поcносить все свои системы и поставить заново. Просто от скуки. Сначала поставил Win7. Меня взяла ностальгия и я полтора дня рубился в Rome Total War. Когда надоело, стал лениво листать интересные патерны и трюки на питоне. И вот натыкаюсь на фразу "Реализация факториала через лямбда функцию". Хандру как рукой сняло, глаза загорелись. Захотелось самому додуматься.Браузеры закрыл открыл IDLE Питона и понеслась... Таки сделал, в пол третего ночи, довольный собой, пошел спать. В статье хочу привести пример реализации, и немного подробней рассказать почему оно работает.

К середине статьи я приведу функцию.
Сперва немного теории про лямбда функции. В Вики-учебнике худо расписано, но понять можно.
Лямбда функция эта такая функция которая содержит всего 1 строку(выражение), и в отличии от обыкновенной функции, её легко создать прямо походу выполнения программы.
Используется за частую в аргументах к функциям, при передаче параметров. Если обьяснять на примере то например в вашей программе надо часто использовать подобное выражение( с переменными a,b,c)

sin(ln(a/(b+c*(log(c+b+a))))-sqtr(b+1/c)*tg(a div (c+a)))
И переменные a,b,c постоянно разные, вас задалбывает это всё набирать. И вы делаете следующее:

long_func = lambda a,b,c: sin(ln(a/(b+c*(log(c+b+a))))-sqtr(b+1/c)*tg(a div (c+a))
# И потом используете так
try:
   result = long_func(x,y,z)
except:
   result = -1
   print("Arguments Wrong")
А чтоб сделать рекурсию в лямбде надо вызвать её из самой себя где-то так


recurs = lambda : recurs()
Это сделает бесконечную рекурсию. И так с лямбдой разобрались.
Далее в программе: and, or , not.

Начнём с and.
Смысл функции должен быть ясен логически, не зная о том, что такое дискретная математика. Если мы сообщаем ей 2 правды она скажет "Правда" Если хоть один из аргументов будет ложный, она скажет "Лож". Прошу заметить что притон интерпретирует любое численное значение кроме 0, как правду, а 0 интерпретирует как "Лож". Но в этой функции есть пасхальное яйцо. На котором строится решение выше поставленной задачи. Если мы сообщили ей 2 числовых аргумента(не 0), то она вернёт последний(тот который справа). Если дадим хоть 1 ноль, то она возвращает 0. Но это не всё есть ещё 1 пасхальное яйцо. Которое умно называется Сокращенные вычисления. Насколько мне известно, это присуще почти всем языкам программирования.Смысл вот в чём: при длинном выражении использующем логические функции, вычисление прекращается, как-только встречается хоть 1 переменная решающая итог выражения. Таким образом Что-бы дальше не происходило, на результат это не повлияет.
вот небольшой пример показывающий это:


>>> class F():
 def __init__(self,name,RES):
  self.RES = RES
  self.name = name
 def do(self):
  print self.name
  return self.RES

 
>>> for x in range(10):
 exec("F%s = F('F%s',True)"%(x,x))

 
>>> F4.RES = False
>>> F1.do and F2.do() and F3.do() and F4.do() and F5.do() and F6.do() and F7.do() and F8.do() and F9.do()
F2
F3
F4
False
Для тех кто в не совсем понял что творится: Я создал 10 объектов, при выполнении их метода do() они печатают своё имя, и возвращают True. Кроме 4, его я заставил возвращать False. И так давайте посмотрим, что вышло. А вышло то. что как только 4 объект возвратил False,
все and(ы) перестали выполнятся, потому что в этом нету смысла, всё-рфвно общим результатом будет False.

Теперь про or: or возвратит "Правда" если ХОТЬ 1 элемент правдивый, и возвратит "Лож" если Оба ложны. Пасхальные яйца с and(ом) у or анологичны. Кроме:
Если сообщаем 2 не нуля, то она возвращает нам ПЕРВЫЙ( тот что слева) не ноль, даже если скармливаем ей "Правду " и "не ноль" то она возвратит то что слева. Если пичкаем её двумя неправдами ( будь 0 и False, или False и False, или False и 0, или 0 и 0) Она возвратит нам то что справа. Фокус с Сокращёнными вычислениями, анологичен.
Дальше немножко про функцию not. Что бы её не скормили, она возвратит True, или False. Ну вот пример

>>> not False
True
>>> not True
False
>>> not 0
True
>>> not
Ну и последняя капля ,для тех кто не знает как считается факториал с помошью рекурсии.
Вызывается функция с 2 параметрами, 1 (a) - служебный, собственно он будет результатом. 2(i) - то чиcло факториал которого вы собираетесь получать. В своём теле функция умножает служебный параметр(a) на то число факториал которого вы хотите получить. И полученый результат записывает в (a), после этого функция уменьшает (i) на еденицу, и вызывает себя-же, с новым a и новым i. Так продолжается пока i не станет равным 0, к этому времени в a у нас будет факториал i.
вот код:

def fact_def(a,i):
 if i!=0:
  a=a*i
  i-=1
  return fact_def(a,i)
 else:
  return a
Теперь есть всё, чтоб понять наш пример. Если у вас есть спортивный интерес, лучше не смотрите и постарайтесь на основе вышеизложенного написать своё решение. Я узнавал всё о чём я написал сегодня методом научного тыка, проб, предположений и ошибок. У вас же халява, так что вперёд и с песней.

Ну а для тех кто ленив, то вот :

fact_lambda =lambda a,i: not i-1 and a*i or fact_lambda(a*i,i-1)
Если и сейчас вы недопоняли, то читаем дальше.
Ход 1)Поставлю ка я and. При чём так, что по левую сторону будет i-1, а по правую вызов себя-же с i-1 и a*i. (i-1)and(self(a*i,i-1))
Что это даст? (Я для вас поставлю метку Label1, и бо вернусь к этой строке алгоритма )
Label1: Сначала программа считывает левую сторону и смотрит что она равна True( число ведь) потом правую, а в правой вызывается этаже процедура и повторяется Label1. И так пока левая cторона вдруг не станет 0. Когда это случится вся функция возвратит 0.
К примеру я сказал "Факториал от 1,3" и функция построила рекурсивно для себя вот такую картину (3-1)and(2-1)and(1-1). Когда оно дошло до 1-1. То Рекурсия завершилась, потому что выражение равно False, дальше считать нет смысла.
Ход 2) Пусть будет не and а or, только вместо (i-1) будет not(i-1)   Итого, результат аналогичен. Только функция теперь возвращает не 0 а True, потому что not преобразует 0 в True. Дальше, мы знаем, что функция or когда настигнет правдивый результат( это с левой стороны, для этого надо чтоб i-1 ==0) возвратит нам значение с левой стороны. Значит давайте поместим в левую сторону or, end: ((some1)end(some2))or(some3). При чём этот энд должен возвращать факториал на текущем ходу(a*i) при условии что мы дошли до последнего i  (i=1). То-есть он должен быть гдето таким ( not(i-1) ) and( a*i )  .  Когда i станет 1. not(i-1) возвратит правду, и ( not(i-1) ) and( a*i )  тоже возвратит правду, но не просто правду, а вспомним наши пасхальные яйца, правду из правой её руки. А в её правой руке, находится текущее состояние факториала.
Ход 3) Осталось объеденить. Вспомним что мы сделали в Ходе 1, и в начале Хода 2. Да-да мы планировали, Чтоб вместо True которое возвращает not(i-1) возвращалось значение факториала в данный момент. И вот мы сделали это в конце 2 хода. Вместе это выглядит так.

; (   ( not(i-1) )and( a*i )  )or( self(a*i,i-1) ) 

В так как все логические операторы имеют одинаковый и самый низкий приоритет, скобочки можно убрать, выполнятся они будут последовательно, как раз так, как нам надо. Но со скобочками, мне кажется, нагляднее. Хотя на деле, без скобочек красивее. Ну вот. Разжевал буквально до пюрешки. Вам осталось только сглотнуть.  Спасибо тем у кого хватило терпения дочитать эту статью.

Комментариев нет:

Отправить комментарий