יום שני, 19 בפברואר 2018

פרסום קוד: מציאת כל הפתרונות האפשריים של כדור מחומשים (#C)

מציאת כל הפתרונות האפשריים שיש לכדור המחומשים

הכל התחיל מכדור מחומשים יפה ושלם.
מדובר היה במשחק, לפרק את הכדור ולהרכיב אותו בחזרה, איזה כיף.
אך אבוי! אחרי שפורק הכדור איש לא הצליח להרכיב אותו בהצלחה.

עברו שנים, ונזכרתי שאני מתכנת...





תמונה של הכדור מפורק:






תמונה של הכדור השלם:
(המטרה היא להשיג כדור שלם כמו זה, בכל הדרכים האפשריות. התמונה צולמה אחרי כתיבת הקוד)










....................................................
שלב ראשון: מציאת שיטת ייצוג לנתונים
הצגת החלקים על דף:




הצגת צורה של כדור שלם על דף:









....................................................
שלב שני: מימוש אלגוריתם להרכבת הכדור
האלגוריתם הוא למעשה Brute Force. יש לנו "מבנה מדומה" שמזיזים בכל הדרכים האפשריות. אחרי כל הזזה בודקים האם המבנה תואם כעת ל"מבנה הנכון", כלומר בודקים האם המבנה המדומה יוצר צורה של כדור מושלם.
קוד:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SolveBall
{
    class Program
    {
        static void Main(string[] args)
        {
            //////////////////////////////////////////////////////////////////////////////////
            /////////                  יכול להיות כדור תקין discsSequence הרעיון זה לבדוק האם //////////
            /////////                              goodBall על פי חוקיו של כדור הנמצאים ב //////////
            //////////////////////////////////////////////////////////////////////////////////
 
            // המערך הבסיסי שגדל כל לופ
            // [InSize1, OutSize1, InSize2, OutSize2...]
            int[] attemtsArray = new int[20];
 
            // רצף של המחומשים בסידור אפשרי מסויים שמתיימר להיות כדור
            DiscsSequence discsSequence = new DiscsSequence();
 
            // תבנית כדור תקין אמיתי
            GoodBall goodBall = new GoodBall();
 
            int countOfGoodSolutions = 0;
            Console.WriteLine("Solving...");
 
            // לולאה שמנסה את כל הצירופים האפשריים
            do
            {
                // ניסיון המרה של המערך לרצף מחומשים אפשרי
                if (!discsSequence.TryInitByArray(attemtsArray))
                {
                    continue;
                }
 
                // ( מספר מצבים אפשריים שהגיעו לכאן: 73,728 )
 
                goodBall.Reset();
 
                // בדיקה האם הרצף יוצר כדור תקין
                if (!goodBall.TryInitBallByDiscsSequence(discsSequence))
                {
                    continue;
                }
 
                countOfGoodSolutions++;
 
                // אם הגענו לכאן המערך מייצג כדור תקין. נדפיס אותו
                Console.WriteLine(countOfGoodSolutions + ".");
                UsefulTools.PrintArray(attemtsArray);
 
                // הגדל את המערך
                // (המספר 2 על שום שאין מחומש עם כניסה או יציאה גדולים מ2 כאשר מתחילים למנות מ0)
            } while (UsefulTools.IncreaseArray(attemtsArray, 2));
 
            Console.WriteLine("Count: " + countOfGoodSolutions);
            Console.WriteLine("Goodbye.");
            Console.ReadLine();
        }
    }
 
    // ייצוג של מאפייני מחומש שלא משתנים
    class DiscProperties
    {
        // גודל כניסה - מספר הפתחים בכניסה
        public readonly int InSize;
 
        // גודל יציאה - מספר הפתחים ביציאה
        public readonly int OutSize;
 
        // מספר יציאה - כאשר מעמידים את המחומש על צלע הכניסה שהכי נוטה נגד כיוון השעון
        // ומתחילים למנות נגן כיוון השעון את מספר הצלעות מהצלע התחתונה עד
        // לצלע היציאה הראשונה
        public readonly int OutNumber;
 
        public DiscProperties(int inSize, int outSize, int outNumber)
        {
            this.InSize = inSize;
            this.OutSize = outSize;
            this.OutNumber = outNumber;
        }
    }
 
    // ייצוג של מיקום החוט כרגע במחומש
    class DiscWithWire
    {
        /* הסכום של 'נסיון הכניסה' ו'נסיון היציאה' יהיה ההיסט שמוסיפים להיסט הטבעי של המחומש */
 
        // ההפרש בין צלע הכניסה שהכי נוטה נגד כיוון השעון לבין צלע הכניסה שהחוט נכנס בה
        public int InTry;
 
        // ההפרש בין צלע היציאה שהכי נוטה בכיוון השעון לבין צלע היציאה שהחוט יוצא ממנה
        public int OutTry;
    }
 
    // ייצוג רצף אפשרי כלשהו של מחומשים המחוברים אחד לשני על ידי החוט
    class DiscsSequence
    {
        // מאפייני המחומשים שאינם משתנים
        public static readonly DiscProperties[] DiscsProperties = new DiscProperties[12];
 
        // האופן שהחוט כרגע עובר בין המחומשים
        public DiscWithWire[] discsWithWire = new DiscWithWire[12];
 
        static DiscsSequence()
        {
            // Set the InSize, OutSize, and OutNumber of all discs
            DiscsProperties[0]  = new DiscProperties(0, 0, 4);
            DiscsProperties[1]  = new DiscProperties(1, 1, 3);
            DiscsProperties[2]  = new DiscProperties(0, 1, 3);
            DiscsProperties[3]  = new DiscProperties(0, 1, 3);
            DiscsProperties[4]  = new DiscProperties(1, 0, 3);
            DiscsProperties[5]  = new DiscProperties(1, 2, 2);
            DiscsProperties[6]  = new DiscProperties(1, 1, 3);
            DiscsProperties[7]  = new DiscProperties(0, 1, 3);
            DiscsProperties[8]  = new DiscProperties(1, 1, 2);
            DiscsProperties[9]  = new DiscProperties(2, 1, 2);
            DiscsProperties[10] = new DiscProperties(1, 0, 3);
            DiscsProperties[11] = new DiscProperties(0, 0, 3);
        }
 
        public DiscsSequence()
        {
            for (int i = 0; i < 12; i++)
            {
                discsWithWire[i] = new DiscWithWire();
            }
        }
 
        public bool TryInitByArray(int[] attemtsArr)
        {
            // לצורך יעילות התוכנית, מאחר והמחומש הראשון והמחומש האחרון לא משתנים, אפשר לא
            // ובמקום זה לשים בהם תמיד את אותם ערכים attemtsArr לייצג אותם מערך
            discsWithWire[0].InTry   = 0;
            discsWithWire[0].OutTry  = 0;
            discsWithWire[11].InTry  = 0;
            discsWithWire[11].OutTry = 0;
 
            for (int attemtsArrIndex = 0, currDisc = 1;
                 (currDisc < 11);
                 attemtsArrIndex += 2, currDisc++)
            {
                if (DiscsProperties[currDisc].InSize < attemtsArr[attemtsArrIndex])
                {
                    return false;
                }
 
                if (DiscsProperties[currDisc].OutSize < attemtsArr[attemtsArrIndex + 1])
                {
                    return false;
                }
 
                discsWithWire[currDisc].InTry = attemtsArr[attemtsArrIndex];
                discsWithWire[currDisc].OutTry = attemtsArr[attemtsArrIndex + 1];
            }
 
            return true;
        }
    }
 
    class DiscPropertiesInGoodBall
    {
        // אינדקסים של המחומשים שצמודים למחומש הנוכחי
        public readonly int[] IndexesOfDiscsAround;
 
        // ה'תוספת' שיש להוסיף לכל מחומש מסביב כדי לחשב נכון את האינדקסים שלו ביחס לכניסה אליו
        public readonly int[] DiffAround;
 
        public DiscPropertiesInGoodBall(int i0, int i1, int i2, int i3, int i4,
            int d0, int d1, int d2, int d3, int d4)
        {
            IndexesOfDiscsAround = new int[5];
            IndexesOfDiscsAround[0] = i0;
            IndexesOfDiscsAround[1] = i1;
            IndexesOfDiscsAround[2] = i2;
            IndexesOfDiscsAround[3] = i3;
            IndexesOfDiscsAround[4] = i4;
 
            DiffAround = new int[5];
            DiffAround[0] = d0;
            DiffAround[1] = d1;
            DiffAround[2] = d2;
            DiffAround[3] = d3;
            DiffAround[4] = d4;
        }
    }
 
    class GoodBall
    {
        private static DiscPropertiesInGoodBall[] GoodBallArr;
        private bool[] isFullPosition = new bool[13];
 
        // קביעת איך נראה כדור שלם: אילו מחומשים מקיפים כל מחומש
        // ומה ההפרש בין הצלע של המחומש החיצוני שנוגעת בנוכחי לבין אחד
        static GoodBall()
        {
            GoodBallArr = new DiscPropertiesInGoodBall[13];
 
            GoodBallArr[1] = new DiscPropertiesInGoodBall(4, 5, 6, 2, 3, // אינדקסים של המחומשים מסביב
                0, 0, 0, 0, 0);                                          // ההפרשים
            GoodBallArr[2] = new DiscPropertiesInGoodBall(1, 6, 11, 7, 3,
                3, 4, 2, 2, 1);
            GoodBallArr[3] = new DiscPropertiesInGoodBall(1, 2, 7, 8, 4,
                4, 4, 1, 4, 1);
            GoodBallArr[4] = new DiscPropertiesInGoodBall(1, 3, 8, 9, 5,
                0, 4, 3, 3, 1);
 
            GoodBallArr[5] = new DiscPropertiesInGoodBall(1, 4, 9, 10, 6,
                1, 4, 2, 3, 1);
            GoodBallArr[6] = new DiscPropertiesInGoodBall(1, 5, 10, 11, 2,
                2, 4, 2, 3, 1);
            GoodBallArr[7] = new DiscPropertiesInGoodBall(8, 3, 2, 11, 12,
                0, 2, 3, 1, 0);
 
            GoodBallArr[8] = new DiscPropertiesInGoodBall(7, 12, 9, 4, 3,
                0, 4, 4, 2, 3);
            GoodBallArr[9] = new DiscPropertiesInGoodBall(12, 10, 5, 4, 8,
                3, 4, 2, 3, 2);
 
            GoodBallArr[10] = new DiscPropertiesInGoodBall(12, 11, 6, 5, 9,
                2, 4, 2, 3, 1);
            GoodBallArr[11] = new DiscPropertiesInGoodBall(12, 7, 2, 6, 10,
                1, 3, 2, 3, 1);
            GoodBallArr[12] = new DiscPropertiesInGoodBall(7, 11, 10, 9, 8,
                4, 0, 0, 0, 1);
        }
 
        // זו הפונקציה העיקרית של התוכנית, בודקת האם המערך מייצג צורת כדור תקינה
        public bool TryInitBallByDiscsSequence(DiscsSequence discsSeq)
        {
            // מתחילים תמיד כשהמחומש הראשון ברצף נחשב למחומש הראשון בצורת הכדור הנכונה
            int indOfCurrDiscInGoodShape = 1;
            int diffOfCurrDiscInGoodShape = 0;
            int nextRib;
 
            for (int i = 0; i < 12; i++)
            {
                // אם רצף המחומשים שקיבלנו הוביל לכך שמחומש אחד עולה על מחומש אחר
                if (isFullPosition[indOfCurrDiscInGoodShape])
                {
                    return false;
                }
 
                // נסמן שהמחומש הנוכחי כעת מלא
                isFullPosition[indOfCurrDiscInGoodShape] = true;
 
                // סביב כל מחומש יש חמישה מחומשים, המחומש שלנו יודע איזה מחומש צמוד לכל צלע, נשאר
                // לגלות לאיזו צלע החוט שלנו ממשיך
                nextRib = discsSeq.discsWithWire[i].InTry +
                          discsSeq.discsWithWire[i].OutTry +
                          DiscsSequence.DiscsProperties[i].OutNumber +
                          diffOfCurrDiscInGoodShape;
 
                // אם ההיסטים, צורת המחומש, והתוספת שצריך להוסיף בגלל צורת הכדור הובילו לכך
                // שהצלע שמפנה למחומש הבא חושבה בסיבוב של יותר מ360 מעלות - יש לחשב מחדש מה המיקום המעשי שלה
                // (הנוסחה הציקלית גם הייתה יכולה לפתור את הבעיה)
                if (nextRib > 5)
                {
                    nextRib -= 5;
                }
 
                // נחשב כבר עכשיו את 'התוספת' שצריך להוסיף למחומש הבא, היות וכל מחומש
                // יודע מה התוספת שלו על פי המחומש שקדם לו
                diffOfCurrDiscInGoodShape =
                    GoodBallArr[indOfCurrDiscInGoodShape].DiffAround[nextRib - 1];
 
                // נחשב את אינדקס המחומש הבא
                indOfCurrDiscInGoodShape =
                    GoodBallArr[indOfCurrDiscInGoodShape].IndexesOfDiscsAround[nextRib - 1];
            }
 
            return true;
        }
 
        public void Reset()
        {
            // Init the array to the default false value
            isFullPosition = new bool[13];
        }
    }
 
    class UsefulTools
    {
        public static void PrintArray(int[] Array)
        {
            foreach (int num in Array)
            {
                Console.Write(num + ", ");
            }
 
            Console.WriteLine();
            Console.WriteLine();
        }
 
        // Increase the given array, as it was one number and each cell is one of its digits.
        //
        // Parameters:
        //     arr - The array to increase. must be Initialized.
        //     max - The 'base' of the array-number: the max value each cell can contain.
        //
        // Return Value:
        //     If can't increase the array anymore (all the calls reached the max value),
        //     return 'false', otherwise return true.
        //
        // Usage Example:
        //     int[] A = {0, 0};
        //     do
        //     {
        //         UsefulTools.PrintArray(A);
        //     } while (IncreaseArray(A, 3));
        //
        public static bool IncreaseArray(int[] arr, int max)
        {
            return IncreaseArray(arr, max, arr.Length - 1);
        }
 
        private static bool IncreaseArray(int[] arr, int max, int ind)
        {
            if (ind < 0)
            {
                return false;
            }
 
            if (arr[ind] == max)
            {
                arr[ind] = 0;
                return IncreaseArray(arr, max, ind - 1);
            }
 
            arr[ind]++;
            return true;
        }
 
        // This function do the same as 'IncreaseArray' but not in recursive way
        public static bool IncreaseArray2(int[] arr, int max)
        {
            int ind = arr.Length - 1;
 
            while (true)
            {
                if (ind < 0)
                {
                    return false;
                }
 
                if (arr[ind] == max)
                {
                    arr[ind] = 0;
                    ind--;
                }
                else
                {
                    arr[ind]++;
                    return true;
                }
            }
        }
    }
}









מסקנה לאחר הרצת התוכנית: קיימים ארבעה פתרונות אפשריים!

משמאל לימין: "גודל כניסה", "גודל יציאה", "גודל כניסה", "גודל יציאה"...
(המחומש הראשון והמחומש האחרון לא נמצאים משום שהם קבועים)


1.
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0,

2.
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0,

3.
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0,

4.
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0,










בעיה:
הקוד ממש איטי (3 דקות ו-51 שניות אצלי).
הסיבה לכך היא שכדי לממש את ה-Brute Force יצרתי מערך שגדל בכל איטרציה, ובכל פעם שהוא גדל ניסיתי להמיר אותו לרצף אפשרי של מחומשים. (רק את הרצפים האפשריים בודקים האם הם פתרון טוב לכדור).

איך המערך גדל כל פעם?
עשיתי שימוש בפונקציה כללית להגדלת מערכים בשם 'IncreaseArray' אשר מתייחסת למערך כאל מספר שיש להגדיל.
הפונקציה הזו טובה במקרים אחרים, אבל במקרה שלנו היא מייצרת כמעט תמיד מערך שלא יכול להפוך ל"רצף אפשרי של מחומשים" בשל ה'גודל כניסה מקסימלי' וה'גודל יציאה מקסימלי' של כל מחומש ברצף.


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


הקוד הבא מהיר לאין שיעור מהקודם:
(0.03 שניות אצלי. הפעם המחומש הראשון והאחרון כן נכללים בתהליך החישוב ובהדפסה הסופית)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SolveBall
{
    class Program
    {
        static void Main(string[] args)
        {
            //////////////////////////////////////////////////////////////////////////////////
            /////////                  יכול להיות כדור תקין discsSequence הרעיון זה לבדוק האם //////////
            /////////                              goodBall על פי חוקיו של כדור הנמצאים ב //////////
            //////////////////////////////////////////////////////////////////////////////////
 
            // המערך הבסיסי שגדל כל לופ
            // [InSize1, OutSize1, InSize2, OutSize2...]
            int[] attemtsArray = new int[24];
 
            // רצף של המחומשים בסידור אפשרי מסויים שמתיימר להיות כדור
            DiscsSequence discsSequence = new DiscsSequence();
 
            // אובייקט שיודע להגדיל את המערך
            ArrayIncreaser arrayIncreaser =
                new ArrayIncreaser(attemtsArray, DiscsSequence.GetArrayOfAllMaxValues());
 
            // תבנית כדור תקין
            GoodBall goodBall = new GoodBall();
 
            int countOfGoodSolutions = 0;
            Console.WriteLine("Solving...");
 
            // לולאה שמנסה את כל הצירופים האפשריים
            do
            {
                // המרה של המערך לרצף מחומשים אפשרי
                // מובטח שניתן לבצע את ההמרה arrayIncreaser בזכות
                discsSequence.InitByArray(attemtsArray);
 
                // איפוס הכדור
                goodBall.Reset();
 
                // בדיקה האם הרצף יוצר כדור תקין
                if (!goodBall.IsDiscsSequenceGoodBall(discsSequence))
                {
                    continue;
                }
 
                countOfGoodSolutions++;
 
                // אם הגענו לכאן המערך מייצג כדור תקין. נדפיס אותו
                Console.WriteLine(countOfGoodSolutions + ".");
                UsefulTools.PrintArray(attemtsArray);
 
                // הגדלת המערך
            } while (arrayIncreaser.IncreaseArray());
 
            Console.WriteLine("Count: " + countOfGoodSolutions);
            Console.WriteLine("Goodbye.");
            Console.ReadLine();
        }
    }
 
    // ייצוג של מאפייני מחומש שלא משתנים
    class DiscProperties
    {
        // גודל כניסה - מספר הפתחים בכניסה
        public readonly int InSize;
 
        // גודל יציאה - מספר הפתחים ביציאה
        public readonly int OutSize;
 
        // מספר יציאה - המספר שמתקבל כאשר מעמידים את המחומש על צלע הכניסה שהכי
        // נוטה נגד כיוון השעון ומתחילים למנות נגן כיוון השעון את מספר הצלעות
        // מהצלע התחתונה עד לצלע היציאה הראשונה
        public readonly int OutNumber;
 
        public DiscProperties(int inSize, int outSize, int outNumber)
        {
            this.InSize = inSize;
            this.OutSize = outSize;
            this.OutNumber = outNumber;
        }
    }
 
    // ייצוג של מיקום החוט כרגע במחומש
    class DiscWithWire
    {
        /* הסכום של 'נסיון הכניסה' ו'נסיון היציאה' יהיה ההיסט שמוסיפים להיסט הטבעי של המחומש */
 
        // ההפרש בין צלע הכניסה שהכי נוטה נגד כיוון השעון לבין צלע הכניסה שהחוט נכנס בה
        public int InTry;
 
        // ההפרש בין צלע היציאה שהכי נוטה לכיוון השעון לבין צלע היציאה שהחוט יוצא ממנה
        public int OutTry;
    }
 
    // ייצוג רצף אפשרי כלשהו של מחומשים המחוברים אחד לשני על ידי החוט
    class DiscsSequence
    {
        // מאפייני מחומשים שאינם משתנים
        public static readonly DiscProperties[] DiscsProperties = new DiscProperties[12];
 
        // האופן שבו ההחוט כרגע עובר בין המחומשים
        public DiscWithWire[] discsWithWire = new DiscWithWire[12];
 
        // אתחול המאפיינים הקבועים
        static DiscsSequence()
        {
            // Set the InSize, OutSize, and OutNumber of all discs
            DiscsProperties[0]  = new DiscProperties(0, 0, 4);
            DiscsProperties[1]  = new DiscProperties(1, 1, 3);
            DiscsProperties[2]  = new DiscProperties(0, 1, 3);
            DiscsProperties[3]  = new DiscProperties(0, 1, 3);
            DiscsProperties[4]  = new DiscProperties(1, 0, 3);
            DiscsProperties[5]  = new DiscProperties(1, 2, 2);
            DiscsProperties[6]  = new DiscProperties(1, 1, 3);
            DiscsProperties[7]  = new DiscProperties(0, 1, 3);
            DiscsProperties[8]  = new DiscProperties(1, 1, 2);
            DiscsProperties[9]  = new DiscProperties(2, 1, 2);
            DiscsProperties[10] = new DiscProperties(1, 0, 3);
            DiscsProperties[11] = new DiscProperties(0, 0, 3);
        }
 
        // ArrayIncreaser-פונקציה זו נועדה רק לשימוש על ידי ה
        public static int[] GetArrayOfAllMaxValues()
        {
            List<int> myList = new List<int>();
 
            foreach (DiscProperties disc in DiscsProperties)
            {
                myList.Add(disc.InSize);
                myList.Add(disc.OutSize);
            }
 
            return myList.ToArray();
        }
 
        public DiscsSequence()
        {
            for (int i = 0; i < 12; i++)
            {
                discsWithWire[i] = new DiscWithWire();
            }
        }
 
        public void InitByArray(int[] attemtsArr)
        {
            // מובטח שהמערך שקיבלנו תקין, כלומר לא יוצר מופעים של דיסקים שיש בהם
            // גודל כניסה או גודל יציאה גדולים מהאפשריים בהם
 
            for (int attemtsArrIndex = 0, currDisc = 0;
                 (currDisc < 12);
                 attemtsArrIndex += 2, currDisc++)
            {
                discsWithWire[currDisc].InTry = attemtsArr[attemtsArrIndex];
                discsWithWire[currDisc].OutTry = attemtsArr[attemtsArrIndex + 1];
            }
        }
    }
 
    class DiscPropertiesInGoodBall
    {
        // אינדקסים של המחומשים שצמודים למחומש הנוכחי
        public readonly int[] IndexesOfDiscsAround;
 
        // ה'תוספת' שיש להוסיף לכל מחומש מסביב כדי לחשב נכון את האינדקסים שלו ביחס לכניסה אליו
        public readonly int[] DiffAround;
 
        public DiscPropertiesInGoodBall(int i0, int i1, int i2, int i3, int i4,
            int d0, int d1, int d2, int d3, int d4)
        {
            IndexesOfDiscsAround = new int[5];
            IndexesOfDiscsAround[0] = i0;
            IndexesOfDiscsAround[1] = i1;
            IndexesOfDiscsAround[2] = i2;
            IndexesOfDiscsAround[3] = i3;
            IndexesOfDiscsAround[4] = i4;
 
            DiffAround = new int[5];
            DiffAround[0] = d0;
            DiffAround[1] = d1;
            DiffAround[2] = d2;
            DiffAround[3] = d3;
            DiffAround[4] = d4;
        }
    }
 
    class GoodBall
    {
        private static DiscPropertiesInGoodBall[] GoodBallArr;
        private bool[] isFullPosition = new bool[13];
 
        // קביעת איך נראה כדור שלם: אילו מחומשים מקיפים כל מחומש
        // ומה ההפרש בין הצלע של המחומש החיצוני שנוגעת בנוכחי לבין אחד
        static GoodBall()
        {
            // (לצורך הפשטות למרות שיש רק 12 מחומשים המערך מכיל 13 כך שנוכל להתעלם מאפס)
            GoodBallArr = new DiscPropertiesInGoodBall[13];
 
            GoodBallArr[1] = new DiscPropertiesInGoodBall(4, 5, 6, 2, 3, // אינדקסים של המחומשים מסביב
                0, 0, 0, 0, 0);                                          // ההפרשים
            GoodBallArr[2] = new DiscPropertiesInGoodBall(1, 6, 11, 7, 3,
                3, 4, 2, 2, 1);
            GoodBallArr[3] = new DiscPropertiesInGoodBall(1, 2, 7, 8, 4,
                4, 4, 1, 4, 1);
            GoodBallArr[4] = new DiscPropertiesInGoodBall(1, 3, 8, 9, 5,
                0, 4, 3, 3, 1);
 
            GoodBallArr[5] = new DiscPropertiesInGoodBall(1, 4, 9, 10, 6,
                1, 4, 2, 3, 1);
            GoodBallArr[6] = new DiscPropertiesInGoodBall(1, 5, 10, 11, 2,
                2, 4, 2, 3, 1);
            GoodBallArr[7] = new DiscPropertiesInGoodBall(8, 3, 2, 11, 12,
                0, 2, 3, 1, 0);
 
            GoodBallArr[8] = new DiscPropertiesInGoodBall(7, 12, 9, 4, 3,
                0, 4, 4, 2, 3);
            GoodBallArr[9] = new DiscPropertiesInGoodBall(12, 10, 5, 4, 8,
                3, 4, 2, 3, 2);
 
            GoodBallArr[10] = new DiscPropertiesInGoodBall(12, 11, 6, 5, 9,
                2, 4, 2, 3, 1);
            GoodBallArr[11] = new DiscPropertiesInGoodBall(12, 7, 2, 6, 10,
                1, 3, 2, 3, 1);
            GoodBallArr[12] = new DiscPropertiesInGoodBall(7, 11, 10, 9, 8,
                4, 0, 0, 0, 1);
        }
 
        // זו הפונקציה העיקרית של התוכנית, בודקת האם המערך מייצג צורת כדור תקינה
        public bool IsDiscsSequenceGoodBall(DiscsSequence discsSeq)
        {
            // מתחילים תמיד כשהמחומש הראשון ברצף נחשב למחומש הראשון בצורת הכדור הנכונה
            int indOfCurrDiscInGoodShape = 1;
            int diffOfCurrDiscInGoodShape = 0;
            int nextRib;
 
            for (int i = 0; i < 12; i++)
            {
                // אם רצף המחומשים שקיבלנו הוביל לכך שמחומש אחד עולה על מחומש אחר
                if (isFullPosition[indOfCurrDiscInGoodShape])
                {
                    return false;
                }
 
                // נסמן שהמחומש הנוכחי כעת מלא
                isFullPosition[indOfCurrDiscInGoodShape] = true;
 
                // סביב כל מחומש יש חמישה מחומשים, המחומש שלנו יודע איזה מחומש צמוד
                // לכל צלע, נשאר רק לגלות לאיזו צלע החוט שלנו ממשיך
                nextRib = discsSeq.discsWithWire[i].InTry +
                          discsSeq.discsWithWire[i].OutTry +
                          DiscsSequence.DiscsProperties[i].OutNumber +
                          diffOfCurrDiscInGoodShape;
 
                // אם ההיסטים, צורת המחומש, והתוספת שצריך להוסיף בגלל צורת הכדור הובילו לכך
                // שהצלע שמפנה למחומש הבא חושבה בסיבוב של יותר מ360 מעלות - יש לחשב מחדש מה המיקום המעשי שלה
                // (הנוסחה הציקלית גם הייתה יכולה לפתור את הבעיה)
                if (nextRib > 5)
                {
                    nextRib -= 5;
                }
 
                // נחשב כבר עכשיו את 'התוספת' שצריך להוסיף למחומש הבא, היות וכל מחומש
                // יודע מה התוספת שלו על פי המחומש שקדם לו
                diffOfCurrDiscInGoodShape =
                    GoodBallArr[indOfCurrDiscInGoodShape].DiffAround[nextRib - 1];
 
                // נחשב את אינדקס המחומש הבא
                indOfCurrDiscInGoodShape =
                    GoodBallArr[indOfCurrDiscInGoodShape].IndexesOfDiscsAround[nextRib - 1];
            }
 
            return true;
        }
 
        public void Reset()
        {
            // Init the array to the default false value
            isFullPosition = new bool[13];
        }
    }
 
    class UsefulTools
    {
        public static void PrintArray(int[] Array)
        {
            foreach (int num in Array)
            {
                Console.Write(num + ", ");
            }
 
            Console.WriteLine();
            Console.WriteLine();
        }
    }
 
    class ArrayIncreaser
    {
        private int[] maxValueOfEachCell;
        private int[] arrayToIncrease;
 
        public ArrayIncreaser(int[] arrayToIncrease, params int[] maxValueOfEachCell)
        {
            if (arrayToIncrease == null)
            {
                throw new ArgumentException("arrayToIncrease can't be null");
            }
 
            if (arrayToIncrease.Length != maxValueOfEachCell.Length)
            {
                throw new ArgumentException(
                    "Both arrays must be the same length.\n" +
                    "Current len of arrayToIncrease:   " + arrayToIncrease.Length + "\n" +
                    "Current len of maxValueOfEachCell:" + maxValueOfEachCell.Length + "\n");
            }
 
            this.maxValueOfEachCell = maxValueOfEachCell;
            this.arrayToIncrease = arrayToIncrease;
        }
 
        public bool IncreaseArray()
        {
            int ind = arrayToIncrease.Length - 1;
 
            while (true)
            {
                if (ind < 0)
                {
                    return false;
                }
 
                if (arrayToIncrease[ind] == maxValueOfEachCell[ind])
                {
                    arrayToIncrease[ind] = 0;
                    ind--;
                }
                else
                {
                    arrayToIncrease[ind]++;
                    return true;
                }
            }
        }
    }
}





נקודה אחרונה:
לאלו שלא מחבבים שימוש בלולאת do-while, אפשר להגיע לאותן תוצאות גם עם for:

        static void Main(string[] args)
        {
            int[] attemtsArray = new int[24];
            DiscsSequence discsSequence = new DiscsSequence();
            ArrayIncreaser arrayIncreaser =
                new ArrayIncreaser(attemtsArray, DiscsSequence.GetArrayOfAllMaxValues());
            GoodBall goodBall = new GoodBall();
 
            int countOfGoodSolutions = 0;
            Console.WriteLine("Solving...");
 
            // לולאה שמנסה את כל הצירופים האפשריים
            for (bool canIncreaseArr = true;
                 canIncreaseArr;
                 canIncreaseArr = arrayIncreaser.IncreaseArray())
            {
                discsSequence.InitByArray(attemtsArray);
                goodBall.Reset();
 
                if (!goodBall.IsDiscsSequenceGoodBall(discsSequence))
                {
                    continue;
                }
 
                countOfGoodSolutions++;
                Console.WriteLine(countOfGoodSolutions + ".");
                UsefulTools.PrintArray(attemtsArray);
            };
 
            Console.WriteLine("Count: " + countOfGoodSolutions);
            Console.WriteLine("Goodbye.");
            Console.ReadLine();
        }








ראו גם: "Increase_the_array" בשפת C
מדידת הזמן שלוקח לקוד לרוץ ב#C

אין תגובות:

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