Random.Next inaczej działa w debugerze a inaczej w programie

Random.Next inaczej działa w debugerze a inaczej w programie
ninja_koder
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 9 lat
  • Postów:9
0

Przerabiam przykład z książki. na podstawie tego co jest napisane w książce, stworzyłem program, który:

-tworzy instancję klasy zawierającej kilka tablic, każda składa się z kilku elementów
-tworzy instancję klasy Random i za jej pomocą wybiera losowy element z każdej tablicy
-przekazuje wylosowany element z każdej tablicy do stringa, w którym są one dodawane i wyświetlane w etykiecie
-etykiet jest kilka, zatem po uruchomieniu programu mam kilka zestawów losowo dobranych elementów

Program działa znakomicie i zgodnie z założeniami. Tyle, że nie rozumiem, po co tworzona jest instancja klasy zawierającej te tablice i chciałem sprawdzić, czy da się pominąć tę instrukcję (ponieważ uważam, że jest zbędna). Napisałem program i... w każdej etykiecie pojawiają mi się prawie za każdym razem takie same wartości, w sensie:

etykieta 1: aabbc
etykieta 2: aabbc
etykieta 3: aabbc
etykieta 4: aabbc

Wartości te zmieniają się np. na cbcab, ale w każdej etykiecie prawie zawsze się pokrywają. Czasem zadziała tak, że jedna z etykiet jest inna od reszty, ale zdarza się to rzadko. Ponieważ nie mogłem zrozumieć skąd się to bierze, to przepuściłem program przez debuggera i okazało się, że w debuggerze program działa prawidłowo! I tak jest za każdym razem. Program zachowuje się tak, jakby Random nie nadążał ze zmianą swoich wartości i przez to liczba odpowiedzialna za losowanie była taka sama dla każdego losowania.

To jest poprawnie działający kod tworzący instancję klasy MenuMaker (wg mnie niepotrzebnie)

kod klasy zawierającej tablice i metodę odpowiedzialną za losowanie

Kopiuj
    class MenuMaker
    {
        public Random Randomizer = new Random();

        string[] Meats = { "Pieczona wołowina", "Salami", "Indyk", "Szynka", "Karkówka" };
        string[] Condiments = {"żółta musztarda", "przyprawa", "brązowa musztarda",
                                "musztarda miodowa", "majonez", "sos francuski"};
        string[] Breads = {"chleb ryżowy", "chleb biały", "chleb zbożowy", "pumpernikiel",
                            "chleb włoski", "bułka"};

        public string GetMenuItem()
        {
            string randomMeat = Meats[Randomizer.Next(Meats.Length)];
            string randomCondiments = Condiments[Randomizer.Next(Condiments.Length)];
            string randomBreads = Breads[Randomizer.Next(Breads.Length)];

            return randomMeat + ", " + randomCondiments + ", " + randomBreads + ".";
        }

     }

-to jest kod podpięty do przycisku tworzący zawartość etykiet

Kopiuj
        private void button1_Click(object sender, EventArgs e)
        {
            MenuMaker menu = new MenuMaker();

            label1.Text = menu.GetMenuItem();
            label2.Text = menu.GetMenuItem();
            label3.Text = menu.GetMenuItem();
            label4.Text = menu.GetMenuItem();
            label5.Text = menu.GetMenuItem();
            label6.Text = menu.GetMenuItem();
        }
 

A to kod programu, który nie działa prawidłowo (poza debuggerem) - ma tę samą klasę MenuMaker, różni się tylko okodowaniem przycisku

Kopiuj
private void button1_Click(object sender, EventArgs e)
        {
            label1.Text = MenuMaker.GetMenuItem();
            label2.Text = MenuMaker.GetMenuItem();
            label3.Text = MenuMaker.GetMenuItem();
            label4.Text = MenuMaker.GetMenuItem();
            label5.Text = MenuMaker.GetMenuItem();
            label6.Text = MenuMaker.GetMenuItem();

        } 
edytowany 1x, ostatnio: ninja_koder
1

wywołując "new Random()" tworzysz nowy generator liczb pseudolosowych przyjmujący jako ziarno aktualną godzinę
tzn. że wszystkie "generatory" utworzone w tej samej sekundzie będą kolejno generować te same liczby
resztę sobie dopowiedz

ninja_koder
no to już jest spore naprowadzenie, tylko dlaczego program zachowuje się poprawnie kiedy tworzę instancję klasy MenuMaker? instancja jest utworzona raz, a potem dzieje się (wg mnie) dokładnie to samo co w drugim przypadku - tzn. wiem, że nie dzieje się, skoro mam różne wyniki, ale nie rozumiem dlaczego... czy to chodzi o to, że w związku z utworzeniem instancji klasy dochodzą jakieś dodatkowe kroki, które wydłużają czas przetwarzania instrukcji, co sprawia, że czas ma większą szansę się zmienić?
1

no to już jest spore naprowadzenie, tylko dlaczego program zachowuje się poprawnie kiedy tworzę instancję klasy MenuMaker? instancja jest utworzona raz, a potem dzieje się (wg mnie) dokładnie to samo co w drugim przypadku - tzn. wiem, że nie dzieje się, skoro mam różne wyniki, ale nie rozumiem dlaczego... czy to chodzi o to, że w związku z utworzeniem instancji klasy dochodzą jakieś dodatkowe kroki, które wydłużają czas przetwarzania instrukcji, co sprawia, że czas ma większą szansę się zmienić?

tworząc instancję klasy, tworzysz generator liczb RAZ i go wykorzystujesz ponownie. Nie mogę napisać czemu Twój kod nie działa bo go nie załączyłeś, ale prawdopodobnie tworzysz nowy obiekt typu Random za każdym razem

SO
  • Rejestracja:ponad 10 lat
  • Ostatnio:około rok
0

Pokaż jak wygląda GetMenuItem() po przerobieniu na metodę statyczną, bo tam tam prawdopodobnie masz błąd.

ninja_koder
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 9 lat
  • Postów:9
0

ok, wklejam pełny kod

Program działający poprawnie
Plik Form1.cs

Kopiuj
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Niechlujny_Janek
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MenuMaker menu = new MenuMaker();

            label1.Text = menu.GetMenuItem();
            label2.Text = menu.GetMenuItem();
            label3.Text = menu.GetMenuItem();
            label4.Text = menu.GetMenuItem();
            label5.Text = menu.GetMenuItem();
            label6.Text = menu.GetMenuItem();
        }
    }
}

i jego klasa

Kopiuj
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Niechlujny_Janek
{
    class MenuMaker
    {
        public Random Randomizer = new Random();

        string[] Meats = { "Pieczona wołowina", "Salami", "Indyk", "Szynka", "Karkówka" };
        string[] Condiments = {"żółta musztarda", "przyprawa", "brązowa musztarda",
                                "musztarda miodowa", "majonez", "sos francuski"};
        string[] Breads = {"chleb ryżowy", "chleb biały", "chleb zbożowy", "pumpernikiel",
                            "chleb włoski", "bułka"};

        public string GetMenuItem()
        {
            string randomMeat = Meats[Randomizer.Next(Meats.Length)];
            string randomCondiments = Condiments[Randomizer.Next(Condiments.Length)];
            string randomBreads = Breads[Randomizer.Next(Breads.Length)];

            return randomMeat + ", " + randomCondiments + ", " + randomBreads + ".";
        }

    }
}

A to program działający niepoprawnie

Plik Form1.cs

Kopiuj
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace paplacz
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {

            label3.Text = Talker.TworzMenuInator();
            label4.Text = Talker.TworzMenuInator();
            label5.Text = Talker.TworzMenuInator();

        }
    }
}
 

I jego klasa

Kopiuj
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace paplacz
{
    class Talker
    {
        public static string TworzMenuInator()
        {
            Random Randomizer = new Random();

            string[] Meats = { "Pieczona wołowina", "Salami", "Indyk", "Szynka", "Karkówka" };
            string[] Condiments = {"żółta musztarda", "przyprawa", "brązowa musztarda",
                                "musztarda miodowa", "majonez", "sos francuski"};
            string[] Breads = {"chleb ryżowy", "chleb biały", "chleb zbożowy", "pumpernikiel",
                            "chleb włoski", "bułka"};

            string randomMeat = Meats[Randomizer.Next(Meats.Length)];
            string randomCondiments = Condiments[Randomizer.Next(Condiments.Length)];
            string randomBreads = Breads[Randomizer.Next(Breads.Length)];

            return randomMeat + ", " + randomCondiments + ", " + randomBreads + ".";

        }
    }
}
edytowany 1x, ostatnio: ninja_koder
SO
  • Rejestracja:ponad 10 lat
  • Ostatnio:około rok
1

No to odpowiedź podał @KrzywyPomidor.
Przy każdym wywołaniu metody tworzysz nową instancjęRandom, a dzieje się to tak szybko, że każdy Random dostaje taką samą wartość jako seed i zwraca takie same wartości.

edytowany 1x, ostatnio: some_ONE
grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0
SO
Ta, i zaraz zaimplementuje Singletona do tej swojej klasy :P
grzesiek51114
grzesiek51114
@some_ONE przepraszam... ;)
ninja_koder
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 9 lat
  • Postów:9
0

dzięki wszystkim za szybką odpowiedź. zaczaiłem na czym polegał błąd w moim rozumowaniu. sądziłem, że random tworzy JEDNĄ liczbę z zakresu 0...1 (jakoś tak zapamiętałem z czasów, kiedy uczyłem się pisać programy na ATARI) i ta liczba jest używana do wybierania losowo elementów z podanego zakresu - a to nie pasowało mi do tego, że poprawne rozwiązanie polegające na stworzeniu tylko jednej instancji ma dawać różne wyniki (no niemożliwe, aby raz wylosowana liczba x mnożona przez te same liczby dawała różne wyniki). poszperałem po sieci o zasadzie działania klasy Random i doczytałem, że tworzona jest nie jedna liczba, a cała tablica liczb, z których kolejne wartości są pobierane za pomocą Random.Next().

Dla dociekliwych: potrzeba około 10ms przerwy pomiędzy kolejnymi wywołaniami funkcji Random, aby stworzone zostały różne tablice.

edytowany 1x, ostatnio: ninja_koder

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.