יום רביעי, 15 בנובמבר 2017

מספרים ומערכים רנדומלים (#C)

למדנו על מספרים רנדומליים בשפת C וב ++C. היום נלמד עליהם ב-#C תוך הרחבה של הנושא.

מספרים אקראיים:


כדי ליצור מספר אקראי ניצור אובייקט מהמחלקה Random:
Random r = new Random();

נוכל להשתמש בו כדי לקבל מספר רנדומלי חדש.
כך: הפונקציה ()Next מחזירה מספר רנדומלי חדש (לא תחזיר מינוס)
r.Next();


נוכל לשלוח לפונקציה Next טווח מספרים.
כך למשל על-מנת להגריל מספר שלם בין 1 ל- 7 (כולל 1 אך לא כולל 7):
r.Next(1,7);
לדוגמה:
            Random r = new Random();
            Console.WriteLine(r.Next(1,9)); // ידפיס מספר רנדומלי בין 1 ל-8 כולל 








נוכל לשלוח לפונקציה Next מספר אחד בלבד. הטווח יהיה בין אפס לבין המספר (לא כולל המספר).
כך הפונקציה תחזיר מספר שלם בין 0 ל- 7 (כולל 0 אך לא כולל 7):
r.Next(7);
לדוגמה:
            Random r = new Random();
            Console.WriteLine(r.Next(9)); // ידפיס מספר רנדומלי בין 0 ל-8 כולל 










בעיה
נתבונן בקוד הבא:
            for (int i = 0; i < 2; i++)
            {
                Random r = new Random();
                Console.WriteLine(r.Next(1, 10));
                Console.WriteLine(r.Next(1, 10));
            }






הקוד הזה לא ידפיס באופן אקראי! במקום זה נקבל: AB AB.
(כש-A הוא המספר שמודפס ראשון ו-B הוא המודפס שני)

למה?
'r' יצר את האקראיות שלו על פי הזמן בו הוא הופעל. מכיוון שבכל איטרציה של הלולאה 'r' נוצר מחדש,
ובגלל שהפרש הזמן בין האיטרציה הראשונה והשנייה שואף לאפס, 'r' מקבל את אותו ערך-הזמן בשתי הפעמים!
האקראיות החדשה של Next מבוססת על האקראיות שהייתה בעת האתחול (ואיננה בודקת שוב את הזמן) ולכן נקבל את אותם מספרים.





פתרון חלקי:
קיימת אפשרות לאתחל את 'r' כך שהאקראיות לא תהיה מבוססת על זמן, אלא על ערך שניתן לו בעצמנו:
Random r = new Random(4);

כעת הזמן לא משפיע על האקראיות.
מה הרווחנו?
            for (int i = 0; i < 2; i++)
            {
                Random r = new Random(i); //משתנה בכל איטרציה גם האקראיות משתנה i מכיוון ש
                Console.WriteLine(r.Next(1, 10));
                Console.WriteLine(r.Next(1, 10));
            }


כעת האקראיות משתנה בכל איטרציה!




אך עדיין יש בעיה:
בכל פעם שנפעיל את התוכנית נקבל את אותם מספרים! (משום ש-i תמיד מתחיל ב-0).
(נקבל בכל הרצה תמיד את אותם: ABCD)






* פתרון מלא:
נשתמש יחד גם באקראיות של זמן וגם באקראיות של מספר!
"זמן" כדי שבכל הרצה של התוכנית נקבל אקראיות חדשה, ו"מספר" כדי שכל האיטרציות לא יתנו את אותם ערכים בשל הזמן הקצר ביניהן.

דוגמה:
            for (int i = 0; i < 2; i++)
            {
                Random rByTime = new Random();
                Random rByNumner = new Random(rByTime.Next()+i);
 
                Console.WriteLine(rByNumner.Next(1, 10));
                Console.WriteLine(rByNumner.Next(1, 10));
            }







אבל רגע!
למה לא עשינו מההתחלה פשוט:
            Random r = new Random();
            for (int i = 0; i < 2; i++)
            {
                Console.WriteLine(r.Next(1, 10));
                Console.WriteLine(r.Next(1, 10));
            }


ברגע שהוצאנו אתחול הזמן מהלולאה כבר אין חשש שהאיטרציות יהיו זהות!
אז למה להסתבך?

תשובה:
זה בהחלט אפשרי. אבל אחרי שהבנו את העיקרון נוכל להתקדם לתוכניות מעשיות יותר:


בתוכניות הבאה ניצור מחלקה עבור צרכים רנדומליים:
    class RandomTools
    {
        public static void ShowOneRandomNumber(int min, int max)
        {
            Random r = new Random();
            Console.WriteLine(r.Next(min, max));
        }
 
    }
    class Program
    {
        static void Main(string[] args)
        {
            RandomTools.ShowOneRandomNumber(1,8);
        }
    }



התוכנית עובדת מעולה.




אבל, מה אם נרצה להשתמש בפונקציה "()ShowOneRandomNumber" הרבה פעמים במהירות?
לדוגמה:
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++ )
                RandomTools.ShowOneRandomNumber(1, 8);
        }


רצינו בסך הכל להציג 10 מספרים רנדומליים, אבל במקום זה נציג שוב ושוב את אותו מספר, משום שלא עבר מספיק זמן בין כל קריאה.





פתרון סופי 1 (פחות מומלץ):
    class RandomTools
    {
        static int Iteration = 0;
        public static void ShowOneRandomNumber(int min, int max)
        {
            Iteration++;
            Random rByTime = new Random();
            Random rByNumner = new Random(rByTime.Next() + Iteration);
            Console.WriteLine(rByNumner.Next(min, max));
        }
 
    }
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++ )
                RandomTools.ShowOneRandomNumber(1, 8);
        }
    }







פתרון סופי 2 (מומלץ יותר):
כאן יוצרים 'rByTime' אחד ויחיד שמשמש כל פונקציה שעושה שימוש ברנדומליות.
יתרון: מלבד התחביר הקריא יותר, כנראה נרוויח כך מיטוב של הביצועים. זאת משום שיש רק גישה
אחת ויחידה לשעון המחשב לאורך כל התוכנית.
    class RandomTools
    {
        static Random rByTime = new Random(); // בדיקה אחת ויחידה של הזמן
        public static void ShowOneRandomNumber(int min, int max)
        {
            Console.WriteLine(rByTime.Next(min, max));
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
                RandomTools.ShowOneRandomNumber(1, 8);        
        }
    }





מקורות ועוד: וובמאסטר, MSDN




...................................................................................................................................












העשרה: הדפסה רנדומלית של ערכים בוליאניים:
מקור: MSDN
        public static void Main()
        {
            Random rnd = new Random();
            for (int ctr = 1; ctr <= 5; ctr++)
            {
                Boolean bln = Convert.ToBoolean(rnd.Next(0, 2));
                Console.WriteLine("True or False: {0}", bln);
            }
        }








מילוי מערך במספרים רנדומליים:
    class RandomTools
    {
        static Random rByTime = new Random();
        public static void randomTheArray(int[] arr)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                arr[i] = rByTime.Next(0, 4);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[4];
            RandomTools.randomTheArray(arr);
            Console.WriteLine(String.Join(", ", arr));
        }
    }








ערבוב מערך:
    class RandomTools
    {
        static Random rByTime = new Random();
 
        public static void ShuffleArray(int[] array)
        {
            int i, j, k;
            for (i = array.Length; i > 0; i--)
            {
                j = rByTime.Next(i);
                k = array[j];
                array[j] = array[i - 1];
                array[i - 1] = k;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[4] { 1, 2, 3, 4 };
            RandomTools.ShuffleArray(arr);
            Console.WriteLine(String.Join(", ", arr));
        }
    }












מילוי מערך במספרים רנדומליים כשכל מספר מופיע במערך לכל היותר פעם אחת:

דרך א' (הדרך הפשוטה):
    class RandomTools
    {
        static Random rByTime = new Random();
        public static void RandomArray_NoDuplic(int[] Arr, int min, int max)
        {
            int i, j, num;
            Arr[0] = rByTime.Next(min, max+1);
            for (i = 1; i < Arr.Length; i++)
            {
                do
                {
                    num = rByTime.Next(min, max + 1);
                    for (j = 0; j < i; j++)
                    {
                        if (Arr[j] == num) break;
                    }
                    if (j < i) continue;// כלומר אם מצאנו מספר שווה לו במערך
                    break;
                } while (true);
                Arr[i] = num;
            }
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[4];
            RandomTools.RandomArray_NoDuplic(arr, 0, 7);
            Console.WriteLine(String.Join(", ", arr));
        }
    }







דרך ב' (תוך שימוש בפונקציה "ערבוב מערך"):
דרך ב' תהיה לרוב מהירה יותר מדרך א'.

תיאור לדוגמה:
מערך היעד בגודל 4. על כל איבר במערך להכיל מספר רנדומלי בטווח בין 0 ל-7 (כולל). אסור למספר לחזור על עצמו.

שלבים:
1. ניצור מערך שמייצג את כל טווח המספרים. (מערך בגודל 8, שהאיבר הראשון שלו מכיל 0, השני 1, השלישי 2 וכו' עד 7)
2. נערבב את המערך הנ"ל.
3. נעתיק את 4 האיברים הראשונים של המערך אל מערך היעד.

    class RandomTools
    {
        static Random rByTime = new Random();
 
        public static void ShuffleArray(int[] array)
        {
            int i, j, k;
            for (i = array.Length; i > 0; i--)
            {
                j = rByTime.Next(i);
                k = array[j];
                array[j] = array[i - 1];
                array[i - 1] = k;
            }
        }
 
        public static void RandomArray_NoDuplic_Using_Shuffle(int[] Arr, int min, int max)
        {
            // נציב במערך הטווח את כל המספרים בין המינימום למקסימום
            int[] ArrayOfAllRange = new int[max - min + 1];
            for (int i = 0; i < ArrayOfAllRange.Length; i++)
            {
                ArrayOfAllRange[i] = i + min;
            }
 
            // נערבב את מערך הטווח
            ShuffleArray(ArrayOfAllRange);
 
 
            // נעתיק למערך היעד את המספרים שבתחילת מערך הטווח כאורך מערך היעד
            for (int i = 0; i < Arr.Length; i++)
            {
                Arr[i] = ArrayOfAllRange[i];
            }
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[4];
            RandomTools.RandomArray_NoDuplic_Using_Shuffle(arr, 0, 7);
            Console.WriteLine(String.Join(", ", arr));
        }
    }






בהצלחה!


אין תגובות:

הוסף רשומת תגובה