SpÄļu izstrÄdÄ bieži kaut kas jÄsaista ar nejauŔību: Unity Å”im nolÅ«kam ir savs Random, un paralÄli tam ir System.Random. KÄdreiz vienÄ no projektiem man radÄs iespaids, ka abi varÄtu darboties savÄdÄk (lai gan vajadzÄtu vienmÄrÄ«gu sadalÄ«jumu).
Tad viÅi neiedziļinÄjÄs detaļÄs - pietika ar pÄreju uz System.Random visas problÄmas. Tagad mÄs nolÄmÄm to izpÄtÄ«t sÄ«kÄk un veikt nelielu izpÄti: cik āneobjektÄ«viā vai paredzami ir RNG un kuru izvÄlÄties. TurklÄt vairÄk nekÄ vienu reizi esmu dzirdÄjis pretrunÄ«gus viedokļus par viÅu āgodÄ«gumuā - mÄÄ£inÄsim izdomÄt, kÄ Ä«stie rezultÄti ir salÄ«dzinÄmi ar deklarÄtajiem.
ÄŖsa izglÄ«tÄ«bas programma jeb RNG patiesÄ«bÄ ir RNG
Ja esat jau iepazinies ar nejauÅ”o skaitļu Ä£eneratoriem, varat nekavÄjoties pÄriet uz sadaļu āPÄrbaudeā.
NejauÅ”ie skaitļi (RN) ir skaitļu virkne, kas Ä£enerÄta, izmantojot kÄdu nejauÅ”u (haotisku) procesu, kas ir entropijas avots. Tas ir, Ŕī ir secÄ«ba, kuras elementus savstarpÄji nesaista neviens matemÄtisks likums ā tiem nav cÄloÅsakarÄ«bas.
To, kas rada nejauÅ”o skaitļu, sauc par nejauÅ”o skaitļu Ä£eneratoru (RNG). Å Ä·iet, ka viss ir elementÄri, bet, ja mÄs pÄrejam no teorijas uz praksi, tad patiesÄ«bÄ nav tik vienkÄrÅ”i ieviest programmatÅ«ras algoritmu Å”Ädas secÄ«bas Ä£enerÄÅ”anai.
Iemesls ir tÄda paÅ”a haosa neesamÄ«ba mÅ«sdienu plaÅ”a patÄriÅa elektronikÄ. Bez tÄ nejauÅ”ie skaitļi pÄrstÄj bÅ«t nejauÅ”i, un to Ä£enerators pÄrvÄrÅ”as par parastu acÄ«mredzami definÄtu argumentu funkciju. VairÄkÄm specialitÄtÄm IT jomÄ tÄ ir nopietna problÄma (piemÄram, kriptogrÄfija), bet citÄm ir pilnÄ«gi pieÅemams risinÄjums.
Ir nepiecieÅ”ams uzrakstÄ«t algoritmu, kas atgrieztu, lai arÄ« ne Ä«sti nejauÅ”us skaitļus, bet pÄc iespÄjas tuvÄk tiem - tÄ sauktos pseidogadÄ«juma skaitļus (PRN). Algoritmu Å”ajÄ gadÄ«jumÄ sauc par pseidogadÄ«juma skaitļu Ä£eneratoru (PRNG).
PRNG izveidei ir vairÄkas iespÄjas, taÄu tÄlÄk norÄdÄ«tais bÅ«s aktuÄls ikvienam:
- NepiecieÅ”amÄ«ba pÄc iepriekÅ”Äjas inicializÄcijas.
PRNG nav entropijas avota, tÄpÄc pirms lietoÅ”anas tam ir jÄpieŔķir sÄkotnÄjais stÄvoklis. Tas ir norÄdÄ«ts kÄ skaitlis (vai vektors) un tiek saukts par sÄklu (izlases sÄkla). Bieži vien procesora pulksteÅa skaitÄ«tÄjs vai sistÄmas laika skaitliskais ekvivalents tiek izmantots kÄ sÄkums.
- SecÄ«bas reproducÄjamÄ«ba.
PRNG ir pilnÄ«bÄ determinÄta, tÄpÄc inicializÄcijas laikÄ norÄdÄ«tÄ sÄkla unikÄli nosaka visu turpmÄko skaitļu secÄ«bu. Tas nozÄ«mÄ, ka atseviŔķs PRNG, kas inicializÄts ar vienu un to paÅ”u sÄklu (dažÄdos laikos, dažÄdÄs programmÄs, dažÄdÄs ierÄ«cÄs), Ä£enerÄs to paÅ”u secÄ«bu.
Ir jÄzina arÄ« PRNG raksturojoÅ”ais varbÅ«tÄ«bas sadalÄ«jums ā kÄdus skaitļus tas Ä£enerÄs un ar kÄdu varbÅ«tÄ«bu. VisbiežÄk tas ir normÄls sadalÄ«jums vai vienmÄrÄ«gs sadalÄ«jums.
NormÄls sadalÄ«jums (pa kreisi) un vienmÄrÄ«gs sadalÄ«jums (pa labi)
PieÅemsim, ka mums ir godÄ«gs kauliÅÅ” ar 24 malÄm. Ja jÅ«s to izmetat, varbÅ«tÄ«ba iegÅ«t vienu bÅ«s vienÄda ar 1/24 (tÄda pati kÄ varbÅ«tÄ«ba iegÅ«t jebkuru citu skaitli). Izdarot daudz metienu un piefiksÄjot rezultÄtus, pamanÄ«sit, ka visas malas izkrÄ«t aptuveni vienÄdi. BÅ«tÄ«bÄ Å”o matricu var uzskatÄ«t par RNG ar vienmÄrÄ«gu sadalÄ«jumu.
Ko darÄ«t, ja jÅ«s uzreiz izmetat 10 no Å”iem kauliÅiem un saskaitÄt kopÄjos punktus? Vai tam tiks saglabÄta vienveidÄ«ba? NÄ. VisbiežÄk summa bÅ«s tuvu 125 punktiem, tas ir, kÄdai vidÄjai vÄrtÄ«bai. Un rezultÄtÄ jau pirms metiena veikÅ”anas var aptuveni novÄrtÄt nÄkotnes rezultÄtu.
Iemesls ir tÄds, ka ir vislielÄkais kombinÄciju skaits, lai iegÅ«tu vidÄjo punktu skaitu. Jo tÄlÄk no tÄ, jo mazÄk kombinÄciju - un attiecÄ«gi mazÄka ir zaudÄjuma iespÄjamÄ«ba. Ja Å”ie dati ir vizualizÄti, tie neskaidri atgÄdinÄs zvana formu. TÄpÄc ar zinÄmu stiepÅ”anos 10 kauliÅu sistÄmu var saukt par RNG ar normÄlu sadalÄ«jumu.
VÄl viens piemÄrs, tikai Å”oreiz lidmaŔīnÄ ā Å”auÅ”ana mÄrÄ·Ä«. Å ÄvÄjs bÅ«s RNG, kas Ä£enerÄ skaitļu pÄri (x, y), kas tiek parÄdÄ«ts diagrammÄ.
PiekrÄ«tiet, ka opcija kreisajÄ pusÄ ir tuvÄk reÄlajai dzÄ«vei - tas ir RNG ar normÄlu sadalÄ«jumu. Bet, ja jums ir nepiecieÅ”ams izkaisÄ«t zvaigznes tumÅ”Äs debesÄ«s, tad pareizÄkais variants, kas iegÅ«ts, izmantojot RNG ar vienmÄrÄ«gu sadalÄ«jumu, ir labÄk piemÄrots. Parasti izvÄlieties Ä£eneratoru atkarÄ«bÄ no veicamÄ uzdevuma.
Tagad parunÄsim par PNG secÄ«bas entropiju. PiemÄram, ir secÄ«ba, kas sÄkas Å”Ädi:
89, 93, 33, 32, 82, 21, 4, 42, 11, 8, 60, 95, 53, 30, 42, 19, 34, 35, 62, 23, 44, 38, 74, 36, 52, 18, 58, 79, 65, 45, 99, 90, 82, 20, 41, 13, 88, 76, 82, 24, 5, 54, 72, 19, 80, 2, 74, 36, 71, 9, ...
Cik nejauÅ”i ir Å”ie skaitļi no pirmÄ acu uzmetiena? SÄksim ar sadalÄ«juma pÄrbaudi.
Tas izskatÄs gandrÄ«z viendabÄ«gs, bet, ja lasÄt divu skaitļu secÄ«bu un interpretÄjat tos kÄ koordinÄtas plaknÄ, jÅ«s iegÅ«stat Å”o:
Raksti kļūst skaidri redzami. Un tÄ kÄ secÄ«bÄ esoÅ”ie dati ir sakÄrtoti noteiktÄ veidÄ (tas ir, tiem ir zema entropija), tas var izraisÄ«t Å”o āneobjektivitÄtiā. Vismaz Å”Äds PRNG nav Ä«paÅ”i piemÄrots koordinÄtu Ä£enerÄÅ”anai plaknÄ.
VÄl viena secÄ«ba:
42, 72, 17, 0, 30, 0, 15, 9, 47, 19, 35, 86, 40, 54, 97, 42, 69, 19, 20, 88, 4, 3, 67, 27, 42, 56, 17, 14, 20, 40, 80, 97, 1, 31, 69, 13, 88, 89, 76, 9, 4, 85, 17, 88, 70, 10, 42, 98, 96, 53, ...
Å Ä·iet, ka Å”eit viss ir kÄrtÄ«bÄ pat lidmaŔīnÄ:
Apskatīsim apjomu (lasīt trīs ciparus vienlaikus):
Un atkal modeļi. Vairs nav iespÄjams izveidot vizualizÄciju ÄetrÄs dimensijÄs. TaÄu modeļi var pastÄvÄt gan Å”ajÄ dimensijÄ, gan lielÄkÄs.
KriptogrÄfijÄ, kur PRNG tiek izvirzÄ«tas visstingrÄkÄs prasÄ«bas, Å”Äda situÄcija ir kategoriski nepieÅemama. TÄpÄc to kvalitÄtes novÄrtÄÅ”anai ir izstrÄdÄti Ä«paÅ”i algoritmi, kurus mÄs tagad neskarsim. TÄma ir plaÅ”a un ir pelnÄ«jusi atseviŔķu rakstu.
TestÄÅ”ana
Ja mÄs kaut ko nezinÄm droÅ”i, tad kÄ ar to strÄdÄt? Vai ir vÄrts ŔķÄrsot ceļu, ja nezinÄt, kurÅ” luksofors to atļauj? Sekas var bÅ«t dažÄdas.
Tas pats attiecas uz bÄdÄ«gi slaveno nejauŔību VienotÄ«bÄ. Ir labi, ja dokumentÄcija atklÄj nepiecieÅ”amÄs detaļas, bet raksta sÄkumÄ minÄtais stÄsts noticis tieÅ”i vÄlamÄs specifikas trÅ«kuma dÄļ.
Un, ja jÅ«s nezinÄt, kÄ rÄ«ks darbojas, jÅ«s nevarÄsit to pareizi izmantot. KopumÄ ir pienÄcis laiks pÄrbaudÄ«t un veikt eksperimentu, lai beidzot pÄrliecinÄtos vismaz par izplatÄ«Å”anu.
RisinÄjums bija vienkÄrÅ”s un efektÄ«vs ā vÄc statistiku, iegÅ«sti objektÄ«vus datus un apskati rezultÄtus.
Studiju priekŔmets
Ir vairÄki veidi, kÄ Ä£enerÄt nejauÅ”us skaitļus Unity ā mÄs pÄrbaudÄ«jÄm piecus.
- System.Random.Next(). Ä¢enerÄ veselus skaitļus noteiktÄ vÄrtÄ«bu diapazonÄ.
- System.Random.NextDouble(). Ä¢enerÄ dubultas precizitÄtes skaitļus diapazonÄ no [0; 1).
- UnityEngine.Random.Range(). Ä¢enerÄ atseviŔķus precizitÄtes skaitļus (peld) noteiktÄ vÄrtÄ«bu diapazonÄ.
- UnityEngine.Random.value. Ä¢enerÄ atseviŔķus precizitÄtes skaitļus (peld) diapazonÄ no [0; 1).
- VienotÄ«ba.MatemÄtika.Random.NextFloat(). Daļa no jaunÄs VienotÄ«bas.MatemÄtikas bibliotÄkas. Ä¢enerÄ atseviŔķus precizitÄtes skaitļus (peld) noteiktÄ vÄrtÄ«bu diapazonÄ.
GandrÄ«z visur dokumentÄcijÄ tika norÄdÄ«ts vienmÄrÄ«gs sadalÄ«jums, izÅemot UnityEngine.Random.value (kur sadalÄ«jums nebija norÄdÄ«ts, bet pÄc analoÄ£ijas ar UnityEngine.Random.Range() uniformu arÄ« bija paredzÄts) un Unity.Mathematics.Random. .NextFloat() (kur pamats ir xorshift algoritms, kas nozÄ«mÄ, ka atkal jÄgaida vienmÄrÄ«gs sadalÄ«jums).
PÄc noklusÄjuma gaidÄmie rezultÄti tika uzskatÄ«ti par tÄdiem, kas norÄdÄ«ti dokumentÄcijÄ.
Metodoloģija
MÄs uzrakstÄ«jÄm nelielu lietojumprogrammu, kas Ä£enerÄja nejauÅ”u skaitļu secÄ«bas, izmantojot katru no piedÄvÄtajÄm metodÄm, un saglabÄja rezultÄtus turpmÄkai apstrÄdei.
Katras secības garums ir 100 000 skaitļu.
NejauŔo skaitļu diapazons ir [0, 100).
Dati tika savÄkti no vairÄkÄm mÄrÄ·a platformÄm:
- Windows
ā Unity v2018.3.14f1, redaktora režīms, mono, .NET standarts 2.0 - macOS
ā Unity v2018.3.14f1, redaktora režīms, mono, .NET standarts 2.0
ā Unity v5.6.4p4, redaktora režīms, mono, .NET standarts 2.0 - android
ā Unity v2018.3.14f1, bÅ«vÄjums katrai ierÄ«cei, Mono, .NET standarts 2.0 - iOS
ā Unity v2018.3.14f1, bÅ«vÄjums katrai ierÄ«cei, il2cpp, .NET standarts 2.0
IevieŔana
Mums ir vairÄki dažÄdi veidi, kÄ Ä£enerÄt nejauÅ”us skaitļus. Katram no tiem mÄs uzrakstÄ«sim atseviŔķu iesaiÅojuma klasi, kurÄ bÅ«tu jÄnorÄda:
- IespÄja iestatÄ«t vÄrtÄ«bu diapazonu [min/max). Tiks iestatÄ«ts caur konstruktoru.
- MF atgrieÅ”anas metode. KÄ veidu izvÄlÄsimies pludiÅu, jo tas ir vispÄrÄ«gÄks.
- RezultÄtu atzÄ«mÄÅ”anas Ä£enerÄÅ”anas metodes nosaukums. ÄrtÄ«bas labad mÄs kÄ vÄrtÄ«bu atgriezÄ«sim pilnu klases nosaukumu + MF Ä£enerÄÅ”anai izmantotÄs metodes nosaukumu.
Vispirms deklarÄsim abstrakciju, ko attÄlo IRandomGenerator interfeiss:
namespace RandomDistribution
{
public interface IRandomGenerator
{
string Name { get; }
float Generate();
}
}
System.Random.Next() ievieŔana
Å Ä« metode ļauj iestatÄ«t vÄrtÄ«bu diapazonu, taÄu tÄ atgriež veselus skaitļus, taÄu ir nepiecieÅ”ami pludiÅi. JÅ«s varat vienkÄrÅ”i interpretÄt veselu skaitli kÄ pludiÅu vai arÄ« paplaÅ”inÄt vÄrtÄ«bu diapazonu par vairÄkÄm kÄrtÄm, kompensÄjot tos ar katru vidÄjÄ diapazona paaudzi. RezultÄts bÅ«s kaut kas lÄ«dzÄ«gs fiksÄtam punktam ar noteiktu precizitÄtes secÄ«bu. MÄs izmantosim Å”o opciju, jo tÄ ir tuvÄk reÄlajai peldoÅ”ajai vÄrtÄ«bai.
using System;
namespace RandomDistribution
{
public class SystemIntegerRandomGenerator : IRandomGenerator
{
private const int DefaultFactor = 100000;
private readonly Random _generator = new Random();
private readonly int _min;
private readonly int _max;
private readonly int _factor;
public string Name => "System.Random.Next()";
public SystemIntegerRandomGenerator(float min, float max, int factor = DefaultFactor)
{
_min = (int)min * factor;
_max = (int)max * factor;
_factor = factor;
}
public float Generate() => (float)_generator.Next(_min, _max) / _factor;
}
}
System.Random.NextDouble() ievieŔana
Å eit fiksÄts vÄrtÄ«bu diapazons [0; 1). Lai to projicÄtu uz konstruktorÄ norÄdÄ«to, mÄs izmantojam vienkÄrÅ”u aritmÄtiku: X * (max ā min) + min.
using System;
namespace RandomDistribution
{
public class SystemDoubleRandomGenerator : IRandomGenerator
{
private readonly Random _generator = new Random();
private readonly double _factor;
private readonly float _min;
public string Name => "System.Random.NextDouble()";
public SystemDoubleRandomGenerator(float min, float max)
{
_factor = max - min;
_min = min;
}
public float Generate() => (float)(_generator.NextDouble() * _factor) + _min;
}
}
UnityEngine.Random.Range() ievieŔana
Å Ä« UnityEngine.Random statiskÄs klases metode ļauj iestatÄ«t vÄrtÄ«bu diapazonu un atgriež pludiÅa veidu. Jums nav jÄveic nekÄdas papildu transformÄcijas.
using UnityEngine;
namespace RandomDistribution
{
public class UnityRandomRangeGenerator : IRandomGenerator
{
private readonly float _min;
private readonly float _max;
public string Name => "UnityEngine.Random.Range()";
public UnityRandomRangeGenerator(float min, float max)
{
_min = min;
_max = max;
}
public float Generate() => Random.Range(_min, _max);
}
}
UnityEngine.Random.value ievieŔana
StatiskÄs klases UnityEngine vÄrtÄ«bas rekvizÄ«ts.Random atgriež peldoÅ”o tipu no fiksÄta vÄrtÄ«bu diapazona [0; 1). ProjicÄsim to noteiktÄ diapazonÄ tÄdÄ paÅ”Ä veidÄ kÄ ievieÅ”ot System.Random.NextDouble().
using UnityEngine;
namespace RandomDistribution
{
public class UnityRandomValueGenerator : IRandomGenerator
{
private readonly float _factor;
private readonly float _min;
public string Name => "UnityEngine.Random.value";
public UnityRandomValueGenerator(float min, float max)
{
_factor = max - min;
_min = min;
}
public float Generate() => (float)(Random.value * _factor) + _min;
}
}
Unity.Mathematics.Random.NextFloat() ievieŔana
Klases Unity.Mathematics.Random metode NextFloat() atgriež peldoÅ”Ä komata tipa peldoÅ”o punktu un ļauj norÄdÄ«t vÄrtÄ«bu diapazonu. VienÄ«gÄ nianse ir tÄ, ka katrs Unity.Mathematics.Random gadÄ«jums bÅ«s jÄinicializÄ ar kÄdu sÄklu - tÄdÄ veidÄ mÄs izvairÄ«simies no atkÄrtotu secÄ«bu Ä£enerÄÅ”anas.
using Unity.Mathematics;
namespace RandomDistribution
{
public class UnityMathematicsRandomValueGenerator : IRandomGenerator
{
private Random _generator;
private readonly float _min;
private readonly float _max;
public string Name => "Unity.Mathematics.Random.NextFloat()";
public UnityMathematicsRandomValueGenerator(float min, float max)
{
_min = min;
_max = max;
_generator = new Random();
_generator.InitState(unchecked((uint)System.DateTime.Now.Ticks));
}
public float Generate() => _generator.NextFloat(_min, _max);
}
}
MainController ievieŔana
Ir gatavas vairÄkas IRandomGenerator implementÄcijas. PÄc tam jums ir jÄÄ£enerÄ secÄ«bas un jÄsaglabÄ iegÅ«tÄ datu kopa apstrÄdei. Lai to izdarÄ«tu, Unity izveidosim ainu un nelielu MainController skriptu, kas veiks visu nepiecieÅ”amo darbu un vienlaikus bÅ«s atbildÄ«gs par mijiedarbÄ«bu ar UI.
IestatÄ«sim datu kopas lielumu un MF vÄrtÄ«bu diapazonu, kÄ arÄ« iegÅ«sim metodi, kas atgriež konfigurÄtu un darbam gatavu Ä£eneratoru masÄ«vu.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
private const int DefaultDatasetSize = 100000;
public float MinValue = 0f;
public float MaxValue = 100f;
...
private IRandomGenerator[] CreateRandomGenerators()
{
return new IRandomGenerator[]
{
new SystemIntegerRandomGenerator(MinValue, MaxValue),
new SystemDoubleRandomGenerator(MinValue, MaxValue),
new UnityRandomRangeGenerator(MinValue, MaxValue),
new UnityRandomValueGenerator(MinValue, MaxValue),
new UnityMathematicsRandomValueGenerator(MinValue, MaxValue)
};
}
...
}
}
Tagad izveidosim datu kopu. Å ajÄ gadÄ«jumÄ datu Ä£enerÄÅ”ana tiks apvienota ar rezultÄtu ierakstÄ«Å”anu teksta straumÄ (csv formÄtÄ). Lai saglabÄtu katra IRandomGenerator vÄrtÄ«bas, tiek pieŔķirta atseviŔķa kolonna, un pirmajÄ rindÄ ir Ä£eneratora nosaukums.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
...
private void GenerateCsvDataSet(TextWriter writer, int dataSetSize, params IRandomGenerator[] generators)
{
const char separator = ',';
int lastIdx = generators.Length - 1;
// write header
for (int j = 0; j <= lastIdx; j++)
{
writer.Write(generators[j].Name);
if (j != lastIdx)
writer.Write(separator);
}
writer.WriteLine();
// write data
for (int i = 0; i <= dataSetSize; i++)
{
for (int j = 0; j <= lastIdx; j++)
{
writer.Write(generators[j].Generate());
if (j != lastIdx)
writer.Write(separator);
}
if (i != dataSetSize)
writer.WriteLine();
}
}
...
}
}
Atliek tikai izsaukt GenerateCsvDataSet metodi un saglabÄt rezultÄtu failÄ vai nekavÄjoties pÄrsÅ«tÄ«t datus pa tÄ«klu no gala ierÄ«ces uz saÅÄmÄju serveri.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
...
public void GenerateCsvDataSet(string path, int dataSetSize, params IRandomGenerator[] generators)
{
using (var writer = File.CreateText(path))
{
GenerateCsvDataSet(writer, dataSetSize, generators);
}
}
public string GenerateCsvDataSet(int dataSetSize, params IRandomGenerator[] generators)
{
using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
{
GenerateCsvDataSet(writer, dataSetSize, generators);
return writer.ToString();
}
}
...
}
}
Projekta avoti atrodas plkst
rezultÄtus
NekÄds brÄ«nums nenotika. ViÅi gaidÄ«ja to, ko viÅi ieguva - visos gadÄ«jumos vienmÄrÄ«gu izplatÄ«Å”anu bez sazvÄrestÄ«bas mÄjiena. Es neredzu jÄgu platformÄm ievietot atseviŔķus grafikus - tie visi parÄda aptuveni vienÄdus rezultÄtus.
RealitÄte ir Å”Äda:
SekvenÄu vizualizÄcija plaknÄ no visÄm piecÄm Ä£enerÄÅ”anas metodÄm:
Un vizualizÄcija 3D formÄtÄ. Es atstÄÅ”u tikai System.Random.Next() rezultÄtu, lai neradÄ«tu identisku saturu.
IevadÄ izstÄstÄ«tais stÄsts par UnityEngine.Random normÄlu sadalÄ«jumu neatkÄrtojÄs: vai nu tas sÄkotnÄji bija kļūdains, vai arÄ« pÄc tam dzinÄjÄ kaut kas ir mainÄ«jies. Bet tagad mÄs esam pÄrliecinÄti.
Avots: www.habr.com