воскресенье, 3 марта 2013 г.

Модуль Random

  Модуль random из состава python вполне приличная вещь. Если верить встроенной документации, то период генерации ограничивается числом (2**19937)-1. Число, мягко говоря - фантастическое. Это правоверная реализация ГПСПЧ на  Си. Надо полагать, что делали не дураки, но как требует чекисткая мудрость: доверяй, но проверяй.
  В итоге родился код, суть которого в двух словах: в одном из процессов идёт отрисовка Canvas размером, который задаётся при создании класса clsRndTester(Tk). Второй процесс ставит в очередь массив точек с координатами и цветом полученными из класса Random. Теоретически, генерацией чисел можно нагрузить столько ядер, сколько есть в системе. Пакет multiprocessing позволяет это сделать не напрягаясь. Да только генерация чисел происходит куда быстрее, чем отрисовка точек на экране ;). Код ниже:

# -*- coding: utf8 -*-
'''
Программулина тестирует модуль random.
На экране выводит массив точек для оценки случайности.
Размер экрана - 900*500=480 тыс. - вполне достаточно для
оценки случайности.
'''
from random import Random
from Tkinter import Tk, Label, Frame, Button, Canvas
from multiprocessing import Process, Lock, cpu_count, Queue
from time import sleep

class clsGenerator:
     def __init__(self, queue=None, dx=None, dy=None):
         self.queue=queue
         self.dx=dx
         self.dy=dy
         self.rnd=Random()
         self.proc=Process(target=self.create_rnd)
         self.proc.start()
    
     def create_rnd(self):
         while True:
             if not self.queue.full():
                 x=self.rnd.randint(0,self.dx)
                 y=self.rnd.randint(0,self.dy)
                 color='#'
                 for i in range(0,3):
                     c=self.rnd.randint(0,15)
                     if c<=9:
                         c=chr(c+ord('0'))
                     else:
                         c-=9
                         c=chr(c+ord('@'))
                     color+=c
                 self.queue.put((x,y, color))
             else:
                 sleep(0.005)
        
class clsRndTester(Tk):
     def __init__(self, dx=500, dy=300):
         def create_self():
             Tk.__init__(self)
             self.running=0
             # получить число процессоров
             self.cpu_num=cpu_count()
             # создать очередь сообщений
             self.queue=Queue(maxsize=500)
             # создать процессы по числу ядер проца
             # первое ядро не трогать - под графику
             self.list_proc=[]
             for i in xrange(0, self.cpu_num-1):
                 i0=clsGenerator(queue=self.queue, dx=self.dx, dy=self.dy)
                 self.list_proc.append(i0)
         def create_frmUp():
             def create_lblProgress():
                 self.lblProgress=Label(self.frmUp, relief='sunken', text='0', width=10)
                 self.lblProgress.pack(side='left')
             def create_btnStart():
                 self.btnStart=Button(self.frmUp,
                         text='Start',
                         command=self.start_rnd)
                 self.btnStart.pack()
             self.frmUp=Frame(self)
             self.frmUp.pack(side='top', fill='x')
             create_lblProgress()
             create_btnStart()
         def create_frmDown():
             def create_cnvRnd():
                 self.cnvRnd=Canvas(self.frmDown,
                         bg='#FFFFFF',
                         relief='groove',
                         bd=3,
                         height=self.dy,
                         width=self.dx)
                 self.cnvRnd.pack()
             self.frmDown=Frame(self)
             self.frmDown.pack(side='bottom', fill='both', expand=1)
             create_cnvRnd()
        
         self.dx=dx
         self.dy=dy
         create_self()
         create_frmUp()
         create_frmDown()
    
     def start_rnd(self, event=''):
         if self.running==0:
             self.btnStart['text']='Stop'
             self.running=1
         else:
             self.btnStart['text']='Start'
             self.running=0
         if self.running:
             for x0 in xrange(self.dx):
                 if not self.running:
                     break
                 for y0 in xrange(self.dy):
                     if not self.running:
                         break
                     crd=self.queue.get()
                     x,y,fill=crd
                     self.cnvRnd.create_line(x,y,x+1,y+1, fill=fill)
                 self.lblProgress['text']=str(int(x0/500.0*100))+'%'
                 self.update()
            
if __name__=='__main__':
     app=clsRndTester(dx=900, dy=500)
     app.mainloop()


Типичная картинка с размером 200 на 300:

   Тут стоит упомянуть о том, что шкодю я на нетбуке с 2-мя ядрами 1,3 ГГц и 1 ГБ RAM. Графика, понятное дело - номинальная. Так что, вывод 450 тыс. точек на экран занял у меня почти час. Картинка на разных разрешениях имела явно разные кластеры по цвету и группировке. Но в целом, можно сказать, что диких косяков нет.
  Технически, можно устроить seed двум разным генераторам от исходного, но как говорит теория - эти два генератора связаны единым алгоритмом, а значит, можно запросто поломать монотонность генератора и получить отрицательный эффект. Тем более что, (2**19937)-1 в десятичной форме - это 6001 цифра для записи полного числа. Хочу напомнить, что "Гугл" - это всего лишь 10**100.

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

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