Advertisement
Guest User

SquareSwap4.cs

a guest
Oct 1st, 2021
177
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 17.99 KB | None | 0 0
  1. using System;
  2. using System.Linq;
  3. using System.Text;
  4. using System.Collections.Generic;
  5. using Dirichlet.Numerics;
  6.  
  7. // SquareSwap4.cs
  8. // used for a breadth-first exhaustive search for an answer at
  9. // https://puzzling.stackexchange.com/questions/111932/can-you-reactivate-a-4x4-magic-square
  10. // still "a work in progress" so not very tidied up...
  11. // compile with command line
  12. // csc SquareSwap4.cs UInt128.cs /r:System.Numerics.dll
  13. // you will need UInt128.cs from https://github.com/ricksladkey/dirichlet-numerics
  14. // see https://stackoverflow.com/questions/227731/int128-in-net
  15.  
  16.  
  17. public struct CellState : IEquatable<CellState>
  18. {
  19.   public byte Value { get; set; }
  20.   public byte MovesLeft { get; set; }
  21.  
  22.   public override bool Equals(object obj)
  23.   {
  24.     return obj is CellState other && Equals(other);
  25.   }
  26.  
  27.   public bool Equals(CellState other)
  28.   {
  29.     return Value == other.Value && MovesLeft == other.MovesLeft;
  30.   }
  31.  
  32.   public override int GetHashCode()
  33.   {
  34.     return Value * 256 + MovesLeft;
  35.   }
  36.  
  37.   public bool IsSwapValid(CellState other)
  38.   {
  39.     if (Value > other.Value)
  40.       return MovesLeft >= other.Value && other.MovesLeft > 0;
  41.     else
  42.       return other.MovesLeft >= Value && MovesLeft > 0;
  43.   }
  44. }
  45.  
  46.  
  47.  
  48.  
  49.  
  50. public class GridState : IEquatable<GridState>
  51. {
  52.   private static CellState[] MakeCellStates(params string[] initialRows)
  53.   {
  54.     int columnCount = initialRows[0].Length;
  55.     var result = new CellState[initialRows.Length * columnCount];
  56.     for (int row = 0; row < initialRows.Length; row++)
  57.     {
  58.       if (initialRows[row].Length != columnCount)
  59.         throw new ArgumentException("All rows must be the same length", nameof(initialRows));
  60.       for (int col = 0; col < columnCount; col++)
  61.       {
  62.         int value = _mainNumbers.IndexOf(initialRows[row][col]);
  63.         if (value < 0)
  64.           throw new ArgumentException("Row contained invalid character: " + initialRows[row]);
  65.  
  66.         result[row * columnCount + col] = new CellState { Value = (byte)value, MovesLeft = (byte)value };
  67.       }
  68.     }
  69.     return result;
  70.   }
  71.  
  72.   public GridState(params string[] initialRows)
  73.     : this(initialRows[0].Length, MakeCellStates(initialRows))
  74.   { }
  75.  
  76.   public GridState(int columnCount, CellState[] cellStates)
  77.   {
  78.     if (cellStates.Length % columnCount != 0)
  79.       throw new ArgumentOutOfRangeException();
  80.     ColumnCount = columnCount;
  81.     CellStates = cellStates;
  82.   }
  83.  
  84.   public int SetMagicConstant()
  85.   {
  86.     MagicConstant = 0;
  87.     if (RowCount == ColumnCount)
  88.     {
  89.       int totalValues = CellStates.Sum(c => (int)c.Value);
  90.       if (totalValues % ColumnCount == 0)
  91.         MagicConstant = totalValues / ColumnCount;
  92.     }
  93.     return MagicConstant;
  94.   }
  95.  
  96.   public int ColumnCount { get; }
  97.   public int RowCount => CellStates.Length / ColumnCount;
  98.  
  99.   private static int MagicConstant { get; set; }
  100.  
  101.   public CellState[] CellStates { get; }
  102.  
  103.   public CellState this[int row, int col]
  104.   {
  105.     get
  106.     {
  107.       if (row < 0 || row > RowCount)
  108.         throw new ArgumentOutOfRangeException(nameof(row));
  109.       if (col < 0 || col > ColumnCount)
  110.         throw new ArgumentOutOfRangeException(nameof(col));
  111.       return CellStates[row * ColumnCount + col];
  112.     }
  113.   }
  114.  
  115.   private static readonly string _mainNumbers      = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
  116.   private static readonly string _subscriptNumbers = "₀₁₂₃₄₅₆₇₈₉₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊₊";
  117.  
  118.   public override string ToString()
  119.   {
  120.     int rowLength = ColumnCount * 5;
  121.  
  122.     StringBuilder result = new StringBuilder(rowLength * RowCount * 2);
  123.  
  124.     for (int row = 0; row < RowCount; row++)
  125.     {
  126.       for (int col = 0; col < ColumnCount; col++)
  127.       {
  128.         var state = CellStates[row * ColumnCount + col];
  129.         result.Append(new[] { ' ', _mainNumbers[state.Value], _subscriptNumbers[state.MovesLeft], ' ', col < ColumnCount - 1 ? '|' : '\n' });
  130.       }
  131.       if (row < RowCount - 1)
  132.       {
  133.         result.Append(string.Join("+", Enumerable.Repeat("----", ColumnCount)));
  134.         result.Append("\n");
  135.       }
  136.     }
  137.     return result.ToString();
  138.   }
  139.  
  140.   public override bool Equals(object obj)
  141.   {
  142.     return base.Equals(obj) || obj is GridState other && Equals(other);
  143.   }
  144.  
  145.   public bool Equals(GridState other)
  146.   {
  147.     var otherCellStates = other.CellStates;
  148.     if (otherCellStates.Length != CellStates.Length)
  149.       return false;
  150.     for (int i = 0; i < CellStates.Length; i++)
  151.       if (!CellStates[i].Equals(otherCellStates[i]))
  152.         return false;
  153.     return true;
  154.   }
  155.  
  156.   public bool EqualValues(GridState other)
  157.   {
  158.     var otherCellStates = other.CellStates;
  159.     if (otherCellStates.Length != CellStates.Length)
  160.       return false;
  161.     for (int i = 0; i < CellStates.Length; i++)
  162.       if (!CellStates[i].Value.Equals(otherCellStates[i].Value))
  163.         return false;
  164.     return true;
  165.   }
  166.  
  167.   public override int GetHashCode()
  168.   {
  169.     int result = 0;
  170.     for (int i = 0; i < CellStates.Length; i++)
  171.       result = (result >> 18) ^ (result * 0x1413 + CellStates[i].GetHashCode());
  172.     return result;
  173.   }
  174.  
  175.   public int TotalMovesLeft => CellStates.Sum(c => c.MovesLeft);
  176.  
  177.   public GridState GetStateAfterSwap(SwapInfo swapInfo)
  178.   {
  179.     CellState stateFrom = this[swapInfo.rowFrom, swapInfo.colFrom];
  180.     CellState stateTo = this[swapInfo.rowTo, swapInfo.colTo];
  181.  
  182.     if (!stateFrom.IsSwapValid(stateTo))
  183.       throw new ArgumentOutOfRangeException(nameof(swapInfo), $"Invalid swap R{swapInfo.rowFrom + 1}C{swapInfo.colFrom + 1} with R{swapInfo.rowTo + 1}C{swapInfo.colTo + 1} for grid:\n{this.ToString()}");
  184.  
  185.     var cellStatesAfter = CellStates.ToArray();
  186.  
  187.     CellState stateFromAfter = new CellState { Value = (byte)stateTo.Value, MovesLeft = (byte)(stateTo.MovesLeft - (stateTo.Value > stateFrom.Value ? stateFrom.Value : 1)) };
  188.     CellState stateToAfter = new CellState { Value = (byte)stateFrom.Value, MovesLeft = (byte)(stateFrom.MovesLeft - (stateFrom.Value > stateTo.Value ? stateTo.Value : 1)) };
  189.  
  190.     cellStatesAfter[swapInfo.rowFrom * ColumnCount + swapInfo.colFrom] = stateFromAfter;
  191.     cellStatesAfter[swapInfo.rowTo * ColumnCount + swapInfo.colTo] = stateToAfter;
  192.  
  193.     return new GridState(ColumnCount, cellStatesAfter);
  194.   }
  195.  
  196.   public bool IsSwapValid(SwapInfo info) => this[info.rowFrom, info.colFrom].IsSwapValid(this[info.rowTo, info.colTo]);
  197.  
  198.   public bool IsOrdered()
  199.   {
  200.     for (int i = 0; i < CellStates.Length - 1; i++)
  201.       if (CellStates[i].Value > CellStates[i + 1].Value)
  202.         return false;
  203.     return true;
  204.   }
  205.  
  206.   public bool IsMagic()
  207.   {
  208.     if (MagicConstant == 0)
  209.       throw new InvalidOperationException("Cannot be magic - wrong dimensions or total...");
  210.     int count = ColumnCount;
  211.     return Enumerable.Range(0, count).Sum(i => (int)this[i, i].Value) == MagicConstant &&
  212.            Enumerable.Range(0, count).Sum(i => (int)this[count - 1 - i, i].Value) == MagicConstant &&
  213.            Enumerable.Range(0, count).All(i => Enumerable.Range(0, count).Sum(col => (int)this[i, col].Value) == MagicConstant &&
  214.                                                Enumerable.Range(0, count).Sum(row => (int)this[row, i].Value) == MagicConstant);
  215.   }
  216. }
  217.  
  218.  
  219. public struct SwapInfo : IEquatable<SwapInfo>
  220. {
  221.   public byte rowFrom { get; set; }
  222.   public byte colFrom { get; set; }
  223.   public byte rowTo { get; set; }
  224.   public byte colTo { get; set; }
  225.  
  226.   public override bool Equals(object obj)
  227.   {
  228.     return obj is SwapInfo other && Equals(other);
  229.   }
  230.  
  231.   public bool Equals(SwapInfo other)
  232.   {
  233.     return rowFrom == other.rowFrom && colFrom == other.colFrom && rowTo == other.rowTo && colTo == other.colTo ||
  234.            rowFrom == other.rowTo && colFrom == other.colTo && rowTo == other.rowFrom && colTo == other.colFrom;
  235.   }
  236.  
  237.   public override int GetHashCode()
  238.   {
  239.     return Math.Min(rowFrom, rowTo) + Math.Max(rowFrom, rowTo) * 64 + Math.Min(colFrom, colTo) * 4096 + Math.Max(colFrom, colTo) * 262144;
  240.   }
  241. }
  242.  
  243.  
  244. public class PuzzleState
  245. {
  246.   private PuzzleState(GridState gridState, params SwapInfo[] movesDone)
  247.   {
  248.     GridState = gridState;
  249.     MovesDone = movesDone;
  250.   }
  251.  
  252.   public GridState GridState { get; private set; }
  253.  
  254.   public PuzzleState(params string[] lines)
  255.     : this(new GridState(lines))
  256.   {
  257.   }
  258.  
  259.   public PuzzleState(CompressedPuzzleState compressed, GridState gridState)
  260.     : this(gridState, compressed.MovesDone)
  261.   { }
  262.  
  263.   // parent and move count were never used, so removed these.
  264.  
  265.   public SwapInfo[] MovesDone { get; }
  266.  
  267.   private static SwapInfo[] ConcatSwapInfo(PuzzleState parent, SwapInfo move)
  268.   {
  269.     int parentLength = parent.MovesDone.Length;
  270.     var result = new SwapInfo[parentLength + 1];
  271.     Array.Copy(parent.MovesDone, result, parentLength);
  272.     result[parentLength] = move;
  273.     return result;
  274.   }
  275.  
  276.   private PuzzleState(PuzzleState parent, SwapInfo move, GridState gridState)
  277.     : this(gridState, ConcatSwapInfo(parent,move))
  278.   {
  279.     // bug in previous version before major refactoring left out this (now redundant!) line
  280.     AllowWrapAround = parent.AllowWrapAround;
  281.   }
  282.  
  283.   public bool IsSwapValid(SwapInfo info) => GridState.IsSwapValid(info);
  284.  
  285.   public bool AllowWrapAround { get; set; } = true;
  286.  
  287.   public IEnumerable<PuzzleState> GetStatesAfterValidMoves()
  288.   {
  289.     int fromRowLimit = AllowWrapAround ? GridState.RowCount : (GridState.RowCount - 1);
  290.     int fromColLimit = AllowWrapAround ? GridState.ColumnCount : (GridState.ColumnCount - 1);
  291.     for (int fromRow = 0; fromRow < fromRowLimit; fromRow++)
  292.     {
  293.       int toNextRow = (fromRow + 1) % GridState.RowCount;
  294.       for (int fromCol = 0; fromCol < fromColLimit; fromCol++)
  295.       {
  296.         CellState fromState = GridState[fromRow, fromCol];
  297.         if (fromState.MovesLeft < 1)
  298.           continue;
  299.  
  300.         CellState toState = GridState[toNextRow, fromCol];
  301.         if (fromState.IsSwapValid(toState))
  302.         {
  303.           var swapInfo = new SwapInfo { colFrom = (byte)fromCol, rowFrom = (byte)fromRow, colTo = (byte)fromCol, rowTo = (byte)toNextRow };
  304.           yield return new PuzzleState(this, swapInfo, GridState.GetStateAfterSwap(swapInfo));
  305.         }
  306.  
  307.         int toNextCol = (fromCol + 1) % GridState.ColumnCount;
  308.         toState = GridState[fromRow, toNextCol];
  309.         if (fromState.IsSwapValid(toState))
  310.         {
  311.           var swapInfo = new SwapInfo { colFrom = (byte)fromCol, rowFrom = (byte)fromRow, colTo = (byte)toNextCol, rowTo = (byte)fromRow };
  312.           yield return new PuzzleState(this, swapInfo, GridState.GetStateAfterSwap(swapInfo));
  313.         }
  314.       }
  315.     }
  316.   }
  317.  
  318. //  static GridState targetState = new GridState("123", "456", "789");
  319.  
  320.   public bool IsTargetState => GridState.IsMagic();
  321.  
  322.   public IEnumerable<SwapInfo> GetSwaps()
  323.   {
  324.     return MovesDone;
  325.   }
  326. }
  327.  
  328. public struct CompressedGridState4 : IEquatable<CompressedGridState4>
  329. {
  330.   public static implicit operator CompressedGridState4(GridState source) => new CompressedGridState4(source);
  331.   public static implicit operator GridState(CompressedGridState4 source) => source.GetGridState();
  332.  
  333.   // thoretically, we only "need" a 96 bit integer, but code for that seems unlikely to be much of an improvement...
  334.   // limit is based on 16! * 17!
  335.   UInt128 _compressedData;
  336.  
  337.   public CompressedGridState4(GridState source)
  338.   {
  339.     if (source.ColumnCount != 4 || source.CellStates.Length != 16)
  340.       throw new InvalidOperationException("This compression function assumes a 4 x 4 grid containing precisely the numbers 1-16");
  341.  
  342.     var cellStates = source.CellStates;
  343.     UInt128 multiplier = 1;
  344.     _compressedData = 0;
  345.  
  346.     List<int> possiblePositions = Enumerable.Range(0, 16).ToList();
  347.  
  348.     for (uint i = 1; i <= 16; i++)
  349.     {
  350.       // find index of value i
  351.       int pos = 0;
  352.       while (cellStates[pos].Value != i)
  353.       {
  354.         if (pos >= cellStates.Length - 1)
  355.           throw new InvalidOperationException($"Value {i} not found in grid state");
  356.         else
  357.           pos++;
  358.       }
  359.       int posIndex = possiblePositions.IndexOf(pos);
  360.       if (posIndex == -1)
  361.         throw new InvalidOperationException($"Value {i} found at position {pos} which isn't possible! Possible: [{string.Join(",", possiblePositions)}]");
  362.  
  363.       _compressedData += multiplier * (uint)posIndex;
  364.       multiplier *= (uint)possiblePositions.Count;
  365.  
  366.       possiblePositions.RemoveAt(posIndex);
  367.  
  368.       _compressedData += multiplier * (uint)cellStates[pos].MovesLeft;
  369.       multiplier *= i + 1;
  370.  
  371.       //Console.WriteLine($"Compression diagnostic: i={i}, pos={pos} index {posIndex}, moves = {cellStates[pos].MovesLeft}, multiplier = {multiplier}, data = {_compressedData}");
  372.     }
  373.   }
  374.  
  375.   public GridState GetGridState()
  376.   {
  377.     var cellStates = new CellState[16];
  378.     UInt128 multiplier = 1;
  379.  
  380.     List<int> possiblePositions = Enumerable.Range(0, 16).ToList();
  381.  
  382.     for (byte i = 1; i <= 16; i++)
  383.     {
  384.       UInt128 nextMultiplier = multiplier * (uint)possiblePositions.Count;
  385.       int posIndex = (int)(_compressedData % nextMultiplier / multiplier);
  386.       int pos = possiblePositions[posIndex];
  387.       possiblePositions.RemoveAt(posIndex);
  388.  
  389.       multiplier = nextMultiplier;
  390.       nextMultiplier *= (uint)i + 1;
  391.  
  392.       byte movesLeft = (byte)(_compressedData % nextMultiplier / multiplier);
  393.       multiplier = nextMultiplier;
  394.  
  395.       cellStates[pos] = new CellState { Value = i, MovesLeft = movesLeft };
  396.  
  397.       //Console.WriteLine($"Decompression diagnostic: i={i}, pos={pos} index {posIndex}, moves = {cellStates[pos].MovesLeft}, multiplier = {multiplier}, data = {_compressedData}");
  398.     }
  399.  
  400.     return new GridState(4, cellStates);
  401.   }
  402.  
  403.   public override bool Equals(object obj)
  404.   {
  405.     return base.Equals(obj) || obj is CompressedGridState4 other && Equals(other) || obj is GridState otherExpanded && GetGridState().Equals(otherExpanded);
  406.   }
  407.  
  408.   public bool Equals(CompressedGridState4 other)
  409.   {
  410.     return _compressedData == other._compressedData;
  411.   }
  412.  
  413.   public override int GetHashCode()
  414.   {
  415.     return _compressedData.GetHashCode();
  416.   }
  417.  
  418. }
  419.  
  420.  
  421. public struct CompressedPuzzleState
  422. {
  423.   public static implicit operator CompressedPuzzleState(PuzzleState source) => new CompressedPuzzleState(source);
  424.  
  425.   public CompressedPuzzleState(PuzzleState source)
  426.   {
  427.     MovesDone = source.MovesDone;
  428.   }
  429.  
  430.   public PuzzleState GetPuzzleState(GridState gridState) => new PuzzleState(this, gridState) { AllowWrapAround = false };
  431.  
  432.   // currently not (further) compressed. That can be next step if existing changes are insufficient.
  433.   public SwapInfo[] MovesDone { get; }
  434.  
  435. }
  436.  
  437.  
  438. public class SquareSwap
  439. {
  440.   static public void Main()
  441.   {
  442.     Console.OutputEncoding = System.Text.Encoding.UTF8;
  443.  
  444.     var startingState = new PuzzleState("867G", "143A", "9D2E", "BC5F") { AllowWrapAround = false };
  445.  
  446. //    var startingState = new PuzzleState("1FE4", "BA58", "G69C", "732D") { AllowWrapAround = false }; // test starting state similar to "given" magic square
  447.  
  448. //    var startingState = new PuzzleState("1FE4", "AB85", "769C", "G32D") { AllowWrapAround = false }; // test starting state similar to "given" magic square
  449.  
  450.  
  451.     var magicConstant = startingState.GridState.SetMagicConstant();
  452.  
  453.     Console.WriteLine($"Starting state [total moves left: {startingState.GridState.TotalMovesLeft}, magic constant = {magicConstant}]:");
  454.     Console.WriteLine(startingState.GridState);
  455.  
  456.     // test case for compression and decompression functions.
  457.     CompressedGridState4 comp = startingState.GridState;
  458.     Console.WriteLine($"Equality check: {comp.Equals((object)startingState.GridState)}");
  459.  
  460.     var workingSets = Enumerable.Range(0, startingState.GridState.TotalMovesLeft + 1).Select(i => new Dictionary<CompressedGridState4, CompressedPuzzleState>()).ToArray();
  461.  
  462.     workingSets[startingState.GridState.TotalMovesLeft].Add(startingState.GridState, startingState);
  463.  
  464.     bool found = false;
  465.  
  466.     for (int i = workingSets.Length-1; i > 0 /*&& !found*/; i--)
  467.     {
  468.       Console.WriteLine($"Starting on working set #{i}, which has {workingSets[i].Count} members... [{Enumerable.Range(0,i).Sum(x => workingSets[x].Count)} already in later working sets]");
  469.  
  470.       System.Threading.Tasks.Parallel.ForEach(workingSets[i], pair =>
  471.       {
  472.         GridState gridState = pair.Key; // implicit conversion to decompress
  473.         var puzzleState = pair.Value.GetPuzzleState(gridState);
  474.         // non-compressed puzzle state is now only scoped to this block.
  475.  
  476.         if (puzzleState.IsTargetState)
  477.         {
  478.           found = true;
  479.           lock (workingSets[i])
  480.           {
  481.             Console.WriteLine("Found solution!");
  482.             Console.WriteLine($"... after swaps: {string.Join(", ", puzzleState.GetSwaps().Select(s => $"R{s.rowFrom + 1}C{s.colFrom + 1}<>R{s.rowTo + 1}C{s.colTo + 1}"))}");
  483.             Console.WriteLine(puzzleState.GridState);
  484.           }
  485.         }
  486.         else
  487.           foreach (var nextState in puzzleState.GetStatesAfterValidMoves())
  488.           {
  489.             int movesLeft = nextState.GridState.TotalMovesLeft;
  490.             //var swapInfo = nextState.MovesDone.Last();
  491.             //Console.WriteLine($"Possible next state after swapping R{swapInfo.rowFrom + 1}C{swapInfo.colFrom + 1} with R{swapInfo.rowTo + 1}C{swapInfo.colTo + 1} [total moves left: {movesLeft}]:");
  492.             //Console.WriteLine(nextState.GridState);
  493.  
  494.             if (movesLeft >= i)
  495.               throw new InvalidOperationException("Total moves left didn't decrease???!");
  496.  
  497.             CompressedGridState4 nextGridStateCompressed = nextState.GridState;
  498.  
  499.             lock (workingSets[movesLeft])
  500.             {
  501.               if (workingSets[movesLeft].TryGetValue(nextGridStateCompressed, out var firstEquivalentState))
  502.               {
  503.                 //Console.WriteLine("Duplicate found!");
  504.               }
  505.               else // implicit conversion to compressed data.
  506.                 workingSets[movesLeft].Add(nextGridStateCompressed, nextState);
  507.             }
  508.           }
  509.       });
  510.       // allow dictionaries of compresed state to be reclaimed by garbage collector
  511.       workingSets[i] = null;
  512.     }
  513.   }
  514. }
  515.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement