Книга: Программирование на языке Ruby

18.2.1. Получение истинно случайных чисел из Web

18.2.1. Получение истинно случайных чисел из Web

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

Джон фон Нейман

В модуле Kernel есть функция rand, которая возвращает случайное число, но вот беда — число-то не является истинно случайным. Если вы математик, криптограф или еще какой-нибудь педант, то назовете эту функцию генератором псевдослучайных чисел, поскольку она пользуется алгебраическими методами для детерминированного порождения последовательности чисел. Стороннему наблюдателю эти числа представляются случайными и даже обладают необходимыми статистическими свойствами, но рано или поздно последовательность начнет повторяться. Мы можем даже намеренно (или случайно) повторить ее, задав ту же самую затравку.

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

Есть источники случайных чисел и в Web. Один из них — сайт www.random.org, который мы задействуем в следующем примере.

Программа в листинге 18.4 имитирует подбрасывание пяти обычных (шестигранных) костей. Конечно, игровые фанаты могли бы увеличить число граней до 10 или 20, но тогда стало бы сложно рисовать ASCII-картинки.

Листинг 18.4. Случайное бросание костей

require 'net/http'
HOST = "www.random.org"
RAND_URL = "/cgi-bin/randnum?col=5&"
def get_random_numbers(count=1, min=0, max=99)
 path = RAND_URL + "num=#{count}&min=#{min}&max=#{max}"
 connection = Net::HTTP.new(HOST)
 response, data = connection.get(path)
 if response.code == "200"
  data.split.collect { |num| num.to_i }
 else
  []
 end
end
DICE_LINES = [
 "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ",
 "|     | |  *  | |  *  | | * * | | * * | | * * | ",
 "|  *  | |     | |  *  | |     | |  *  | | * * | ",
 "|     | |  *  | |  *  | | * * | | * * | | * * | ",
 "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ "
DIE_WIDTH = DICE_LINES[0].length/6
def draw_dice(values)
 DICE_LINES.each do | line |
  for v in values
   print line[(v-1)*DIE_WIDTH, DIE_WIDTH]
   print " "
  end
  puts
 end
end
draw_dice(get_random_numbers(5, 1, 6))

Здесь мы воспользовались классом Net::НТТР для прямого взаимодействия с Web-сервером. Считайте, что эта программа — узкоспециализированный браузер. Мы формируем URL и пытаемся установить соединение; когда оно будет установлено, мы получаем ответ, возможно, содержащий некие данные. Если код ответа показывает, что ошибок не было, то можно разобрать полученные данные. Предполагается, что исключения будут обработаны вызывающей программой.

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

В листинге 18.5 эта мысль реализована. Буфер заполняется отдельным потоком и совместно используется всеми экземплярами класса. Размер буфера и «нижняя отметка» (@slack) настраиваются; какие значения задать в реальной программе, зависит от величины задержки при обращении к серверу и от того, как часто приложение выбирает случайное число из буфера.

Листинг 18.5. Генератор случайных чисел с буферизацией

require "net/http"
require "thread"
class TrueRandom
 def initialize(min=nil,max=nil,buff=nil,slack=nil)
  @buffer = []
  @site = "www.random.org"
  if ! defined? @init_flag
   # Принять умолчания, если они не были заданы явно И
   # это первый созданный экземпляр класса...
   @min = min || 0
   @max = max || 1
   @bufsize = buff || 1000
   @slacksize = slack || 300
   @mutex = Mutex.new
   @thread = Thread.new { fillbuffer }
   @init_flag = TRUE # Значение может быть любым.
  else
   @min = min || @min
   @max = max || @max
   @bufsize = buff || @bufsize
   @slacksize = slack || @slacksize
  end
  @url = "/cgi-bin/randnum" +
   "?num=#@bufsize&min=#@min&max=#@max&col=1"
 end
 def fillbuffer
  h = Net::HTTP.new(@site, 80)
  resp, data = h.get(@url, nil)
  @buffer += data.split
 end
 def rand
  num = nil
  @mutex.synchronize { num = @buffer.shift }
  if @buffer.size < @slacksize
   if ! @thread.alive?
    @thread = Thread.new { fillbuffer }
   end
  end
  if num == nil
   if @thread.alive?
    @thread.join
   else
    @thread = Thread.new { fillbuffer }
    @thread.join
   end
   @mutex.synchronize { num = @buffer.shift }
  end
  num.to_i
 end
end
t = TrueRandom.new(1,6,1000,300)
count = {1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0}
10000.times do |n|
 x = t.rand
 count[x] += 1
end
p count
# При одном прогоне:
# {4=>1692, 5=>1677, 1=>1678, 6=>1635, 2=>1626, 3=>1692}

Оглавление книги


Генерация: 1.192. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз