C# numere aleatoare ponderate C# (Programare, C#, Unity3D, Random)

Paski7 a intrebat.

Am nevoie de ajutor cu programarea unui joc.

Deschizi un cufăr și cu o anumită probabilitate găsești un obiect.

Element / Șansă

A / 10%
B / 30%
C / 60%

Random random = new Random();
int x = random.Next(1, 101);

if (x < 11) // Numbers 1..10 ( A -> 10% )
{ 
     do_something1(); d
} 
else if (x < 41) // Numbers 11..40 ( B -> 30 % )
{ 
     do_something2();
}
else if (x < 101) // Numbers 41..100 ( C -> 60 % ) 
{ 
     do_something3();
}

Acest exemplu are cu adevărat sens, în termeni de probabilitate?Aveți o altă soluție?

Vă mulțumesc anticipat!

Comentarii

  • Mie mi se pare o soluție rezonabilă! –  > Por EpicKip.
  • Ce se întâmplă când x == 101 în acest caz? –  > Por Timothy Groote.
  • Aceeași soluție a primit 33 de voturi în sus aici stackoverflow.com/questions/1522208/… așa că ar trebui să fii bine –  > Por Lennart.
  • @TimothyGroote nu este posibil ca al doilea parametru să fie Limita superioară exclusivă a numărului aleatoriu returnat msdn.microsoft.com/en-us/library/2dx6wyd4(v=vs.110).aspx –  > Por fubo.
  • @fubo adevărat, dar nu ar trebui să uitați să actualizați fiecare „valoare magică” din acest exemplu atunci când schimbați limitele distribuției dvs. dacă o faceți, acolo se vor strecura bug-urile. –  > Por Timothy Groote.
4 răspunsuri
pcdev

Sunt de acord cu @Timothy, aș opta pentru o soluție mai ușor de întreținut, în care nu vă bazați pe numere magice pentru a vă împărți probabilitățile. De asemenea, este o preferință personală, dar aș numi, de asemenea, raportul mai degrabă decât procentul, altfel „100” devine un alt număr magic și te limitezi la o probabilitate minimă de 1%. În acest fel, poți împărți 1:10:200 sau cum dorești:

public static readonly int RATIO_CHANCE_A = 10;
public static readonly int RATIO_CHANCE_B = 30;
//                         ...
public static readonly int RATIO_CHANCE_N = 60;

public static readonly int RATIO_TOTAL = RATIO_CHANCE_A
                                       + RATIO_CHANCE_B
                                         // ...
                                       + RATIO_CHANCE_N;

Random random = new Random();
int x = random.Next(0, RATIO_TOTAL);

if ((x -= RATIO_CHANCE_A) < 0) // Test for A
{ 
     do_something1();
} 
else if ((x -= RATIO_CHANCE_B) < 0) // Test for B
{ 
     do_something2();
}
// ... etc
else // No need for final if statement
{ 
     do_somethingN();
}

EDITARE: O soluție mai generalizată

Comentarii

  • Ar trebui să schimbați x < RATIO_CHANCE_B în x < RATIO_CHANCE_A + RATIO_CHANCE_B deoarece cu soluția actuală șansele sunt de 10/20/70 –  > Por Rafiwui.
  • Văd că devine puțin cam încurcat dacă aveți mai mult de 3 căi posibile, în acest caz aș căuta probabil să decrementăm x cu valoarea raportului anterior în fiecare else bloc în loc să adăugați fiecare rată ulterioară în if() clauza –  > Por pcdev.
  • Nici măcar nu aș scădea cu valoarea anterioară, ci cu cea curentă: /*else*/ if ((x -= RATIO_CHANCE_CURRENT) < 0) { do_sth(); } –  > Por Rafiwui.
Timothy Groote

Îmi dau seama că este un pic cam târziu, dar iată un exemplu de a face asta fără consts, fără declarații laborioase if/else și/sau switch ;

public class WeightedChanceParam
{
    public Action Func { get; }
    public double Ratio { get; }

    public WeightedChanceParam(Action func, double ratio)
    {
        Func = func;
        Ratio = ratio;
    }
}

public class WeightedChanceExecutor
{
    public WeightedChanceParam[] Parameters { get; }
    private Random r;

    public double RatioSum
    {
        get { return Parameters.Sum(p => p.Ratio); }
    }

    public WeightedChanceExecutor(params WeightedChanceParam[] parameters)
    {
        Parameters = parameters;
        r = new Random();
    }

    public void Execute()
    {
        double numericValue = r.NextDouble() * RatioSum;

        foreach (var parameter in Parameters)
        {
            numericValue -= parameter.Ratio;

            if (!(numericValue <= 0))
                continue;

            parameter.Func();
            return;
        }

    }
}

exemplu de utilizare :

WeightedChanceExecutor weightedChanceExecutor = new WeightedChanceExecutor(
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("A");
    }, 25), //25% chance (since 25 + 25 + 50 = 100)
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("B");
    }, 50), //50% chance
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("C");
    }, 25) //25% chance
);

//25% chance of writing "A", 25% chance of writing "C", 50% chance of writing "B"        
weightedChanceExecutor.Execute(); 

Comentarii

  • în cazul tău, valorile decente pentru rapoarte ar fi 1, 3 și 6 (sau 10, 30 și 60) –  > Por Timothy Groote.
  • De fapt, m-am gândit și eu la metodele de callback, dar nu mi-am amintit cum se numesc în C#/Unity xD Dar soluția ta are avantajul clar că nu vei uita niciodată să implementezi o metodă pentru una dintre șanse. –  > Por Rafiwui.
  • de asemenea, nu veți (atât de ușor) reconstrui din greșeală generatorul de numere aleatoare, nu veți uita să vă actualizați comparațiile sau să produceți cod imbricate inutil de adânc, nu veți uita să implementați un switch case (sau să implementați unul care nu este necesar). În cazul în care acțiunea dvs. WeightedChanceParam devine prea mare pentru confort, implementați pur și simplu o metodă și treceți-o ca referință, iar codul dvs. legat de „șansă” va rămâne lizibil. –  > Por Timothy Groote.
Rafiwui

Deci, pentru a încheia soluțiile, iată o soluție pentru orice număr de șanse fără o mulțime de instrucțiuni if-else, ci în schimb un switch-case:

int[] chances = { 1, 23, 14, 49, 61 };
int totalRatio = 0;

foreach(int c in chances)
    totalRatio += c;

Random random = new Random();
int x = random.Next(0, totalRatio);

int iteration = 0; // so you know what to do next
foreach(int c in chances)
{
    iteration++;
    if((x -= c) < 0)
        break;
}

switch(iteration)
{
case 1:
case 2:
//...
default:
}

Comentarii

  • înlocuirea if/else cu o declarație switch este ca și cum ai pune ruj pe un porc 😉 –  > Por Timothy Groote.
  • În acest caz, aș spune că este mult mai ușor de identificat acțiuni specifice, deoarece puteți înlocui cu ușurință numerele întregi cu en enum și IMO switch-case este mai ușor de citit decât if-else 😉 –  > Por Rafiwui.
Paski7

Când combin toate răspunsurile voastre, atunci ar trebui să funcționeze și aici, nu?

double number;
Random x = new Random();
number = x.NextDouble();

double RATIO_CHANCE_A = 0.10;
double RATIO_CHANCE_B = 0.30;
double RATIO_CHANCE_C = 0.60;
double RATIO_TOTAL = RATIO_CHANCE_A + RATIO_CHANCE_B + RATIO_CHANCE_C;


if ( number < RATIO_CHANCE_A ) // A -> 10%
{
do_something1();
}
else if ( number < RATIO_CHANCE_B + RATIO_CHANCE_A ) // B -> 30%
{
do_something2();
}
else if ( number < RATIO_TOTAL ) // C -> 60%
{
do_something3();
}

Comentarii

  • Aceeași problemă aici cu RATIO_CHANCE_B. Asigurați-vă că este A + B altfel ai doar o șansă egală cu diferența dintre A și B pentru B –  > Por Rafiwui.
  • Două probleme minore cu acest lucru față de răspunsul meu, în primul rând este că ar trebui să folosiți din punct de vedere tehnic < în loc de <= (number nu va fi niciodată egal cu 1,0, de exemplu), a doua este că atunci când mențineți aceste rapoarte trebuie să vă asigurați că acestea se adună întotdeauna exact 1. În caz contrar, fie probabilitățile dvs. nu vor fi exacte, fie dacă RATIO_TOTAL < 1.0 s-ar putea ca ocazional să constatați că nu se întâmplă nimic din cauza faptului că finalul if declarație finală –  > Por pcdev.