Guest User

GuessGame3.cs

a guest
Jun 23rd, 2020
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.90 KB | None | 0 0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5.  
  6. namespace GuessGame
  7. {
  8.   public static class Problem
  9.   {
  10.     public interface IValidGuessList : IReadOnlyList<int>
  11.     {
  12.       IValidGuessList GetBefore(int index);
  13.       IValidGuessList GetAfter(int index);
  14.     }
  15.  
  16.     public class EmptyRange : IValidGuessList
  17.     {
  18.       private EmptyRange() { }
  19.       static public EmptyRange Instance = new EmptyRange();
  20.  
  21.       public int Count => 0;
  22.       public int this[int index] => throw new IndexOutOfRangeException();
  23.       public IValidGuessList GetBefore(int index) => this;
  24.       public IValidGuessList GetAfter(int index) => this;
  25.  
  26.       public IEnumerator<int> GetEnumerator() => Enumerable.Empty<int>().GetEnumerator();
  27.       IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  28.     }
  29.  
  30.     public class SingleRange : IValidGuessList
  31.     {
  32.       public SingleRange(int start, int end)
  33.       {
  34.         Start = start;
  35.         End = end;
  36.       }
  37.  
  38.       int Start { get; }
  39.       int End { get; }
  40.  
  41.       public int Count => 1 + End - Start;
  42.  
  43.       public int this[int index] => (index >= 0 && index <= End - Start) ? Start + index : throw new IndexOutOfRangeException();
  44.  
  45.       public IValidGuessList GetBefore(int index)
  46.       {
  47.         if (index <= 0)
  48.           return EmptyRange.Instance;
  49.         if (index >= Count)
  50.           return this;
  51.         return new SingleRange(Start, Start + index - 1);
  52.       }
  53.  
  54.       public IValidGuessList GetAfter(int index)
  55.       {
  56.         if (index >= End - Start)
  57.           return EmptyRange.Instance;
  58.         if (index < 0)
  59.           return this;
  60.         return new SingleRange(Start + index + 1, End);
  61.       }
  62.  
  63.  
  64.       public IEnumerator<int> GetEnumerator() => Enumerable.Range(Start, Count).GetEnumerator();
  65.  
  66.       IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  67.     }
  68.  
  69.     public class JoinedRange : IValidGuessList
  70.     {
  71.       public JoinedRange(IValidGuessList first, IValidGuessList second)
  72.       {
  73.         First = first;
  74.         Second = second;
  75.         Count = first.Count + second.Count;
  76.       }
  77.  
  78.       IValidGuessList First { get; }
  79.       IValidGuessList Second { get; }
  80.  
  81.       public int Count { get; }
  82.  
  83.       public int this[int index] => index < First.Count ? First[index] : Second[index - First.Count];
  84.       public IValidGuessList GetBefore(int index)
  85.       {
  86.         if (index <= First.Count)
  87.           return First.GetBefore(index);
  88.         else if (index >= Count)
  89.           return this;
  90.         return new JoinedRange(First, Second.GetBefore(index - First.Count));
  91.       }
  92.  
  93.       public IValidGuessList GetAfter(int index)
  94.       {
  95.         if (index >= First.Count - 1)
  96.           return Second.GetAfter(index - First.Count);
  97.         if (index < 0)
  98.           return this;
  99.         return new JoinedRange(First.GetAfter(index), Second);
  100.       }
  101.  
  102.       public IEnumerator<int> GetEnumerator() => First.Concat(Second).GetEnumerator();
  103.       IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  104.     }
  105.  
  106.  
  107.     public struct WorstCase
  108.     {
  109.       public static WorstCase CombineSubTrees(WorstCase left, WorstCase right)
  110.       {
  111.         return new WorstCase
  112.         {
  113.           MinDepth = Math.Min(left.MinDepth, right.MinDepth) + 1,
  114.           MaxDepth = Math.Max(left.MaxDepth, right.MaxDepth) + 1,
  115.           TreeCount = left.TreeCount + right.TreeCount + 1,
  116.         };
  117.       }
  118.       public WorstCase AddLevel()
  119.       {
  120.         return new WorstCase { MinDepth = MinDepth + 1, MaxDepth = MaxDepth + 1, TreeCount = TreeCount + 1 };
  121.       }
  122.       internal int MinDepth { get; private set; }
  123.       internal int MaxDepth { get; private set; }
  124.       internal Int64 TreeCount { get; private set; }
  125.  
  126.       internal static WorstCase Leaf1 = new WorstCase() { TreeCount = 1 };
  127.       internal static WorstCase Leaf2 = new WorstCase() { MaxDepth = 1, TreeCount = 2 };
  128.  
  129.     }
  130.  
  131.  
  132.     static Dictionary<int, WorstCase> allTrueResultsDiary = new Dictionary<int, WorstCase>();
  133.  
  134.     static Dictionary<int, Dictionary<int, WorstCase>> resultsDiary = new Dictionary<int, Dictionary<int, WorstCase>>();
  135.  
  136.  
  137.     public static WorstCase CheckState(IValidGuessList lowerList, IValidGuessList higherList, bool saidHigher, int depth)
  138.     {
  139.       int lowerCount = lowerList.Count();
  140.       int higherCount = higherList.Count();
  141.  
  142.       if (lowerCount + higherCount < 3)
  143.  
  144.       // deal with leaf cases - no better strategy than to guess each remaining number.
  145.       switch(lowerCount + higherCount)
  146.       {
  147.         case 0: throw new Exception("Alice CHEATED!  (or maybe there's a bug in my program...)");
  148.         case 1: return WorstCase.Leaf1;
  149. //        case 2: return WorstCase.Leaf2;
  150.       }
  151.  
  152.       // special case "leaf" nodes dealt with...
  153.  
  154.       var trueList = saidHigher ? higherList : lowerList;
  155.       var falseList = saidHigher ? lowerList : higherList;
  156.       var trueCount = saidHigher ? higherCount : lowerCount;
  157.       var falseCount = saidHigher ? lowerCount : higherCount;
  158.  
  159.       WorstCase result;
  160.  
  161.       // although we do the checks based on actual lists of numbers,
  162.       // the result only depends on the counts of these,
  163.       // so we can diarise the result, and avoid another recursive search
  164.       // the next time we're called with equivalent parameters.
  165.  
  166.       if (resultsDiary.TryGetValue(trueCount, out var resultDict))
  167.       {
  168.         if (resultDict.TryGetValue(falseCount, out result))
  169.           return result;
  170.       }
  171.       else
  172.         resultsDiary.Add(trueCount, resultDict = new Dictionary<int, WorstCase>());
  173.  
  174.  
  175.       WorstCase higherResult, lowerResult;
  176.  
  177.       if (falseCount == 0)
  178.       {
  179.         // nothing can be eliminated this turn.
  180.         // just guess the median value, and process the results for all values except the one guessed.
  181.  
  182.         int guessIndex = (lowerCount + higherCount) / 2;
  183.         var lowerThanGuess = trueList.GetBefore(guessIndex);
  184.         var higherThanGuess = trueList.GetAfter(guessIndex);
  185.  
  186.         higherResult = CheckState(lowerThanGuess, higherThanGuess, true, depth + 1);
  187.         lowerResult = CheckState(lowerThanGuess, higherThanGuess, false, depth + 1);
  188.  
  189.         result = WorstCase.CombineSubTrees(lowerResult, higherResult);
  190.         resultDict.Add(falseCount, result);
  191.         return result;
  192.       }
  193.       else if (falseCount + trueCount == 3 && trueCount > 0)
  194.       {
  195.         // guess the middle number (even if it would be in trueList)
  196.         var lowerThanGuess = lowerList.GetBefore(1);
  197.         var higherThanGuess = higherList.GetAfter(higherList.Count - 2);
  198.  
  199.         higherResult = CheckState(saidHigher ? EmptyRange.Instance : lowerThanGuess, higherThanGuess, true, depth + 1);
  200.         lowerResult = CheckState(lowerThanGuess, saidHigher ? higherThanGuess : EmptyRange.Instance, false, depth + 1);
  201.       }
  202.       else if (falseCount < trueCount - 1)
  203.         throw new Exception($"I think we've got some dodgy guesswork here! {falseCount}<{trueCount}");
  204.       else
  205.       {
  206.         int doubleFalseCount = (int)(trueCount*0.191 + falseCount*0.5);
  207.         if (doubleFalseCount >= falseCount)
  208.           throw new Exception("I can't guess the number I want!");
  209.  
  210.         int initialGuess = saidHigher ? doubleFalseCount : (falseCount - doubleFalseCount - 1);
  211.         int nextGuess = initialGuess;
  212.         int guessIndex;
  213.  
  214.         int prevGuess = -1;
  215.         WorstCase? initialResult = null;
  216.         WorstCase? prevResult = null;
  217.  
  218.         while (true)
  219.         {
  220.           guessIndex = nextGuess;
  221.           var lowerThanGuess = falseList.GetBefore(guessIndex);
  222.           var higherThanGuess = falseList.GetAfter(guessIndex);
  223.  
  224.           if (saidHigher)
  225.           {
  226.             // said higher last time, so if said higher again, lowerThanGuess is eliminated as a double-false
  227.             higherResult = CheckState(EmptyRange.Instance, new JoinedRange(higherThanGuess, trueList), true, depth + 1);
  228.  
  229.             // if said lower, values between the two guesses are eliminated.
  230.             lowerResult = CheckState(lowerThanGuess, trueList, false, depth + 1);
  231.           }
  232.           else
  233.           {
  234.             // if said higher, values between the two guesses are eliminated.
  235.             higherResult = CheckState(trueList, higherThanGuess, true, depth + 1);
  236.  
  237.             // said lower last time, so if said lower again, lowerThanGuess is eliminated as a double-false
  238.             lowerResult = CheckState(new JoinedRange(trueList, lowerThanGuess), EmptyRange.Instance, false, depth + 1);
  239.           }
  240.           result = WorstCase.CombineSubTrees(lowerResult, higherResult);
  241.           if (lowerResult.MaxDepth == higherResult.MaxDepth)
  242.             break;
  243.           else if (lowerResult.MaxDepth > higherResult.MaxDepth)
  244.           {
  245.             nextGuess = guessIndex - 1;
  246.             if (nextGuess < 1)
  247.               break;
  248.           }
  249.           else
  250.           {
  251.             nextGuess = guessIndex + 1;
  252.             if (nextGuess >= falseCount)
  253.               break;
  254.           }
  255.           if (nextGuess == prevGuess)
  256.           {
  257.             if (prevResult?.MaxDepth <= result.MaxDepth)
  258.             {
  259.               result = prevResult.Value;
  260.               guessIndex = prevGuess;
  261.             }
  262.             break;
  263.           }
  264.           prevGuess = guessIndex;
  265.           prevResult = result;
  266.           if (guessIndex == initialGuess)
  267.             initialResult = result;
  268.           break; // << remove this line to search for better solutions.
  269.         }
  270.         if(initialResult?.MaxDepth <= result.MaxDepth)
  271.         {
  272.           result = initialResult.Value;
  273.         }
  274.         else if ((guessIndex > initialGuess + 1 || guessIndex < initialGuess - 1) && saidHigher)
  275.           Console.WriteLine($"{trueCount},{falseCount} => adjust {initialGuess} to {guessIndex}, improved MaxDepth {initialResult?.MaxDepth} to {result.MaxDepth}");
  276.         resultDict.Add(falseCount, result);
  277.         return result;
  278.       }
  279.       result = WorstCase.CombineSubTrees(lowerResult, higherResult);
  280.       resultDict.Add(falseCount, result);
  281.       return result;
  282.     }
  283.  
  284.  
  285.     static public void Main()
  286.     {
  287.       int step = 1;
  288.       // can change loop to search for more values of n (but taking longer to reach higher values of n)
  289. //      for(int i=1;i<= 10000000;i+=step)
  290.       foreach(int i in new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 1000000, 10000000, 100000000, 1000000000 })
  291.       {
  292.         var result = CheckState(new SingleRange(1, i), EmptyRange.Instance, false, 1);
  293.         //        var result = CheckState(Enumerable.Range(1, i), Enumerable.Empty<int>(), false, 1);
  294.         if (i.ToString().ToCharArray().Skip(2).All(c => c == '0'))
  295.         {
  296.           Console.WriteLine($"Result for {i} items: MinDepth:{result.MinDepth}, MaxDepth:{result.MaxDepth}, TreeCount:{result.TreeCount}");
  297.           if (step <= i / 1000)
  298.             step *= 10;
  299.         }
  300.       }
  301.     }
  302.   }
  303. }
Add Comment
Please, Sign In to add comment