Jak mogę stworzyć „losowy” generator, który jest uprzedzony przez wcześniejsze zdarzenia?

37

Chcę wdrożyć system oparty na szansie, który jest uprzedzony przez wcześniejsze zdarzenie.

Tło: Kilka lat temu pamiętam aktualizację World of Warcraft, która ogłosiła, że ​​wprowadzili nowy kalkulator szans, który przeciwdziałałby kolczastym łańcuchom wydarzeń. (np. zadawanie krytycznych ciosów lub unikanie kilku razy z rzędu). Pomysł polegał na tym, że w przypadku unikania trafienia szansa na uniknięcie kolejnego trafienia byłaby zmniejszona, ale działałoby to w obie strony. Nie uchylanie się od trafienia zwiększyłoby szanse na uniknięcie kolejnego trafienia. Główna sztuczka polegała na tym, że podczas kilku prób szansa na unik nadal odpowiadałaby procentowi podanemu graczowi w jego arkuszu statystyk.

Tego rodzaju system bardzo mnie wtedy intrygował, a teraz jestem w potrzebie takiego rozwiązania.

Oto moje kłopoty:

  • Zgaduję, że byłbym w stanie znaleźć zasoby online na temat wdrażania takiego systemu, ale być może brakuje mi odpowiednich słów, aby go znaleźć.
  • Potrzebuję również tego podejścia, aby dopasować system, który nie jest dwumianowy (tj. Dwa wyniki), ale zamiast tego zawiera 4 wzajemnie wykluczające się zdarzenia.

Moje obecne podejście jest podobne do systemu biletów losowych. Kiedy zdarzenie ma miejsce, zmieniam wagi na korzyść wszystkich innych zdarzeń. Mogłoby to zadziałać, gdyby cztery zdarzenia miały być jednakowo prawdopodobne, ale w moim przypadku musi być znacznie bardziej powszechne. Ale ponieważ zdarzenie dominujące zdarza się częściej, przesuwa on ciężary innych o wiele wyżej niż zamierzano i wydaje mi się, że nie mogę znaleźć liczb dla przesunięć wagi, które są potrzebne, aby utrzymać średnią liczbę biletów wokół początkowych wartości, które były zdarzeniem dany.

Doceniono by kilka wskazówek kierunkowych lub wyraźny przykład.

Sonaten
źródło
4
Jeśli potrzebujesz wysoce dopracowanej lub wyrafinowanej odpowiedzi, możesz mieć więcej szczęścia pytając w Mathematics.SE. Matematycy z łatwością odpowiadają na skomplikowane pytania dotyczące prawdopodobieństwa. math.stackexchange.com
Kevin - Przywróć Monikę
1
PRNG
Jon
6
Alternatywą dla strony matematyki, na której bardziej prawdopodobne jest zrozumienie odpowiedzi, to Programmers.SE . Projektowanie algorytmów nie jest szczególnie tematyczne na temat matematyki i prawdopodobnie będziesz musiał wymyślić wstępny projekt, aby uzyskać użyteczne dane wejściowe.
Lilienthal
1
Zgadzam się z Kevinem i Lilienthalem, że możesz tam uzyskać lepszą odpowiedź, ale czytając odpowiedź mklingen, zdałem sobie sprawę, że to, co tu opisano, może być modelowane jako łańcuch Markowa i może to być przydatne narzędzie dla twórców gier. Spróbuję to później napisać bardziej szczegółowo.
nwellcome
1
Ponieważ sprawdzam liczby niektórych odpowiedzi tutaj, stwierdzam, że istnieje wiele różnych ograniczeń i że rozwiązanie, które rozwiązuje wszystkie z nich, może być dla mnie bardziej złożone niż to, czego potrzebujesz. Więcej szczegółów na temat twojego przypadku użycia może pomóc zawęzić wybór najlepszych opcji. Na przykład, czy prawdopodobieństwa twoich wydarzeń są dość podobne (np. 5 różnych wyników z 20% szansą na każde), czy bardzo różne (np. 10% brak 80% trafienia 10% krytyczny)? Czy chcesz zminimalizować przebiegi (np. 3 spóźnienia z rzędu) lub kępki / oczekiwania (np. 3 spóźnienia z 8 prób lub 20 prób, zanim dostanę krytyczny)?
DMGregory

Odpowiedzi:

19

Zasadniczo pytasz o „pół-losowy” generator zdarzeń, który generuje zdarzenia o następujących właściwościach:

  1. Średnie tempo wystąpienia każdego zdarzenia jest określone z góry.

  2. To samo zdarzenie jest mniej prawdopodobne, że wystąpi dwa razy z rzędu, niż byłoby przypadkowe.

  3. Wydarzenia nie są w pełni przewidywalne.

Jednym ze sposobów na to jest najpierw zaimplementowanie generatora nieprzypadkowych zdarzeń, który spełnia cele 1 i 2, a następnie dodanie losowości, aby osiągnąć cel 3.


W przypadku generatora zdarzeń nieprzypadkowych możemy zastosować prosty algorytm ditheringu . W szczególności niech p 1 , p 2 , ..., p n będą względnymi prawdopodobieństwami zdarzeń 1 do n , a niech s = p 1 + p 2 + ... + p n będzie sumą wag. Następnie możemy wygenerować nieprzypadkową, maksymalnie równorzędną sekwencję zdarzeń, stosując następujący algorytm:

  1. Początkowo niech e 1 = e 2 = ... = e n = 0.

  2. Aby wygenerować zdarzenie, zwiększ każde e i o p i wyślij zdarzenie k, dla którego e k jest największe (zerwanie powiązań w dowolny sposób).

  3. Decrement e k o s , a następnie powtórz czynności od punktu 2.

Na przykład, biorąc pod uwagę trzy zdarzenia A, B i C, przy p A = 5, p B = 4 i p C = 1, ten algorytm generuje coś w rodzaju następującej sekwencji wyników:

A B A B C A B A B A A B A B C A B A B A A B A B C A B A B A

Zauważ, że ta sekwencja 30 zdarzeń zawiera dokładnie 15 As, 12 Bs i 3 Cs. Nie do końca optymalnie się rozprowadza - jest kilka przypadków dwóch jak z rzędu, których można było uniknąć - ale się zbliża.


Teraz, aby dodać losowość do tej sekwencji, masz kilka (niekoniecznie wykluczających się) opcji:

  • Możesz śledzić porady Philippa i utrzymać „talię” z N nadchodzących wydarzeniach, na pewnym odpowiednim rozmiarze numerem N . Za każdym razem, gdy musisz wygenerować zdarzenie, wybierasz losowe zdarzenie z talii, a następnie zamieniasz je na wyjście następnego zdarzenia za pomocą algorytmu ditheringu powyżej.

    Zastosowanie tego do powyższego przykładu, przy N = 3, daje np .:

    A B A B C A B B A B A B C A A A A B B A B A C A B A B A B A

    podczas gdy N = 10 daje bardziej losowy wygląd:

    A A B A C A A B B B A A A A A A C B A B A A B A C A C B B B

    Zwróć uwagę, że wspólne zdarzenia A i B kończą się znacznie większą liczbą przebiegów z powodu przetasowania, podczas gdy rzadkie zdarzenia C są wciąż dość dobrze rozłożone.

  • Możesz wprowadzić losowość bezpośrednio do algorytmu ditheringu. Na przykład, zamiast zwiększając e I o p ı w etapie 2, można ją zwiększyć p ı x losowej (0, 2), w której losowy ( , b ) jest równomiernie rozproszony liczby losowej o wartości pomiędzy i b ; dałoby to wynik podobny do następującego:

    A B B C A B A A B A A B A B A A B A A A B C A B A B A C A B

    lub możesz zwiększyć e i o p i + losowo (- c , c ), co dałoby (dla c = 0,1 × s ):

    B A A B C A B A B A B A B A C A B A B A B A A B C A B A B A

    lub, dla c = 0,5 × s :

    B A B A B A C A B A B A A C B C A A B C B A B B A B A B C A

    Zauważ, że schemat addytywny ma znacznie silniejszy efekt losowy dla rzadkich zdarzeń C niż dla wspólnych zdarzeń A i B, w porównaniu do multiplikatywnego; to może, ale nie musi być pożądane. Oczywiście, można również korzystać z niektórych kombinacji tych systemów, lub jakiegokolwiek innego dostosowywania do przyrostów, jak długo zachowuje właściwość, że średni przyrost e ja równa P I .

  • Alternatywnie można zakłócać wyjście algorytmu ditheringu, czasami zastępując wybrane zdarzenie k losowym (wybranym zgodnie z surowymi wagami p i ). Tak długo, jak użyjesz tego samego k w kroku 3, jak wypisujesz w kroku 2, proces ditheringu będzie miał tendencję do wyrównywania przypadkowych fluktuacji.

    Na przykład, oto przykładowy wynik, z 10% szansą na losowe wybranie każdego zdarzenia:

    B A C A B A B A C B A A B B A B A B A B C B A B A B C A B A

    a oto przykład z 50% szansą, że każde wyjście będzie losowe:

    C B A B A C A B B B A A B A A A A A B B A C C A B B A B B C

    Można również rozważyć karmienie mieszankę czysto losowych i wygładzony wydarzeń na pokładzie / mieszania basenie, jak opisano powyżej, lub może losowanie algorytm ditheringu wybierając k losowo, jako ważone przez e i s (leczenia negatywnych ciężarów jako zero).

Ps. Oto kilka całkowicie losowych sekwencji zdarzeń, z tymi samymi średnimi wskaźnikami, dla porównania:

A C A A C A B B A A A A B B C B A B B A B A B A A A A A A A
B C B A B C B A A B C A B A B C B A B A A A A B B B B B B B
C A A B A A B B C B B B A B A B A A B A A B A B A C A A B A

Styczna: Ponieważ w komentarzach pojawiła się debata na temat tego, czy w przypadku rozwiązań pokładowych konieczne jest umożliwienie opróżnienia pokładu przed ponownym napełnieniem, postanowiłem dokonać graficznego porównania kilku strategii napełniania pokładu:

Wątek
Wykres kilku strategii generowania pół losowych rzutów monetą (przy średnim stosunku główek do reszek wynoszącym 50:50). Oś pozioma to liczba przewrotów, oś pionowa to skumulowana odległość od oczekiwanego stosunku, mierzona jako (głowice - ogony) / 2 = głowice - przewroty / 2.

Czerwone i zielone linie na wykresie pokazują dwa niepoparte na pokładzie algorytmy do porównania:

  • Czerwona linia, deterministyczne dithering : wyniki parzyste są zawsze główkami, wyniki nieparzyste zawsze są ogonami.
  • Zielona linia, niezależne losowe rzuty : każdy wynik jest wybierany niezależnie losowo, z 50% szansą na głowy i 50% szansą na reszkę.

Pozostałe trzy linie (niebieski, fioletowy i cyjan) pokazują wyniki trzech strategii opartych na talii, z których każda została wdrożona przy użyciu talii 40 kart, która początkowo jest wypełniona 20 kartami „głów” i 20 kartami „ogonów”:

  • Niebieska linia, wypełnij, gdy jest pusta : Karty losowane są do momentu, aż talia będzie pusta, a następnie do talii zostaną napełnione 20 kart „głów” i 20 kart „ogonów”.
  • Purpurowa linia, wypełnij, gdy połowa będzie pusta : Karty losowane są do momentu, gdy w talii pozostanie 20 kart; następnie talię uzupełnia się 10 kartami „głów” i 10 kartami „ogonami”.
  • Cyjan-linia, wypełniaj w sposób ciągły : karty losowane są losowo; losowanie parzyste jest natychmiast zastępowane kartą „głów”, a losowanie nieparzyste kartą „reszka”.

Oczywiście powyższy wykres jest tylko pojedynczą realizacją losowego procesu, ale jest dość reprezentatywny. W szczególności widać, że wszystkie procesy pokładowe mają ograniczone odchylenie i pozostają dość blisko czerwonej (deterministycznej) linii, podczas gdy czysto losowa zielona linia ostatecznie odchodzi.

(W rzeczywistości odchylenie linii niebieskiej, purpurowej i niebieskiej od zera jest ściśle ograniczone rozmiarem talii: niebieska linia nigdy nie może dryfować więcej niż 10 kroków od zera, fioletowa linia może uzyskać tylko 15 kroków od zera , a linia cyjanowa może dryfować co najwyżej o 20 kroków od zera. Oczywiście w praktyce żadna z linii, która faktycznie osiąga swój limit, jest bardzo mało prawdopodobna, ponieważ istnieje silna tendencja do powrotu bliżej zera, jeśli wędrują zbyt daleko poza.)

Na pierwszy rzut oka nie ma oczywistej różnicy między różnymi strategiami opartymi na talii (chociaż średnio niebieska linia pozostaje nieco bliżej czerwonej linii, a linia cyjanowa pozostaje nieco dalej), ale dokładniejsze sprawdzenie niebieskiej linii ujawnia wyraźny deterministyczny wzór: co 40 rysunków (oznaczonych przerywanymi szarymi pionowymi liniami) niebieska linia dokładnie spotyka czerwoną linię na zero. Linie fioletowe i cyjanowe nie są tak ściśle ograniczone i mogą trzymać się zera w dowolnym momencie.

We wszystkich strategiach opartych na talii ważną cechą, która ogranicza ich warianty, jest fakt, że podczas losowania kart z talii, determinacja jest uzupełniana . Gdyby karty użyte do uzupełnienia talii były wybierane losowo, wszystkie strategie oparte na talii byłyby nie do odróżnienia od czystego losowego wyboru (zielona linia).

Ilmari Karonen
źródło
Bardzo szczegółowa odpowiedź. Dodawanie losowych czynników do algorytmu ditheringu wydaje się proste. :)
Sonaten
Postanowiłem pójść z twoją odpowiedzią. :) Ale polecam umieszczenie dodatków przeglądu metod na górze. W oparciu o twoją odpowiedź zamierzam wypróbować zarówno rozwiązanie „czerwony”, jak i „fioletowy”.
Sonaten,
53

Nie rzucaj kośćmi, rozdawaj karty.

Weź wszystkie możliwe wyniki RNG, umieść je na liście, losowo potasuj i zwróć wyniki w losowej kolejności. Gdy znajdziesz się na końcu listy, powtórz.

Wyniki będą nadal równomiernie rozmieszczone, ale poszczególne wyniki nie będą się powtarzać, chyba że ostatnia z listy będzie również pierwszą z następnych.

Jeśli jest to zbyt przewidywalne dla twojego gustu, możesz użyć listy, która jest nrazy liczba możliwych wyników i umieścić każdy możliwy wynik nrazy przed tasowaniem. Lub możesz przetasować listę, zanim zostanie ona całkowicie iterowana.

Philipp
źródło
1
wyszukaj „torbę
losową
3
Tak wiele gier Tetris unika zbyt długo głodujących kluczowych elementów. Ważne jest, aby opróżnić torbę / talię, jak sugeruje Philipp, przed włożeniem nowych kart, jeśli chcesz kontrolować zdarzenia w określonym przedziale czasowym. Ponownie wkładając karty na bieżąco (lub ponownie dostosowując wagi), możesz zniekształcić rozkład prawdopodobieństwa na sposoby trudne do obliczenia i łatwe do pomyłki.
DMGregory
2
@DMGregory: Właściwie w porządku jest dodawanie nowych kart przed opróżnieniem talii (i w rzeczywistości zaleciłbym to, aby wyniki były bardziej naturalne i trudniejsze do przewidzenia). Ważną rzeczą jest, aby upewnić się, że (średnia) frakcja nowych kart tasuje pod pokładem równa żądanej frakcji, który chcesz zwrócić się o niego.
Ilmari Karonen,
4
Illmari Karonen: po wymianie przedmiotów możesz stracić korzyści płynące z tasowania w postaci ograniczania serii identycznych wyników lub długich przerw między poszczególnymi wynikami. Jeśli wskaźnik zastąpienia jest równy docelowemu rozkładowi prawdopodobieństwa, jesteś teraz na tej samej pozycji, co generowanie każdego wyniku niezależnie losowo. Jeśli nie jest równy docelowemu rozkładowi prawdopodobieństwa, możesz wypaczyć efektywne prawdopodobieństwa na sposoby, które są trudne do przewidzenia i odpowiednio wyważyć - pytający opisuje problem z dokładnie tym problemem.
DMGregory
2
Uzgodniony z @DMGregory. Tasując nowe karty, unieważniasz sam system. System rozdawania kart jest specjalnie dopasowany do pożądanego rezultatu. Na przykład, gdy usuniesz królową (na przykład, aby użyć tradycyjnych kart) z talii, prawdopodobieństwo losowania królowej zmniejsza się, a prawdopodobieństwo losowania karty innej niż królowa wzrasta. Jest to system samoregulujący, jeśli chcesz.
Volte
17

Możesz spróbować losowego wykresu Markowa . Rozważ każde zdarzenie, które może wystąpić, jako węzeł na wykresie. Z każdego wydarzenia utwórz link do siebie, które może nastąpić po nim. Każde z tych łączy jest ważone przez coś zwanego prawdopodobieństwem przejścia . Następnie wykonujesz losowy spacer po wykresie zgodnie z modelem przejścia.

Na przykład możesz mieć wykres przedstawiający wynik ataku (trafienie krytyczne, unik itp.). Zainicjuj węzeł początkowy na jeden wybrany losowo, biorąc pod uwagę statystyki gracza (po prostu „rzuć kostką”). Następnie, przy następnym ataku, zdecyduj, co stanie się dalej, biorąc pod uwagę model przejściowy.

Należy zadbać o to, aby zdecydować, jak ważić przejścia. Po pierwsze, wszystkie przejścia wychodzące z węzła muszą się sumować z prawdopodobieństwem 1. Jedną prostą rzeczą, którą możesz zrobić, jest przejście z każdego węzła do każdego innego węzła, z wagami równoważnymi prawdopodobieństwu wystąpienia tych zdarzeń a priori , biorąc pod uwagę, że bieżące zdarzenie nie może się powtórzyć.

Na przykład, jeśli masz trzy zdarzenia:

  Critical, P = 0.1
  Hit,      P = 0.3
  Miss,     P = 0.6

Możesz skonfigurować model przejścia tak, aby trafienie krytyczne nie powtórzyło się po prostu poprzez równomierne rozłożenie jego masy prawdopodobieństwa na inne zdarzenia:

  Critical -> Critical,   P = 0.0
  Critical -> Hit,        P = 0.35
  Critical -> Miss,       P = 0.65

EDYCJA: Jak mówią komentarze poniżej, ten model nie jest wystarczająco skomplikowany, aby uzyskać pożądane zachowanie. Zamiast tego może być konieczne dodanie wielu dodatkowych stanów!

Mklingen
źródło
1
Proponowany schemat przeważania nie zachowuje pożądanych prawdopodobieństw każdego stanu. Wykonując test empiryczny z tymi liczbami, przeoczenia zdarzają się w około 41% przypadków, a krytyczne w około 25%, daleko od wartości wejściowych. Przejście do pozostałych stanów proporcjonalnie do ich prawdopodobieństw (np. Miss ma 25% szansy na trafienie krytyczne i 75% szansę na trafienie) robi się nieco lepiej, z 44% szansą trafienia i 17% trafieniem krytycznym, ale nadal jest nie odzwierciedla pożądanych prawdopodobieństw na wejściu.
DMGregory
Zapomniałem zasady Bayesa :( Ponownie przeliczy ponownie później. Zachowanie wcześniejszego rozkładu prawdopodobieństwa może być niemożliwe, ponieważ model przejściowy w obecnej postaci pomija możliwe sekwencje, takie jak CCHM lub CHHM lub bardzo prawdopodobne MMHM itp.
mklingen
Ograniczenie „brak powtórzeń” może wiązać tutaj ręce w odniesieniu do ekstremalnie wysokich i niskich ciężarów. Jeśli chcesz 1 na 10 prób być krytycznych, jedynym sposobem na spełnienie tej metody jest naprzemiennie 5 chybionych i 5 trafień, co zniekształca prawdopodobieństwo trafienia i pominięcia w stosunku do ich średniej. Żadna sekwencja bez kolejnych braków nie może spełnić wymagań wejścia tutaj.
DMGregory
4
@mklingen, zgadzam się z DMGregory, „absolutnie żadnych powtórzeń” nie jest tutaj pożądane. Chcą raczej, aby prawdopodobieństwo wystąpienia długich łańcuchów o tym samym wyniku było mniej prawdopodobne niż przy jednolitym losowym prawdopodobieństwie. Można to zrobić za pomocą łańcucha Markowa (który jest skierowany), który wygląda jak ten . Wykorzystuje wiele stanów do reprezentowania powtarzających się zdarzeń, w których prawdopodobieństwo przejścia z „Trafienia 1” na „Trafienie 2” i „Trafienie 2” na „Trafienie 3+” maleje, a prawdopodobieństwo przejścia z powrotem na „Trafienie 1” i „Kryt. 1 "do góry.
nwellcome
@Nwellcome to świetny pomysł.
mklingen
3

Oto implementacja, którą utworzyłem w C #, która:

  • Aktywuj zdarzenia na podstawie prawdopodobieństwa
  • Dostosuj te prawdopodobieństwa, aby zmniejszyć szanse na powtarzające się zdarzenia
  • Nie oddalaj się zbytnio od pierwotnych prawdopodobieństw

Dodałem kilka komentarzy, abyś mógł zobaczyć, co robię.

    int percentageEvent1 = 15; //These are the starter values. So given a scenario, the
    int percentageEvent2 = 40; //player would have around a 15 percent chance of event
    int percentageEvent3 = 10; //one occuring, a 40 percent chance of event two occuring
    int percentageEvent4 = 35; //10 percent for event three, and 35 percent for event four.

    private void ResetValues()
    {
        percentageEvent1 = 15;
        percentageEvent2 = 40;
        percentageEvent3 = 10;
        percentageEvent4 = 35;
    }

    int resetCount = 0; //Reset the probabilities every so often so that they don't stray too far.

    int variability = 1; //This influences how much the chance of an event will increase or decrease
                           //based off of past events.

    Random RandomNumberGenerator = new Random();

    private void Activate() //When this is called, an "Event" will be activated based off of current probability.
    {
        int[] percent = new int[100];
        for (int i = 0; i < 100; i++) //Generate an array of 100 items, and select a random event from it.
        {
            if (i < percentageEvent1)
            {
                percent[i] = 1; //Event 1
            }
            else if (i < percentageEvent1 + percentageEvent2)
            {
                percent[i] = 2; //Event 2
            }
            else if (i < percentageEvent1 + percentageEvent2 + percentageEvent3)
            {
                percent[i] = 3; //Event 3
            }
            else
            {
                percent[i] = 4; //Event 4
            }
        }
        int SelectEvent = percent[RandomNumberGenerator.Next(0, 100)]; //Select a random event based on current probability.

        if (SelectEvent == 1)
        {
            if (!(percentageEvent1 - (3 * variability) < 1)) //Make sure that no matter what, probability for a certain event
            {                                                //does not go below one percent.
                percentageEvent1 -= 3 * variability;
                percentageEvent2 += variability;
                percentageEvent3 += variability;
                percentageEvent4 += variability;
            }
        }
        else if (SelectEvent == 2)
        {
            if (!(percentageEvent2 - (3 * variability) < 1))
            {
                percentageEvent2 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent3 += variability;
                percentageEvent4 += variability;
            }
        }
        else if (SelectEvent == 3)
        {
            if (!(percentageEvent3 - (3 * variability) < 1))
            {
                percentageEvent3 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent2 += variability;
                percentageEvent4 += variability;
            }
        }
        else
        {
            if (!(percentageEvent4 - (3 * variability) < 1))
            {
                percentageEvent4 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent2 += variability;
                percentageEvent3 += variability;
            }
        }

        resetCount++;
        if (resetCount == 10)
        {
            resetCount = 0;
            ResetValues();
        }

        RunEvent(SelectEvent); //Run the event that was selected.
    }

Mam nadzieję, że to pomoże, proszę sugerować ulepszenia tego kodu w komentarzach, dzięki!

Superdog
źródło
1
Ten schemat ponownego ważenia prowadzi do tego, że wydarzenia są możliwe do uzyskania. Okresowe resetowanie ciężarów jest tak naprawdę tylko opaską, która ogranicza to, jak źle się robi, a jednocześnie zapewnia, że ​​1 na 10 rzutów nie zyskuje w ogóle na zmianie wagi. Uwaga: jeden algorytm: marnujesz dużo pracy, wypełniając tabelę 100 wpisów, aby dokonać losowego wyboru. Zamiast tego możesz wygenerować losowy rzut, a następnie iterować swoje 4 wyniki, sumując ich prawdopodobieństwa w miarę upływu czasu. Gdy tylko rzut jest mniejszy niż suma, masz swój wynik. Nie wymaga wypełniania tabel.
DMGregory
3

Pozwól mi trochę uogólnić odpowiedź mklingen . Zasadniczo chcesz wdrożyć błąd Hazardzisty , ale przedstawię tutaj bardziej ogólną metodę:

Powiedz, że są nmożliwe zdarzenia z prawdopodobieństwem p_1, p_2, ..., p_n. Kiedy zdarzenie się iwydarzy, jego prawdopodobieństwo przeskaluje się ze współczynnikiem 0≤a_i≤1/p_i(to drugie jest ważne, w przeciwnym razie prawdopodobieństwo będzie większe niż jedno, a pozostałe zdarzenia muszą mieć ujemne prawdopodobieństwa , co w zasadzie oznacza „ anty ” zdarzenia. Lub coś takiego) typowo a_i<1. Możesz na przykład wybrać a_i=p_i, co oznacza, że ​​prawdopodobieństwo wystąpienia zdarzenia po raz drugi jest pierwotnym prawdopodobieństwem, że wydarzenie nastąpi dokładnie dwa razy z rzędu, np. Drugie rzut monetą miałby prawdopodobieństwo 1/4 zamiast 1/2. Z drugiej strony możesz też mieć coś a_i>1, co oznaczałoby wywołanie „udaru szczęścia / nieszczęścia”.

Wszystkie pozostałe zdarzenia powinny być jednakowo prawdopodobne względem siebie, tzn. Wszystkie muszą zostać przeskalowane o ten sam współczynnik, b_itak aby suma wszystkich prawdopodobieństw była równa jeden, tj.

1 = a_i*p_i + b_i*(1-p_i)  # Σ_{j≠i) p_j  = 1 - p_i
 b_i = (1 - a_i*p_i) / (1 - p_i).   (1)

Do tej pory takie proste. Ale teraz dodajmy kolejny wymóg: biorąc pod uwagę wszystkie możliwe sekwencje dwóch zdarzeń, wyodrębnione z nich prawdopodobieństwa pojedynczego zdarzenia będą pierwotnymi prawdopodobieństwami.

Pozwolić

        / p_i * b_i * p_j  (ji)
p_ij = <
        \ a_i * (p_i     (j=i)

oznacz prawdopodobieństwo wystąpienia zdarzenia jpo wydarzeniu ii zwróć uwagę, że p_ij≠p_jichyba b_i=b_j (2)(co (1)implikuje a_j = 1 - a_i*p_i + (1-a_i)*p_i/p_j). Tego właśnie wymaga twierdzenie Bayesa, co również implikuje

Σ_j p_ij = p_i * b_i * (1 - p_i) + a_i * (p_i
         = b_i * p_i + (a_i - b_i) * (p_i
         = p_i  # using (1)

tak jak chcesz. Zwróć uwagę, jak to oznacza, że a_inaprawia się wszystkie pozostałe.


Zobaczmy teraz, co się stanie, gdy zastosujemy tę procedurę wiele razy, tj. Dla sekwencji trzech i więcej zdarzeń. Istnieją zasadniczo dwie opcje wyboru ustalonych prawdopodobieństw trzeciego zdarzenia:

a) Zapomnij o pierwszym zdarzeniu i urządzeniu tak, jakby miało miejsce tylko drugie, tj

         / p_ij * a_j * p_j  (j=k)
p_ijk = <
         \ p_ij * b_j * p_l  (jk)

Pamiętaj, że zwykle narusza to Bayesa, ponieważ np. p_jik≠p_ikjW większości przypadków.

b) Użyj prawdopodobieństw p_ij(dla ustalonych i) jako nowych prawdopodobieństw, pi_jz których uzyskasz nowe prawdopodobieństwa, pi_jkaby zdarzenie kmiało miejsce w następnej kolejności. To, czy zmodyfikujesz, ai_jczy nie, zależy od ciebie, ale pamiętaj, że nowe bi_jsą zdecydowanie różne ze względu na zmodyfikowane pi_j. Z drugiej strony wybór ai_jprawdopodobnie jest ograniczony przez wymaganie wszystkich permutacji ijkwystąpienia z tym samym prawdopodobieństwem. Zobaczmy...

         / p_ij * bi_j * pi_k  (jk)
p_ijk = <
         \ (p_ij * ai_j      (j=k)

         / b_i * bi_j * p_i * p_j * pi_k  (ijki)
         | b_i * ai_j * p_i * (p_j      (ij=k)
      = <  a_i * (p_i * bi_i * pi_k     (i=jk)
         | b_i * p_i * bi_j * p_k * pi_i  (i=kj)
         \ a_i * ai_i * (p_i * pi_i     (i=k=j)

i ich cykliczne permutacje, które muszą być równe dla odpowiednich przypadków.

Obawiam się, że moja kontynuacja będzie musiała chwilę poczekać ...

Tobias Kienzler
źródło
Testując to empirycznie, nadal powoduje to zniekształcenie od prawdopodobieństw wejściowych w wielu przebiegach. Jeśli na przykład a_i / p_i = 0,5 (i przy użyciu liczb z odpowiedzi Mklingena) wskaźnik braku wejścia na poziomie 60% staje się wskaźnikiem zaobserwowanym na poziomie 50,1%, a wskaźnik wejścia na poziomie 10% wynosi 13,8%. Możesz to zweryfikować, przenosząc wynikową macierz przejścia na wysoką moc. Wybór współczynników a_i: p_i bliższych 1 powoduje mniejsze zniekształcenie, ale także mniejszą skuteczność w zmniejszaniu przebiegów.
DMGregory
@DMGregory dobra uwaga: nie można po prostu przejąć mocy macierzy przejścia. Później
rozwinę
@DMGregory Zacząłem opisywać cały proces (wariant b)), ale robi się dość nudny i brakuje mi czasu: /
Tobias Kienzler
1

Myślę, że najlepszą opcją jest użycie losowego wyboru przedmiotów. Jest to implementacja dla C # tutaj , ale można je łatwo znaleźć lub wykonane w innych językach.

Pomysł polegałby na zmniejszeniu wagi opcji za każdym razem, gdy jest ona wybierana, i zwiększaniu jej za każdym razem, gdy nie jest wybierana.

Na przykład, jeśli zmniejszysz wagę wybranej opcji o NumOptions-1i zwiększysz wagę każdej innej opcji o 1 (uważając, aby usunąć przedmioty o wadze <0 i przeczytać je, gdy wzrosną powyżej 0) , każda opcja zostanie wybrana w przybliżeniu tyle samo razy w ciągu długiego okresu, ale ostatnio wybrane opcje będą znacznie mniej prawdopodobne.


Problem z korzystaniem z losowego zamawiania, jak sugeruje wiele innych odpowiedzi, polega na tym, że po wybraniu każdej opcji, z wyjątkiem jednej, można z całkowitą pewnością przewidzieć, która opcja zostanie wybrana w następnej kolejności. To niezbyt losowe.

BlueRaja - Danny Pflughoeft
źródło
1

Moja odpowiedź jest nieprawidłowa, mój test był wadliwy.

Zostawiam tę odpowiedź tutaj do dyskusji i komentarzy, które wskazują na wady tego projektu, ale rzeczywisty test był niepoprawny.

To, czego szukasz, to waga ważona: wagi dla czterech możliwych wyników muszą być dalej korygowane (ważone) o poprzednie wyniki, przy jednoczesnym zachowaniu odpowiednich ogólnych wag.

Najłatwiejszym sposobem na osiągnięcie tego jest zmiana wszystkich wag dla każdego rzutu poprzez zmniejszenie ciężaru dla określonej wartości rzutu i zwiększenie innych ciężarów .

Na przykład załóżmy, że masz 4 ciężary: Fumble, Miss, Hit i Crit. Powiedzmy też, że pożądane dla nich ogólne wagi to Fumble = 10%, Miss = 50%, Hit = 30% i Crit = 10%.

Jeśli używasz generatora liczb losowych (RNG) do generowania wartości od 1 do 100, a następnie porównujesz tę wartość z miejscem, w którym mieści się w tym zakresie (1-10 Fumble, 11-60 miss, 61-90 hit, 91-100 kryt. ), generujesz indywidualny rzut.

Jeśli, kiedy wykonasz ten rzut, natychmiast dostosujesz te zakresy na podstawie wartości rzutu, będziesz oceniać przyszłe rzuty, ale musisz także zmniejszyć wagę rzutu o tę samą całkowitą kwotę, o którą zwiększasz pozostałe ciężary. W naszym powyższym przykładzie zmniejszysz zrolowany ciężar o 3 i zwiększysz pozostałe ciężary o 1 każdy.

Jeśli zrobisz to dla każdego rzutu, nadal będziesz mieć szansę na serie, ale zostaną one znacznie zmniejszone, ponieważ dla każdego rzutu zwiększasz szansę, że przyszłe rzuty będą inne niż bieżący rzut. Możesz zwiększyć ten efekt, a tym samym jeszcze bardziej zmniejszyć ryzyko powstawania smug, zwiększając / zmniejszając ciężary o większy współczynnik (np. Zmniejsz prąd o 6 i zwiększ pozostałe o 2).

Uruchomiłem szybką aplikację, aby zweryfikować to podejście, a po 32 000 iteracjach z tymi wagami generuje następujące wykresy. Górny wykres pokazuje natychmiastowe wartości 4 wag dla każdego rzutu, a dolny wykres pokazuje sumę każdego rodzaju wyniku zrolowanego do tego punktu.

Jak widać, wagi wahają się nieznacznie wokół pożądanych wartości, ale ogólne masy pozostają w pożądanych zakresach, a po ustaleniu początkowej różnorodności liczb początkowych wyniki pasują prawie do pożądanych wartości procentowych.

Zauważ, że ten przykład został stworzony przy użyciu klasy .NET System.Random, która tak naprawdę nie jest jedną z lepszych RNG, więc prawdopodobnie możesz uzyskać dokładniejsze wyniki, używając lepszego RNG. Zauważ również, że 32000 to maksymalne wyniki, które mogłem przedstawić za pomocą tego narzędzia, ale moje narzędzie testowe było w stanie wygenerować ponad 500 milionów wyników przy tych samych ogólnych wzorcach.

David C Ellis
źródło
Pamiętaj, że działa to tylko wtedy, gdy twoje + 1s / -3s są zastosowane względem pierwotnych odważników, a nie do ostatnio używanych odważników. (Ciągłe modyfikowanie ciężarów w jednakowy sposób powoduje, że dryfują one w kierunku możliwości wyposażenia). Podczas gdy utrzymuje to prawdopodobieństwo celu na dłuższą metę, w bardzo niewielkim stopniu redukuje przebiegi. Biorąc pod uwagę, że raz spudłowałem, szansa, że ​​przegapię jeszcze dwa razy z rzędu, wynosi 22% przy tym schemacie, w porównaniu z 25% przy niezależnych losowaniach. Zwiększenie przesunięcia ciężaru dla większego efektu (powiedzmy + 3 / -9) powoduje zmniejszenie prawdopodobieństwa w długim okresie.
DMGregory
W rzeczywistości dane przedstawione powyżej stosują wartość + 1 / -3 do najnowszej masy za każdym razem, gdy rolka jest przetwarzana. Jeśli więc spóźnisz się przy początkowej 50% wadze, następna waży 47%, a jeśli znowu spóźnisz, następna waga wyniesie 44% i tak dalej. Redukuje przebiegi (osobną miarą były przebiegi śledzące, znalezione aż o 24% zmniejszenie przebiegów), ale nadal są one nieuniknione, ponieważ ten schemat nadal ma dużą szansę na pozostawienie każdej z 4 wag z niezerowym prawdopodobieństwem ( np. cztery krytyki z rzędu pozostawią wagę krytyczną z zerową szansą na wystąpienie).
David C Ellis
Jeśli taki był twój zamiar, oznacza to, że Twoja implementacja zawiera błąd. Spójrz na wykres - waga fumble odbija się tylko między 7 a 11, bez żadnych wartości poza tym. Przeprowadziłem symulację przy użyciu ciągłej modyfikacji, którą opisujesz, a wykresy są drastycznie różne, z prawdopodobieństwami każdego stanu zbliżającymi się do 25% w ciągu pierwszych stu prób.
DMGregory
Cholera, w rzeczy samej, jak zauważyłeś, był zepsuty. Uderz w tę odpowiedź.
David C Ellis
@DavidCEllis, czy mówisz, że Twoja implementacja była wadliwa, czy sam pomysł jest? Moja intuicja polegała na z grubsza opisanym modelu (zmniejszanie prawdopodobieństwa podczas rysowania, stopniowe przywracanie wszystkich prawdopodobieństw do ich pierwotnych wartości w czasie) i nadal ma to dla mnie sens.
dimo414
0

Możesz zrobić to, co zasadniczo jest filtrem. Śledź n ostatnich wydarzeń. Prawdopodobieństwo to niektóre filtry zastosowane do tych zdarzeń. 0 filtr jest podstawowym prawdopodobieństwem, jeśli 0, to unikniesz, jeśli 1 nie. Załóżmy, że podstawa wynosiła 25%, a filtr zmniejsza się o połowę z każdą iteracją. Twój filtr to:

[.25 .125 .0625 .03125] 

Jeśli chcesz, możesz kontynuować. Ogólne prawdopodobieństwo tego schematu jest nieco wyższe niż podstawowe prawdopodobieństwo 0,25. W rzeczywistości prawdopodobieństwo, biorąc pod uwagę ten sam schemat, jest (nazywam x rzeczywiste prawdopodobieństwo, p jest wejściem prawdopodobieństwa):

x=p+(1-x)*(p/2+p/4+p/8)

Rozwiązywanie dla x, znajdujemy odpowiedź brzmi p(1+1/2+1/4+1/8)/(1+p(1/2+1/4+1/8), czy za naszym konkretnym przypadku x=0.38461538461. Ale tak naprawdę chcesz znaleźć p, biorąc pod uwagę x. To okazuje się trudniejszym problemem. Jeśli założono filtr nieskończony, problemem staje się x+x*p=2*plub p=x/(2-x). Więc zwiększając swój filtr, możesz następnie rozwiązać dla liczby p, która średnio da ci te same wyniki, ale w tempie zależnym od tego, ile sukcesów ostatnio się wydarzyło.

Zasadniczo za pomocą poprzednich wartości określa się próg akceptacji w tej rundzie i przyjmuje wartość losową. Następnie wygeneruj następną losową wartość dla danego filtra.

PearsonArtPhoto
źródło
-1

Tak jak sam się zaproponowałeś, jednym z podejść do tego jest wdrożenie ważonej losowej. Chodzi o to, aby stworzyć generator liczb losowych (lub wyników), w którym wagi i wyniki mogą być modyfikowane.

Oto implementacja tego w Javie.

import java.util.Map;
import java.util.Random;

/**
 * A psuedorandom weighted outcome generator
 * @param <E> object type to return
 */
public class WeightedRandom<E> {

    private Random random;
    private Map<E, Double> weights;

    public WeightedRandom(Map<E, Double> weights) {
        this.random = new Random();
        this.weights = weights;
    }

    /**
     * Returns a random outcome based on the weight of the outcomes
     * @return
     */
    public E nextOutcome() {
        double totalweight = 0;

        // determine the total weigth
        for (double w : weights.values()) totalweight += w;

        // determine a value between 0.0 and the total weight
        double remaining = random.nextDouble() * totalweight;

        for (E entry : weights.keySet()) {
            // subtract the weight of this entry
            remaining -= weights.get(entry);

            // if the remaining is smaller than 0, return this entry
            if (remaining <= 0) return entry;
        }

        return null;
    }

    /**
     * Returns the weight of an outcome
     * @param outcome the outcome to query
     * @return the weight of the outcome, if it exists
     */
    public double getWeight(E outcome) {
        return weights.get(outcome);
    }

    /**
     * Sets the weight of an outcome
     * @param outcome the outcome to change
     * @param weight the new weigth
     */
    public void setWeight(E outcome, double weight) {
        weights.put(outcome, weight);
    }
}

EDYCJA W przypadku, gdy chcesz automatycznie dostosowywać wagi, na przykład zwiększ szansę na A, gdy wynik to B. Możesz również:

  1. Zmień zachowanie nextOutcome()metody, aby modyfikowała wagę zgodnie z wynikiem
  2. Służy setWeight()do modyfikowania masy zgodnie z wynikiem.
erikgaal
źródło
Myślę, że mogłeś źle odczytać pytanie: PO nie pyta, jak wygenerować ważone losowe wyniki, ale jak dostosować wagi, aby zmniejszyć prawdopodobieństwo wystąpienia tego samego wyniku kilka razy z rzędu.
Ilmari Karonen
Rozumiem, zmieniłem niektóre z moich odpowiedzi, aby wyjaśnić, jak byłoby to możliwe przy użyciu tego systemu.
erikgaal