среда, 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) ) 

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

воскресенье, 14 августа 2011 г.

PyQt Пару слов о потоках.

Графический интерфеейс очень не любит потоки. Ну это естественно, его основная цель - пеерисовываться, медленно последовательно прорисовать каждый пиксель каждой кнопочки, ему далеко до одновременной перерисоки, Он как библиотекарь к которому приходишь за учебниками в 8 класс, и он по очереди приносит тебе каждый учебник. А когда его терзают одноверменно 4 процессора по поводу перерисовки, он ничего не понимает. И поэтому всех кроме первого посылает В (|) И вываливается с ошибкой. Попробуем организовать что-то типо очереди.

Так-Так. Лабръораторная работа номер N, Работа потоков с графическим интерфейсом.
Сначала я думал так:"а что если сделать действительно очередь". Тоетсь сделать класс Lock примерно таким

class Lock():
def __init__(self):
self.locked = False
def begin(self):
while self.locked:
pass
self.locked = True
def end(self)
self.locked = False
## только потом я обнаружил что
## этот велосипед изобрели до меня 
## в threading.Lock()
сделать обьект такого класса, и обличить изменение интерфейса в бегины,энды. Былобы неплохо.
Так програнывает в консоли в критических секциях, или протсо в выполнении какого-то кода. но не с Qt.Ребята создававшие PyQt видимо подумали что я буду небезопасно изменять их графический интерфейс. Ну вот они и запретили потокам его трогать. Так что трогать интерфейс может только главный поток. Ну и что делать спроситте вы? Можно посылать сигналы главному потоку. Вот это мы и попытаемся сделать. И так упрощённая задача. Есть пользователь User который выглядит гдето так.

class User():
def __init__(self,name):
self.name = name
def event(self):
pass
def do_some(self):
print('user.do some')
T = threading.Thread(None,self.event,None)
T.start()
time.sleep(10)
print(self.name)
Это упрощённый вариант,функциональностью не кишит. Но зато каждый пользователь исключительный. Для нас отличается только именем. Мы видем что в функция метода do_some вызывает event(событие , которое мы потом заменим в процессе написания кода) и потом работает ещё 10 секунд.И печатает имя своё.

Формочка наша, это PlanText и pushButton. большего для тестов не надо( я ленив, не люблю писать формы, даже в Дизайнере)

сlass MainForm(QMainWindow):
def __init__(self):
super(MainForm, self).__init__()
uic.loadUi("test.ui", self)

self.connect(self.pushButton,
SIGNAL("clicked()"),
self.pushButton_Click)

self.engine = Engine(self)


self.connect(self.engine,
SIGNAL('send(int)'), self.receiver)

def pushButton_Click(self):
T = threading.Thread(None,self.engine.userlist[0].do_some,None)
T.start()
print('pushButton_Click')
pass
def receiver(self, value):
self.plainTextEdit.appendPlainText(str(value))
Пожалуй вы в замешательстве, тчо такое engine... engine это класс в котором живёт программа. Если немного посмотреть то Графический интерфейс у нас в MainWindow, Данные( обьекты) с которыми мы будем работать это User. Ну а как мы с ними будем работать, это лежит в engine.
Вот он

class Engine(QObject):
 def __init__(self,form):
  QObject.__init__(self)
  self.form = form
  self.userlist = []
  self.userlist.append(User('Alex'))
  self.userlist[0].event = self.event
 def event(self):
  print('Engine.event')
  for x in range(10):
   self.emit(SIGNAL('send(int)'), x)
   time.sleep(0.1)

Я его наследую от QObject почему? потому что он должен порождать сигналы(emit) а это умеет делать только QObject в конце концов мы ведь с Qt работаем. И сссылку на engine мы помещаем в MainWindow, чтоб можно было вызывать его методы в реакциях на кнопки, и enginу мы сообщаем ссылку на форму, чтоб он мог её изменять.Что у нас есть в engine? У нас есть userlist в котором за жизнь программы будет жить только 1 пользователь Alex. И обратите внимание на строку self.userlist[0].event = self.event Таким образом я тому самому event в Userе присваиваю мой собственный переопределённый event, в котором содержится посылка сигнала на изменение интерфейса.

 def do_some(self):
  print('user.do some')
  T = threading.Thread(None,self.event,None)# да,да вот тут и вызовится
  T.start()
  time.sleep(10)
  print(self.name)

Теперь ещё раз прочитите код в MainWindow Видите, по нажатию кнопки вызывается метод do_some единственного нашего пользователя. В нутри метода do_some вызовется переопроеделённое event в отдельном потоке.
И 10 раз выполнится строка self.emit(SIGNAL('send(int)'), x) что это значит? это значит что класс engine генерирует Сигнал 'send(int)', с параметром x. В это самое время основной поток увидит что сигнал сгенерирован вот это строкой

  self.connect(self.engine,
  SIGNAL('send(int)'), self.receiver)

Что у нас тут? мы приконектили сигнал 'send(int)' от self.engine к функции self.receiver.
Это значит тчо если вдруг self.engine скажет 'send(int)' , то вызовется функция self.receiver с параметром x.

А что у нас в receiverе? а там у нас добавление в PlainText переданного параметра.
И ву-аля, это основной потом, при этом наш Юзер в это время спокойно отрабатывает свои 10 секунд, и никому не надо ждать. Но вы не могли не заметить что do some я вызываю в отдельном потоке, почему? Ведь сигнал я уже с потока посылаю. Потому что если я не вызову отдельный поток, то 10  секунд работы прийдётся выполнять основному потоку, а это значит что в это время он не сможет принимать сигналы и изменять граф. интерфейс,  Сигналы будут строится в очередь,  и только когда 10 секунд работы завершится , сигналы приймутся, и PlanText обновится. А когда я запускаю работу Юзера в потоке, то основной поток свободен для наших нужд. Ну вот. Теперь я и вы, можем делать сложные программы с длинными асинхронными вычислениями и связями, и вовремя отображать на экране всё это, без падений программы, потому , что вся работа лежит на новых потоках. Основной поток занимается только по сути обновлением Окна.




PS: Обратите внимание на передаваемый сигнал с параметром, в сигнале я явно указываю ТИП передаваемого параметра. К примеру если вам нужно передать строку , советую использовать QString. Если надо передать параметр своего типа, то этот тип надо специально зарегестрировать до его передачи с помощью qRegisterMetaType()


Читать дальше......