BlessRNG ose kontrola e të rastit për ndershmërinë

BlessRNG ose kontrola e të rastit për ndershmërinë

Në zhvillimin e lojërave shpesh është e nevojshme të lidhet diçka me rastësinë: Unity ka Random-in e vet, ndërsa paralelisht ekziston System.Random. Dikur, gjatë një projekti, më duket ka pasur një përshtypje që të dy mund të punonin ndryshe (ndonëse duhet të kenë shpërndarje të barabartë).

AtĂ«herĂ« nuk u thelluam shumĂ« nĂ« detaje — ishte e mjaftueshme qĂ« kalimi nĂ« System.Random e zgjidhi tĂ« gjitha problemet. Tani kemi vendosur tĂ« shqyrtojmĂ« mĂ« thellĂ« dhe tĂ« kryejmĂ« njĂ« kĂ«rkim tĂ« vogĂ«l: sa ‘tĂ« prirur’ ose tĂ« parashikueshĂ«m janĂ« gjeneratorĂ«t e numrave tĂ« rastit dhe cili duhet zgjedhur. PĂ«r mĂ« tepĂ«r, kam dĂ«gjuar herĂ« pas here mendime tĂ« kundĂ«rta mbi ‘ndershmĂ«rinë’ e tyre — do tĂ« pĂ«rpiqemi tĂ« kuptojmĂ« si rezultatet reale korrespondojnĂ« me ato tĂ« deklaruara.

Një përmbledhje e shkurtër ose çfarë janë në të vërtetë gjeneratorët e numrave të rastit.

Nëse jeni tashmë të njohur me gjeneratorët e numrave të rastit, mund të kaloni menjëherë në seksionin 'Testimi'.

Numrat e rastit (NR) janĂ« njĂ« renditje numrash qĂ« gjenerohen pĂ«rmes njĂ« procesi tĂ« caktuar rastĂ«sor (kaotik), njĂ« burimi entropie. Kjo do tĂ« thotĂ« se Ă«shtĂ« njĂ« renditje, elementet e tĂ« cilĂ«s nuk lidhen me njĂ«ra-tjetrĂ«n me njĂ« ligj matematikor — ato nuk kanĂ« lidhje shkakĂ«sore.

Ajo që krijon NR quhet gjenerator numrash të rastit (GNR). Duke qenë se duket e thjeshtë, por nëse kalojmë nga teoria në praktikë, realizimi i një algoritmi programues për gjenerimin e një renditjeje të tillë nuk është aq e lehtë.

Arsyeja qëndron në mungesën e asaj kaotike në elektronikat moderne. Pa të, numrat e rastit ndalojnë së qenë të rastësishëm, dhe gjeneratori i tyre shndërrohet në një funksion të zakonshëm nga argumente të caktuara. Për një numër profesionistësh në fushën e IT-së, kjo është një problem serioz (p.sh., për kriptografinë), ndërsa për të tjerët ka një zgjidhje të pranueshme.

Duhet tĂ« shkruhet njĂ« algoritĂ«m qĂ« kthen, ndoshta jo numra vĂ«rtet tĂ« rastit, por sa mĂ« tĂ« afĂ«rt me ta — tĂ« ashtuquajturat numra pseudorastĂ«sorĂ« (PNR). Algoritmi nĂ« kĂ«tĂ« rast quhet gjenerator numrash pseudorastĂ«sorĂ« (GPNR).

Ekzistojnë disa variante për krijimin e GPNR, por për të gjithë do të jenë të vlefshme sa vijon:

  1. Nevoja për inicializim të parakohshëm.

    GPNR është pa një burim entropie, kështu që para përdorimit është e nevojshme t'i jepet një gjendje fillestare. Ajo caktohet në formën e një numri (ose vektori) dhe quhet fara (seed, random seed). Shpesh në rol farë përdoret numri i cikleve të procesorit ose ekuivalenti numëror i kohës sistemore.

  2. Riprodhueshmëria e renditjes.

    GPNR është plotësisht deterministik, kështu që fara e caktuar gjatë inicializimit përkufizon në mënyrë të qartë tërë renditjen e ardhshme të numrave. Kjo do të thotë që një GPNR i caktuar, i inicializuar me të njëjtën farë (në kohë të ndryshme, në programe të ndryshme, në pajisje të ndryshme) do të gjenerojë të njëjtën renditje.

Duhet tĂ« dihet gjithashtu shpĂ«rndarja e probabiliteteve qĂ« caracteriza GPNR — cilat numra do tĂ« gjenerojĂ« dhe me çfarĂ« probabiliteti. MĂ« shpesh, kjo Ă«shtĂ« ose shpĂ«rndarje normale (normal distribution) ose shpĂ«rndarje uniforme (uniform distribution).
BlessRNG ose kontrola e të rastit për ndershmërinë
Shpërndarja normale (majtas) dhe shpërndarja uniforme (djathtas).

Supozoni se kemi një zar të ndershëm me 24 faqe. Nëse e hedhim atë, probabiliteti i nxjerrjes së njërit do të jetë 1/24 (ashtu si probabiliteti i nxjerrjes së ndonjë numri tjetër). Nëse bëjmë shumë hedhje dhe regjistrojmë rezultatet, mund të vërejmë se të gjitha faqet dalin përafërsisht me të njëjtin frekuencë. Në thelb, ky zar mund të konsiderohet si GNR me shpërndarje uniforme.

Dhe nĂ«se hedhim menjĂ«herĂ« 10 tĂ« tillĂ« dhe llogarisim shumĂ«n totale tĂ« pikĂ«ve? A do tĂ« ruhet uniformiteti pĂ«r tĂ«? Jo. MĂ« shpesh, shuma do tĂ« jetĂ« afĂ«rsisht 125 pikĂ«, pra njĂ« vlerĂ« mesatare. Dhe si pasojĂ« — akoma para hedhjes mund tĂ« vlerĂ«sojmĂ« pĂ«rafĂ«rsisht rezultatin e ardhshĂ«m.

Arsyeja Ă«shtĂ« se pĂ«r tĂ« arritur shumĂ«n mesatare tĂ« pikĂ«ve ekzistojnĂ« numri mĂ« i madh i kombinimeve. Sa mĂ« larg nga ajo, aq mĂ« pak kombinime — dhe pĂ«r pasojĂ«, probabiliteti Ă«shtĂ« mĂ« i ulĂ«t. NĂ«se kĂ«to tĂ« dhĂ«na vizualizohen, ato do tĂ« ngjajnĂ« larg nĂ« formĂ«n e njĂ« zile. Prandaj, me njĂ«farĂ« tĂ«rheqjeje, njĂ« sistem i 10 zareve mund tĂ« quhet GNR me shpĂ«rndarje normale.

NjĂ« shembull tjetĂ«r, vetĂ«m nĂ« plan — qitja nĂ« njĂ« objektiv. QitĂ«si do tĂ« jetĂ« GNR qĂ« gjeneron njĂ« çift numrash (x, y), i cili shfaqet nĂ« grafik.
BlessRNG ose kontrola e të rastit për ndershmërinë
Mund t'i e dĂ«gjoni se varianti nĂ« tĂ« majtĂ« Ă«shtĂ« mĂ« i afĂ«rt me jetĂ«n reale — ky Ă«shtĂ« njĂ« Gjenerator Numrash me shpĂ«rndarje normale. Por nĂ«se duhet tĂ« shpĂ«rndani yjet nĂ« njĂ« qiell tĂ« errĂ«t, varianti i djathtĂ«, i fituar me njĂ« Gjenerator Numrash me shpĂ«rndarje uniforme, do tĂ« jetĂ« mĂ« i pĂ«rshtatshĂ«m. NĂ« pĂ«rgjithĂ«si, zgjidhni gjeneratorin nĂ« varĂ«si tĂ« detyrĂ«s sĂ« caktuar.

Tani le të flasim për entropinë e sekuencës së Gjeneratorit Numrash të Rastësishëm. Për shembull, kemi një seksion që fillon kështu:

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, 


Sa të rastësishme duken këto numra në shikim të parë? Le të fillojmë me verifikimin e shpërndarjes.
BlessRNG ose kontrola e të rastit për ndershmërinë
Duket si afërsisht e barabartë, por nëse i lexojmë numrat në çift dhe i interpretojmë si koordinata në një plan, rezultati do të jetë ky:
BlessRNG ose kontrola e të rastit për ndershmërinë
Dalin qartë në pah modelet. Dhe duke qenë se të dhënat në sekuencë janë të renditura në një mënyrë të caktuar (pra, kanë entropi të ulët), mund të krijohet ajo të ashtuquajturat 'mënyra e paragjykimeve'. Të paktën, një gjenerator i tillë numrash të rastësishëm nuk është shumë i përshtatshëm për gjenerimin e koordinatave në një plan.

Një tjetër sekuencë:

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, 


Duket se gjithçka është në rregull madje në plan:
BlessRNG ose kontrola e të rastit për ndershmërinë
Të hedhim një sy në volum (lexojmë në grup nga tri numra):
BlessRNG ose kontrola e të rastit për ndershmërinë
Dhe përsëri dalin modelet. Të ndërtosh një vizualizim në katër dimensione tashmë nuk do të jetë e mundur. Por modelet mund të ekzistojnë edhe në këtë dimension dhe në dimensione më të mëdha.

Në të njëjtën fushë të kriptografisë, ku Gjeneratorët e Numrave të Rastësishëm kanë kërkesa shumë të rrepta, një situatë e tillë është kategoria e papranueshme. Prandaj, për të vlerësuar cilësinë e tyre janë zhvilluar algoritme të veçanta, për të cilat tani nuk do të flasim. Ky është një temë e gjerë dhe shtrihet në një artikull të veçantë.

Testimi

Nëse nuk dimë diçka me siguri, si të punojmë me të? A ia vlen të kalosh rrugën, nëse nuk di se cili sinjal ndricues e lejon atë? Pasojat mund të jenë të ndryshme.

E njĂ«jta gjĂ« vlen pĂ«r tĂ« ashtuquajturin rastĂ«si nĂ« Unity. ËshtĂ« e mirĂ« nĂ«se dokumentacioni zbardh detajet e nevojshme, por historia e pĂ«rmendur nĂ« fillim tĂ« artikullit ndodhi pikĂ«risht pĂ«r shkak tĂ« mungesĂ«s sĂ« konkretikave tĂ« dĂ«shiruara.

Dhe nëse nuk di se si funksionon një instrument, nuk mund ta aplikosh saktë. Në përgjithësi, ka ardhur koha për të verifikuar dhe për të kryer një eksperiment, për t'u siguruar të paktën për shpërndarjen.

Zgjidhja ishte e thjeshtĂ« dhe efektive — tĂ« mbledhim statistika, tĂ« marrim tĂ« dhĂ«na objektive dhe tĂ« shohim rezultatet.

Objekti i hulumtimit

NĂ« Unity ekzistojnĂ« disa mĂ«nyra pĂ«r tĂ« gjeneruar numra tĂ« rastĂ«sishĂ«m — ne testuam pesĂ«.

  1. System.Random.Next(). Gjeneron numra të tërë (integer) në një gamë të dhënë vlerash.
  2. System.Random.NextDouble(). Gjeneron numra me pikë të dyfishtë (double) në gamën nga [0; 1).
  3. UnityEngine.Random.Range(). Gjeneron numra me pikë të vetme (float) në një gamë të dhënë vlerash.
  4. UnityEngine.Random.value. Gjeneron numra me pikë të vetme (float) në gamën nga [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Pjesë e bibliotekës së re Unity.Mathematics. Gjeneron numra me pikë të vetme (float) në një gamë të dhënë vlerash.

Gjithçka në dokumentacion është e shënuar me shpërndarje uniforme, përveç UnityEngine.Random.value (ku shpërndarja nuk është e shënuar, por me analogji me UnityEngine.Random.Range() gjithashtu pritej shpërndarje uniforme) dhe Unity.Mathematics.Random.NextFloat() (ku në thelb është algoritmi xorshift, prandaj sërish duhet të presim shpërndarje uniforme).

Marrë si rezultate të pritura janë ato që janë shënuar në dokumentacion.

Metodika

Ne shkruam një aplikacion të vogël, i cili gjeneronte sekuenca numrash të rastësishëm me çdo një nga metodat e paraqitura dhe ruante rezultatet për përpunim të mëtejshëm.

GjatĂ«sia e çdo sekuence — 100,000 numra.
Gama e vlerave tĂ« numrave tĂ« rastĂ«sishĂ«m — [0, 100).

Të dhënat u mbledhën nga disa platforma target:

  • Windows
    — Unity v2018.3.14f1, MĂ«nyra Editor, Mono, .NET Standard 2.0
  • macOS
    — Unity v2018.3.14f1, MĂ«nyra Editor, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, MĂ«nyra Editor, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, ndĂ«rtim nĂ« pajisje, Mono, .NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1, ndĂ«rtim nĂ« pajisje, il2cpp, .NET Standard 2.0

Realizimi

Ne kemi disa mënyra të ndryshme për të gjeneruar numra të rastësishëm. Për secilën prej tyre do të shkruajmë një klasë të veçantë rreth saj, e cila duhet të ofrojë:

  1. Mundësinë për të caktuar gamën e vlerave [min/max). Kjo do të caktohet përmes konstruktorit.
  2. Një metodë që kthen numrin e rastësishëm. Si tip do të zgjidhim float, si më të përgjithshëm.
  3. Emri i metodës së gjenerimit për etiketimin e rezultateve. Për komoditet, si vlerë do të kthejmë emrin e plotë të klasës + emrin e metodës së përdorur për gjenerimin e numrit të rastësishëm.

Së pari, do të deklarojmë një abstraksion, e cila do të paraqitet si ndërfaqja IRandomGenerator:

namespace RandomDistribution
{
    public interface IRandomGenerator
    {
        string Name { get; }

        float Generate();
    }
}

Implementimi i System.Random.Next()

Ky metod lejon të caktosh një gamë vlerash, por kthen numra të plotë (integer), ndërsa ne kemi nevojë për float. Mund të interpretojmë thjesht integer si float, ose mund të zgjasim gamën e vlerave për disa renditje, duke e kompensuar atë në çdo gjenerim. Kjo do të rezultojë në një gjë të ngjashme me pikët fikse me një saktësi të caktuar. Ne do të përdorim këtë variant, pasi është më afër se sa një vlerë reale float.

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;
    }
}

Implementimi i System.Random.NextDouble()

KĂ«tu kemi njĂ« gamĂ« tĂ« fixuar vlerash [0; 1). PĂ«r ta projektuar atĂ« nĂ« gamĂ«n e caktuar nĂ« konstruktor, pĂ«rdorim njĂ« aritmetikĂ« tĂ« thjeshtĂ«: 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;
    }
}

Implementimi i UnityEngine.Random.Range()

Ky metod i klasës statike UnityEngine.Random lejon të caktosh një gamë vlerash dhe kthen një numër të rastësishëm të tipit float. Nuk do të ketë nevojë për konvertime shtesë.

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);
    }
}

Implementimi i UnityEngine.Random.value

Prona value e klasës statike UnityEngine.Random kthen një numër të rastësishëm të tipit float nga një gamë e fixuar vlerash [0; 1). Ta projektojmë atë në gamën e caktuar në të njëjtën mënyrë si në implementimin e 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;
    }
}

Implementimi i Unity.Mathematics.Random.NextFloat()

Metoda NextFloat() e klasĂ«s Unity.Mathematics.Random kthen njĂ« numĂ«r tĂ« rastĂ«sishĂ«m tĂ« tipit float dhe lejon tĂ« caktosh njĂ« gamĂ« vlerash. NjĂ« hollĂ«si Ă«shtĂ« se çdo instancĂ« e Unity.Mathematics.Random duhet tĂ« inicializohet me njĂ« seed — kĂ«shtu ne do tĂ« shmangim gjenerimin e sekuencave tĂ« pĂ«rsĂ«ritura.

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);
    }
}

Implementimi i MainController

Të disa implementimeve të IRandomGenerator janë gati. Tani duhet të gjenerojmë sekuenca dhe të ruajmë datasetin përkatës për përpunim. Për këtë do të krijojmë një skenë në Unity dhe një skript të vogël MainController, i cili do të bëjë të gjithë punën e nevojshme dhe në të njëjtën kohë do të përgjigjet për ndërveprimin me UI.

Do të caktosh madhësinë e dataset-it dhe gamën e vlerave të numrave të rastësishëm, si dhe do të krijosh një metodë që kthen një varg të gjeneruesve të gatshëm për t'u përdorur.

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)
            };
        }

        ...
    }
}

Tani po formojmë datasetin. Në këtë rast, gjenerimi i të dhënave do të kombinohet me regjistrimin e rezultateve në një stream tekstual (në format csv). Për ruajtjen e vlerave nga çdo IRandomGenerator përcaktohet një kolone e veçantë, ndërsa rreshti i parë përmban emrin e gjeneratorit.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        ...
		
        private void GenerateCsvDataSet(TextWriter writer, int dataSetSize, params IRandomGenerator[] generators)
        {
            const char separator = ',';
            int lastIdx = generators.Length - 1;

            // shkruaj kryefjalën
            for (int j = 0; j <= lastIdx; j++)
            {
                writer.Write(generators[j].Name);
                if (j != lastIdx)
                    writer.Write(separator);
            }
            writer.WriteLine();

            // shkruaj të dhënat
            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();
            }
        }

        ...
    }
}

Duhet të thërrasësh metodën GenerateCsvDataSet dhe të ruash rezultatin në një skedar, ose menjëherë të dërgosh të dhënat në rrjet nga pajisja përfundimtare në marrësin. server.

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();
            }
        }

        ...
    }
}

Burimi i projektit gjendet në GitLab.

Rezultatet

Nuk ndodhi asnjĂ« mrekulli. ÇfarĂ« prisnim, atĂ« morĂ«m — nĂ« tĂ« gjitha rastet shpĂ«rndarje tĂ« barabartĂ« pa ndonjĂ« shenjĂ« tĂ« komplotit. Nuk shoh ndonjĂ« kuptim tĂ« paraqes grafikĂ« tĂ« veçanta pĂ«r platforma — ato tregojnĂ« tĂ« njĂ«jtat rezultate nĂ« pĂ«rmasa tĂ« ngjashme.

Realisht, është kjo:
BlessRNG ose kontrola e të rastit për ndershmërinë

Vizualizimi i sekuencave në hapësirë nga të pesë metodat e gjenerimit:
BlessRNG ose kontrola e të rastit për ndershmërinë

Dhe vizualizimi në 3D. Do të lë vetëm rezultatin e System.Random.Next(), për të mos krijuar shumë përmbajtje identike.
BlessRNG ose kontrola e të rastit për ndershmërinë

Historia që u rrëfye në hyrje rreth shpërndarjes normale të UnityEngine.Random nuk u përsërit: ose ajo ishte gabim që në fillim, ose diçka ka ndryshuar që atëherë në motor. Por tani jemi të sigurt.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster