Статьи

Тур по случайному рубину

синие абстрактные цифры

В этой статье рассматриваются различные способы генерации случайной (обычно псевдослучайной) информации с помощью Ruby. Случайная информация может быть полезна для множества вещей, в частности для тестирования, генерации контента и безопасности. Я использовал Ruby 2.0.0, но 1.9 должен давать те же результаты.

Kernel # rand и Random

В прошлом случайный диапазон чисел можно описать так:

rand(max - min) + min 

Например, если вы хотите сгенерировать число от 7 до 10 включительно, вы должны написать:

 rand(4) + 7 

Ruby позволяет вам сделать это намного более читабельным способом, передав объект Range в Kernel # rand.

 >> rand(7..10) => 9 >> rand(1.5..2.8) => 1.67699693779624 

Ядро # srand устанавливает семя для ядра # rand. Это может быть использовано для генерации воспроизводимой последовательности чисел. Это может быть удобно, если вы пытаетесь изолировать / воспроизвести ошибку.

 >> srand(333) >> 10.times.map { rand(10) } => [3, 3, 6, 3, 7, 7, 6, 4, 4, 9] >> 10.times.map { rand(10) } => [7, 5, 5, 8, 8, 7, 3, 3, 3, 9] >> srand(333) >> 10.times.map { rand(10) } => [3, 3, 6, 3, 7, 7, 6, 4, 4, 9] >> 10.times.map { rand(10) } => [7, 5, 5, 8, 8, 7, 3, 3, 3, 9] 

Если вам нужно несколько генераторов, то вы можете получить доступ ко всему интерфейсу PRNG (генератора псевдослучайных чисел) в Ruby через Random.

 >> rng = Random.new >> rng.rand(10) => 4 

Random # new может принимать начальное значение в качестве аргумента. Оператор # == вернет true, если два объекта Random имеют одинаковое внутреннее состояние (они начались с одного и того же начального числа и находятся в одном поколении).

 >> rng1 = Random.new(123) >> rng2 = Random.new(123) >> rng1 == rng2 => true >> rng1.rand => 0.6964691855978616 >> rng1 == rng2 => false >> rng2.rand >> 0.6964691855978616 >> rng1 == rng2 => true 

Случайные элементы массива

Если вам нужен случайный элемент из массива, вы можете передать в массив случайный индекс, например так:

 >> arr = [1, 2, 3, 4, 5] >> arr[rand(arr.size)] => 1 

Это не обязательно. Начиная с Ruby 1.9, вы можете использовать Array # sample. Ранее он был известен как Array # choice.

 >> [1, 2, 3, 4, 5].sample => 4 

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

 >> [1, 2, 3, 4, 5].sample(2) => [4, 1] 

Поскольку #sample доступен только для Array, для других коллекций вам потребуется либо сделать это старомодным способом, либо сначала преобразовать их в Array.

На самом деле случайные числа

Иногда псевдослучайные числа не достаточно хороши . Если они основаны на чем-то предсказуемом, они могут быть предсказаны и использованы злоумышленником.

RealRand — это обертка для 3-х подлинных генераторов случайных чисел

  • random.org : генерирует случайность из атмосферного шума
  • FourmiLab (HotBits): использует радиоактивный распад
  • random.hd.org (EntropyPool): утверждает, что использует различные источники, включая локальные процессы / файлы / устройства, хиты веб-страниц и удаленные веб-сайты.

Примечание . На момент написания этой статьи домашняя страница RealRand содержала примеры для 1.x, где классы RealRand сгруппированы в модуле Random. Новейшая версия gem (2.0.0) группирует классы в модуле RealRand, как в этих примерах.

 $ gem install realrand >> require 'random/online' >> rorg = RealRand::RandomOrg.new >> rorg.randbyte(5) => [197, 4, 205, 175, 84] >> fourmi = RealRand::FourmiLab.new >> fourmi.randbyte(5) => [10, 152, 184, 66, 190] >> entropy = RealRand::EntropyPool.new >> entropy.randbyte(5) => [161, 98, 196, 75, 115] 

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

 >> rorg.randnum(5) => [94, 3, 94, 56, 97] >> rorg.randnum(10, 3, 7) => [7, 7, 7, 5, 7, 4, 4, 5, 6, 7] 

Случайная безопасность

Ruby поставляется с SecureRandom для генерации таких вещей, как UUID (универсальные уникальные идентификаторы), токены сеансов и т. Д.

 >> require 'securerandom' >> SecureRandom.hex => "e551a47137a554bb08ba36de34659f60" >> SecureRandom.base64 => "trwolEFZYO7sFeaI+uWrJg==" >> SecureRandom.random_bytes => "\x10C\x86\x02:\x8C~\xB3\xE0\xEB\xB3\xE7\xD1\x12\xBDw" >> SecureRandom.random_number => 0.7432012014930834 

«Безопасность», вероятно, зависит от того, кто вы есть. SecureRandom использует следующие генераторы случайных чисел:

  • OpenSSL
  • / DEV / urandom
  • Win32

Взгляд на код показывает, что по умолчанию используется OpenSSL::Random#random_bytes . Похоже, что PID и тактовое время процесса (наносекунды) используются для энтропии всякий раз, когда изменяется PID.
Я подозреваю, что этого достаточно для большинства вещей, но если вам нужен дополнительный уровень защиты, вы можете использовать RealRand для дополнительной энтропии. К сожалению, SecureRandom не имеет ничего похожего на метод #seed , поэтому вам нужно будет напрямую #seed OpenSSL. Примечание: семена OpenSSL являются строками.

 >> require 'openssl' >> require 'random/online' >> rng = RealRand::RandomOrg.new >> OpenSSL::Random.random_add(rng.randbyte(256).join, 0.0) 

Вы можете прочитать, почему я использовал 0.0 здесь . Согласно обсуждению патча, 0.0 в качестве второго аргумента для #random_add является величиной предполагаемой энтропии. Ранее оно было завышено, поэтому значение было изменено на 0.0. Однако, согласно документации OpenSSL, 2-й аргумент RAND_add — это количество байтов, которые должны быть смешаны в состояние PRNG, а 3-й аргумент — это предполагаемая величина энтропии. OpenSSL::Random#random_add принимает только 2 аргумента (вместо 3), но если они неправильно указали 2-й аргумент, и 0 байтов начального числа перепутаны, то SecureRandom, вероятно, бесполезен для чего-либо серьезного без исправления. Если вы знаете что-нибудь об этом, пожалуйста, оставьте комментарий.

Случайные числа, основанные на вероятностных распределениях

Допустим, вы хотели создать случайные, но реалистичные человеческие массы (то есть веса для неэгалитовых имперцев). Наивная попытка может выглядеть так:

 >> 10.times.map { rand(50..130) } => [92, 84, 77, 55, 95, 127, 120, 71, 105, 94] 

Теперь, хотя вы можете найти людей весом в 50 килограммов (110 фунтов) и некоторых из них весом в 130 килограммов (286 фунтов), большинство из них не настолько экстремальны, что делает приведенный выше результат маловероятным для совершенно случайной выборки (не в основном члены McDonald’s Anonymous и профессиональные борцы).

Один из вариантов — просто игнорировать экстремальные диапазоны:

 >> 10.times.map { rand(55..85) } => [58, 80, 55, 65, 58, 70, 71, 82, 79, 60] 

Числа, которые обычно можно получить, сейчас немного лучше, но они все еще не соответствуют действительности. Вам нужен способ, чтобы большинство случайных чисел попадало в меньший диапазон, а меньший процент — в гораздо больший диапазон.

Что вам нужно, это распределение вероятностей.

Увы, Руби не силен в математическом отделе. Большинство статистических решений, с которыми я сталкивался, были алгоритмы копирования / вставки, неподдерживаемые библиотеки / привязки с небольшим количеством документации и взломы, которые затрагивают математические среды, такие как R. Они также имели тенденцию предполагать неудобное глубокое знание статистики (хорошо, возможно, как один семестр, но мне все равно не нужно возвращаться в колледж, чтобы генерировать случайные числа, основанные на распределении вероятностей).

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

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

Для этого примера я использовал нормальное распределение со средней массой 68 кг и стандартным отклонением 12 кг (только предположения, которые не следует воспринимать как науку).

 $ gem install rubystats >> require 'rubystats' >> human_mass_generator = Rubystats::NormalDistribution.new(68, 12) >> human_masses = 50.times.map { human_mass_generator.rng.round(1) } => [62.6, 75.4, 62.1, 66.2, 50.9, 58.9, 70.8, 51.4, 60.9, 63.5, 72.0, 48.2, 62.3, 63.0, 75.3, 62.6, 103.0, 62.3, 46.6, 66.2, 62.7, 92.2, 76.1, 85.1, 77.5, 75.9, 57.1, 68.3, 63.8, 53.3, 51.6, 75.4, 61.9, 67.7, 58.2, 64.2, 83.3, 69.0, 75.5, 68.8, 60.4, 83.8, 76.2, 81.0, 60.9, 61.2, 55.5, 53.1, 61.4, 79.0] 

В килограмме 2,2 американских фунта, для тех из вас, для кого эти цифры мало что значат.

 >> human_weights = human_masses.map { |i| (i * 2.2).round(1) } => [137.7, 165.9, 136.6, 145.6, 112.0, 129.6, 155.8, 113.1, 134.0, 139.7, 158.4, 106.0, 137.1, 138.6, 165.7, 137.7, 226.6, 137.1, 102.5, 145.6, 137.9, 202.8, 167.4, 187.2, 170.5, 167.0, 125.6, 150.3, 140.4, 117.3, 113.5, 165.9, 136.2, 148.9, 128.0, 141.2, 183.3, 151.8, 166.1, 151.4, 132.9, 184.4, 167.6, 178.2, 134.0, 134.6, 122.1, 116.8, 135.1, 173.8] 

Если это ваш путь, вы также можете проверить gsl , распределение и статистику2

Случайные строки

На стеке есть хорошая страница, в которой есть несколько решений для генерации случайных строк. Мне понравилось это:

 >> (0...8).map { (65 + rand(26)).chr }.join => "FWCZOUOR" >> (0...50).map{ ('a'..'z').to_a[rand(26)] }.join => ygctkhpzxkbqggvxgmocyhvbocouylzfitujyyvqhzunvgpnqb 

Webster

Webster — это англо-англоязычный генератор слов. Это может быть полезно для генерации кодов подтверждения в западных локализациях.

 $ gem install webster >> require 'webster' >> w = Webster.new >> w.random_word => "unavailed" >> 20.times.map { w.random_word } => ["bombo", "stellated", "kitthoge", "starwort", "poleax", "lacinia", "crusty", "hazelly", "liber", "servilize", "alternate", "cembalist", "dottore", "ullage", "tusculan", "tattlery", "ironness", "grounder", "augurship", "dupedom"] 

случайное слово

Gem для случайных слов утверждает, что использует массивный словарь wordnet для своих методов. Вы когда-нибудь обвиняли вас в использовании «этих громких слов?». Это те слова, которые производят случайные слова.

 $ gem install random-word >> require 'random-word' >> 10.times.map { RandomWord.adjs.next } => ["orthographic", "armenian", "nongranular", "ungetatable", "magnified", "azimuthal", "geosynchronous", "platitudinous", "deep_in_thought", "substitutable"] >> 10.times.map { RandomWord.nouns.next } => ["roy_wilkins", "vascular_tissue", "bygone", "vermiform_process", "anamnestic_reaction", "engagement", "soda_niter", "humber", "fire_salamander", "pyridoxamine"] >> 10.times.map { RandomWord.phrases.next } => ["introvertive glenoid_cavity", "sugarless reshipment", "anticipant cyclotron", "unheaded ligustrum_amurense", "dauntless contemplativeness", "nativistic chablis", "scapular typhoid_fever", "warlike dead_drop", "pyrotechnic varicocele", "avionic cyanite"] 

Если вы хотите избавиться от этих подчеркиваний, просто добавьте gsub:

 >> 10.times.map { RandomWord.nouns.next.gsub('_', ' ') } => ["litterbug", "nebe", "business sector", "stochastic process", "playmaker", "esthesia", "granny knot", "purple osier", "sterculia family", "ant cow"] 

обманщик

Faker полезен для генерации тестовых данных. Он имеет довольно большую библиотеку данных, поэтому вы также можете создавать процедурный игровой контент.

 $ gem install faker >> require 'faker' >> 20.times.map { Faker::Name.name } => ["Gilberto Moen", "Miss Caleb Emard", "Julie Daugherty", "Katelin Rau", "Sheridan Mueller", "Cordell Steuber", "Sherwood Barrows", "Alysson Lind II", "Kareem Toy", "Allison Connelly", "Orin Nolan", "Dolores Kessler", "Kassandra Hackett Jr.", "Mikayla Spencer II", "Lonie Kertzmann", "Emile Walsh V", "Tara Emmerich", "Mrs. Beryl Keeling", "Jerry Nolan DVM", "Linnie Thompson"] >> 10.times.map { Faker::Internet.email } => ["catherine.schamberger@toy.net", "eleonore@heaney.net", "toni@colliermoore.org", "merl_miller@pfeffer.net", "florine_dach@gusikowski.net", "bernadine@walter.net", "stevie.farrell@crooks.net", "janick@satterfield.name", "leanna.lubowitz@bogisich.biz", "rey@kutch.info"] >> 10.times.map { Faker::Address.street_address } => ["3102 Jasen Haven", "8748 Huel Parks", "1886 Gutkowski Creek", "837 Jennie Spurs", "4921 Carter Coves", "7714 Ida Falls", "8227 Sawayn Bypass", "269 Kristopher Village", "31185 Santos Inlet", "96861 Heaney Street"] >> 10.times.map { Faker::Company.bs } => ["aggregate extensible markets", "repurpose leading-edge metrics", "synergize global channels", "whiteboard virtual platforms", "orchestrate ubiquitous relationships", "enable interactive e-services", "engineer end-to-end convergence", "deploy enterprise e-services", "benchmark wireless solutions", "generate impactful eyeballs"] 

Впечатлили еще? Faker также предлагает данные для нескольких локалей . Например, может быть, вы делаете игру, которая происходит в Германии, и вам нужны случайные имена персонажей сорта Deutsch.

 >> Faker::Config.locale = :de >> 10.times.map { Faker::Name.name } => ["Mara Koehl", "Penelope Wagner", "Karolina Kohlmann", "Melek Straub", "Marvin Kettenis", "Lyn Behr", "Karina Deckert", "Janne Damaske", "Sienna Freimuth", "Lias Buder"] 

Или, может быть, вы хотели бы, чтобы компания ловила фразы … на испанском

 >> Faker::Config.locale = :es >> 5.times.map { Faker::Company.catch_phrase } => ["adaptador interactiva Extendido", "lÃnea segura tangible Distribuido", "superestructura asÃncrona Diverso", "flexibilidad bidireccional Total", "productividad acompasada Re-implementado"] 

Конечно, есть и материал Lorem Ipsum.

 >> Faker::Lorem.paragraph => "Sit excepturi et possimus et. Quam consequatur placeat fugit aut et sint. Sint assumenda repudiandae veniam iusto tenetur consequatur." 

Не забудьте проверить документы, чтобы увидеть, что еще он может сделать. Также, если это действительно ваша вещь, посмотрите на функционального предшественника Faker, Forgery . Он вышел из употребления, но, кажется, его легко адаптировать.

random_data

Один из недостатков Faker заключается в том, что он, похоже, не обеспечивает генерацию имен по полу. Gem random_data делает, хотя он мог бы использовать некоторую работу ( начиная с версии 1.6.0).

 $ gem install random_data >> require 'random_data' >> 20.times.map { Random.first_name_female } => ["Donna", "Sharon", "Anna", "Nancy", "Betty", "Margaret", "Maria", "Helena", "Carol", "Cheryl", "Donna", "Cheryl", "Sharon", "Jennifer", "Helena", "Cheryl", "Jessica", "Elizabeth", "Elizabeth", "Sandra"] >> 20.times.map { Random.first_name_male } => ["Richard", "William", "Arthur", "David", "Roger", "Daniel", "Simon", "Anthony", "Adam", "George", "George", "David", "Christopher", "Steven", "Edgar", "Arthur", "Richard", "Kenneth", "Philip", "Charles"] 

Глядя на эти имена, они немного… ну, давайте просто скажем, что нет «Sheniquoi».

Честно говоря, у него есть довольно крутые методы датировки и определения местоположения. Random#date появляется, чтобы выбрать даты рядом с текущей.

 >> 10.times.map { Random.date.strftime('%a %d %b %Y') } => ["Mon 16 Sep 2013", "Sat 21 Sep 2013", "Tue 24 Sep 2013", "Sat 28 Sep 2013", "Thu 03 Oct 2013", "Fri 20 Sep 2013", "Mon 23 Sep 2013", "Tue 24 Sep 2013", "Sun 29 Sep 2013", "Thu 03 Oct 2013"] >> 30.times.map { Random.zipcode } => ["33845", "87791", "27961", "94156", "40897", "24887", "51985", "12099", "82247", "33015", "77437", "93497", "35269", "94426", "58919", "50170", "99952", "62229", "73271", "34316", "17547", "24590", "99613", "52954", "95117", "38454", "70195", "84415", "97096", "58282"] >> 30.times.map { Random.country } => ["Fiji", "Sudan", "Cambodia", "Belgium", "Rwanda", "Czech Republic", "Marshall Islands", "Georgia", "Saudi Arabia", "United Arab Emirates", "Switzerland", "Uganda", "Uruguay", "Somalia", "Ukraine", "Canada", "Jamaica", "Cape Verde", "Indonesia", "Sudan", "Malaysia", "Virgin Islands (US)", "Turkmenistan", "Libya", "Sweden", "St. Vincent and the Grenadines", "Korea, Dem. Rep.", "Faeroe Islands", "Myanmar", "Zimbabwe"] 

Примечание. Согласно странице github random_data, «почтовые индексы абсолютно случайны и не могут быть настоящими почтовыми индексами».

Raingrams

Драгоценный камень raingrams, пожалуй, самая интересная вещь в этом уроке. Он может производить случайные предложения или абзацы на основе предоставленного текста. Например, если вы какой-то больной, испорченный знаток комментариев на YouTube, вы можете создать чудовище, которое генерирует практически бесконечные комментарии на YouTube, переобучая модель худшими комментариями по мере продвижения, скребя глубины абсурда, пока не получите что-то нелепое. нравиться:

«Ни один разговор с демократом не идет так, как Нил Деграсс Тайсон — это, в основном, Карл Саган Блэк, в возрасте nintendo, когда я был в твоем возрасте, я думал, что жадные корпорации работали так, как этот комментарий был удален, потому что видео не имеет ничего общего с тем, что делает эта мама 30 долларов в день, заполняющие опросы Ричарда Докинза, которые все еще являются лучшей любовной историей, чем сумерки ».

Согласно википедии , «n-грамм — это непрерывная последовательность из n элементов из данной последовательности текста или речи. Предметы могут быть фонемами, слогами, буквами, словами или парами оснований в зависимости от приложения ».

Raingrams описывает себя как «гибкую и универсальную библиотеку ngrams, написанную на Ruby». Он генерирует текстовое содержимое путем построения моделей на основе текста, встречающегося в парах, трио и т. Д. — кажется, что нет предела сложности модель, которую вы можете использовать, но включенные классы моделей идут от BigramModel к HexagramModel.

 $ gem install raingrams 

Создать и обучить модель легко.

 require 'raingrams' model = Raingrams::BigramModel.new model.train_with_text "When you are courting a nice girl an hour seems like a second. When you sit on a red-hot cinder for a second that seems like an hour. That's relativity." model.random_sentence => "When you sit on a nice girl an hour." 

Если вы включаете модуль Raingrams, вам не нужно использовать его в качестве пространства имен.

 include Raingrams model = BigramModel.new 

Одна из действительно приятных вещей в Raingrams — это возможность обучать его с помощью файлов или веб-страниц, а не просто строк. Raingrams предоставляет следующие методы обучения:

  • Model#train_with_paragraph
  • Model#train_with_text
  • Model#train_with_file
  • Model#train_with_url

Я был приятно удивлен, обнаружив, что #train_with_url работает … довольно хорошо! Он не идеален, и он может создавать обрезанные предложения, но написать фильтр для отбрасывания неработающих предложений, вероятно, проще, чем написать скребок для каждого сайта, с которым вы хотите обучать свои модели.

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

 >> require 'raingrams' >> include Raingrams >> model = BigramModel.new >> model.train_with_url "http://en.wikipedia.org/wiki/Central_processing_unit" >> model.random_sentence => "One notable late CPU decodes instructions rather than others before him such as pipelining and 1960s no arguments but still continued by eight binary CPU register may not see 4." 

Согласованность почти до правдоподобия начинается с квадраграмм. К сожалению, для получения «случайного» текста квадрограммам требуется совсем немного данных.

 >> model = QuadgramModel.new >> model.train_with_url "http://en.wikipedia.org/wiki/Central_processing_unit" >> model.random_sentence => "Tube computers like EDVAC tended to average eight hours between failures whereas relay computers like the slower but earlier Harvard Mark I which was completed before EDVAC also utilized a stored-program design using punched paper tape rather than electronic memory." 

Если вы хотите создать прозу-генератор «Лавкрафта» , вы можете тренировать n-граммовые модели в его рассказах.

 >> model = QuadgramModel.new >> model.train_with_url "http://www.dagonbytes.com/thelibrary/lovecraft/mountainsofmaddness.htm" >> model.random_sentence => "Halfway uphill toward our goal we paused for a momentary breathing spell and turned to look again at poor Gedney and were standing in a kind of mute bewilderment when the sounds finally reached our consciousness the first sounds we had heard since coming on the camp horror but other things were equally perplexing." >> model.random_sentence => "First the world s other extremity put an end to any of the monstrous sight was indescribable for some fiendish violation of known natural law seemed certain at the outset." 

Этот недостающий апостроф в «мире» не является опечаткой, и он присутствовал в оригинальном тексте. Вам нужно будет следить за такими вещами.

Вывод

У Ruby есть что предложить, когда дело доходит до случайных данных. Более того, многие из этих библиотек можно легко модифицировать или улучшить. Если вы новичок в Ruby и хотите принять участие, это прекрасная возможность.