即使这是我第一次编写这种“大”的东西,也感觉我非常了解C#(毕竟它与Java非常相似)。也很高兴学习LINQ,这些功能给我留下了深刻的印象(就像Java 8中的Steams一样),也许我在这里过度使用了它(如果可能的话)。

类摘要


SudokuFactory:包含创建一些Sudoku变体的静态方法
SudokuBoard:包含SudokuRuleSudokuTile的集合

SudokuRule:是否框,一行,一行或完全不同的内容都没有关系。包含必须唯一的SudokuTile的集合。
SudokuTile:拼图中的每个图块。可以被“阻塞”(就像拼图中的孔一样),记住它是possibleValues,并且还包含一个值(0用于没有值的图块)
SudokuProgress:用于了解求解步骤的进度。
程序:主要起点。包含针对七个不同数独的测试。已经验证所有问题都可以正确解决。

由于这是我第一次使用C#和LINQ,请告诉我任何事情。欢迎所有建议。除了方法box应该称为Box之外。在简化一些LINQ用法的情况下,我会特别感兴趣(相信我,有很多事情)。我希望您能够遵循所有LINQ查询。我试图在需要的地方加一些简短的注释,以解释正在发生的事情。如果您想对某个部分进行解释,请发表评论,然后我会进行解释。

和往常一样,我倾向于将挑战化为超级灵活的事物,并需要更多或更多的支持。减少不必要的事情。这段代码可以解决的一些难题:通过反复试验)
Nonomino
超级数独
武士数独
任意大小,具有任意数量的盒子和盒子大小的经典数独游戏(仅在3x3盒子的9x9和2x2盒子的4x4上经过完全测试,但任何尺寸都可以)

这些图像是在以下代码中进行了测试和解决:



实现中的一个已知问题是,如果您输入一个空的难题,那么它将持续数年的工作时间。找到适合它的所有可能组合。

SudokuProgress

public enum SudokuProgress { FAILED, NO_PROGRESS, PROGRESS }


SudokuTile

public class SudokuTile
{
    internal static SudokuProgress CombineSolvedState(SudokuProgress a, SudokuProgress b)
    {
        if (a == SudokuProgress.FAILED)
            return a;
        if (a == SudokuProgress.NO_PROGRESS)
            return b;
        if (a == SudokuProgress.PROGRESS)
            return b == SudokuProgress.FAILED ? b : a;
        throw new InvalidOperationException("Invalid value for a");
    }

    public const int CLEARED = 0;
    private int _maxValue;
    private int _value;
    private int _x;
    private int _y;
    private ISet<int> possibleValues;
    private bool _blocked;

    public SudokuTile(int x, int y, int maxValue)
    {
        _x = x;
        _y = y;
        _blocked = false;
        _maxValue = maxValue;
        possibleValues = new HashSet<int>();
        _value = 0;
    }

    public int Value
    {
        get { return _value; }
        set
        {
            if (value > _maxValue)
                throw new ArgumentOutOfRangeException("SudokuTile Value cannot be greater than " + _maxValue.ToString() + ". Was " + value);
            if (value < CLEARED)
                throw new ArgumentOutOfRangeException("SudokuTile Value cannot be zero or smaller. Was " + value);
            _value = value;
        }
    }

    public bool HasValue 
    {
        get { return Value != CLEARED; }
    }

    public string ToStringSimple()
    {
        return Value.ToString();
    }

    public override string ToString()
    {
        return String.Format("Value {0} at pos {1}, {2}. ", Value, _x, _y, possibleValues.Count);
    }

    internal void ResetPossibles()
    {
        possibleValues.Clear();
        foreach (int i in Enumerable.Range(1, _maxValue))
        {
            if (!HasValue || Value == i)
                possibleValues.Add(i);
        }
    }

    public void Block()
    {
        _blocked = true;
    }
    internal void Fix(int value, string reason) 
    {
        Console.WriteLine("Fixing {0} on pos {1}, {2}: {3}", value, _x, _y, reason);
        Value = value;
        ResetPossibles();
    }
    internal SudokuProgress RemovePossibles(IEnumerable<int> existingNumbers)
    {
        if (_blocked)
            return SudokuProgress.NO_PROGRESS;
        // Takes the current possible values and removes the ones existing in `existingNumbers`
        possibleValues = new HashSet<int>(possibleValues.Where(x => !existingNumbers.Contains(x)));
        SudokuProgress result = SudokuProgress.NO_PROGRESS;
        if (possibleValues.Count == 1)
        {
            Fix(possibleValues.First(), "Only one possibility");
            result = SudokuProgress.PROGRESS;
        }
        if (possibleValues.Count == 0)
            return SudokuProgress.FAILED;
        return result;
    }

    public bool IsValuePossible(int i) 
    {
        return possibleValues.Contains(i);
    }

    public int X { get { return _x; } }
    public int Y { get { return _y; } }
    public bool IsBlocked { get { return _blocked; } } // A blocked field can not contain a value -- used for creating 'holes' in the map
    public int PossibleCount 
    {
        get {
            return IsBlocked ? 1 : possibleValues.Count; 
        } 
    }
}


SudokuRule

public class SudokuRule : IEnumerable<SudokuTile>
{
    internal SudokuRule(IEnumerable<SudokuTile> tiles, string description)
    {
        _tiles = new HashSet<SudokuTile>(tiles);
        _description = description;
    }

    private ISet<SudokuTile> _tiles;
    private string _description;

    public bool CheckValid()
    {
        var filtered = _tiles.Where(tile => tile.HasValue);
        var groupedByValue = filtered.GroupBy(tile => tile.Value);
        return groupedByValue.All(group => group.Count() == 1);
    }
    public bool CheckComplete()
    {
        return _tiles.All(tile => tile.HasValue) && CheckValid();
    }

    internal SudokuProgress RemovePossibles()
    {
        // Tiles that has a number already
        IEnumerable<SudokuTile> withNumber = _tiles.Where(tile => tile.HasValue);

        // Tiles without a number
        IEnumerable<SudokuTile> withoutNumber = _tiles.Where(tile => !tile.HasValue);

        // The existing numbers in this rule
        IEnumerable<int> existingNumbers = new HashSet<int>(withNumber.Select(tile => tile.Value).Distinct().ToList());

        SudokuProgress result = SudokuProgress.NO_PROGRESS;
        foreach (SudokuTile tile in withoutNumber)
            result = SudokuTile.CombineSolvedState(result, tile.RemovePossibles(existingNumbers));
        return result;
    }
    internal SudokuProgress CheckForOnlyOnePossibility() 
    {
        // Check if there is only one number within this rule that can have a specific value
        IList<int> existingNumbers = _tiles.Select(tile => tile.Value).Distinct().ToList();
        SudokuProgress result = SudokuProgress.NO_PROGRESS;

        foreach (int value in Enumerable.Range(1, _tiles.Count))
        {
            if (existingNumbers.Contains(value)) // this rule already has the value, skip checking for it
                continue;
            var possibles = _tiles.Where(tile => !tile.HasValue && tile.IsValuePossible(value)).ToList();
            if (possibles.Count == 0)
                return SudokuProgress.FAILED;
            if (possibles.Count == 1)
            {
                possibles.First().Fix(value, "Only possible in rule " + ToString());
                result = SudokuProgress.PROGRESS;
            }
        }
        return result;
    }

    internal SudokuProgress Solve()
    {
        // If both are null, return null (indicating no change). If one is null, return the other. Else return result1 && result2
        SudokuProgress result1 = RemovePossibles();
        SudokuProgress result2 = CheckForOnlyOnePossibility();
        return SudokuTile.CombineSolvedState(result1, result2);
    }

    public override string ToString()
    {
        return _description;
    }

    public IEnumerator<SudokuTile> GetEnumerator()
    {
        return _tiles.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public string Description { get { return _description; } }
}


SudokuBoard:

public class SudokuBoard
{
    public SudokuBoard(SudokuBoard copy)
    {
        _maxValue = copy._maxValue;
        tiles = new SudokuTile[copy.Width, copy.Height];
        CreateTiles();
        // Copy the tile values
        foreach (var pos in SudokuFactory.box(Width, Height))
        {
            tiles[pos.Item1, pos.Item2] = new SudokuTile(pos.Item1, pos.Item2, _maxValue);
            tiles[pos.Item1, pos.Item2].Value = copy.tiles[pos.Item1, pos.Item2].Value;
        }

        // Copy the rules
        foreach (SudokuRule rule in copy.rules) 
        {
            var ruleTiles = new HashSet<SudokuTile>();
            foreach (SudokuTile tile in rule) 
            {
                ruleTiles.Add(tiles[tile.X, tile.Y]);
            }
            rules.Add(new SudokuRule(ruleTiles, rule.Description));
        }
    }

    public SudokuBoard(int width, int height, int maxValue)
    {
        _maxValue = maxValue;
        tiles = new SudokuTile[width, height];
        CreateTiles();
        if (_maxValue == width || _maxValue == height) // If maxValue is not width or height, then adding line rules would be stupid
            SetupLineRules();
    }

    public SudokuBoard(int width, int height) : this(width, height, Math.Max(width, height)) {}

    private int _maxValue;

    private void CreateTiles()
    {
        foreach (var pos in SudokuFactory.box(tiles.GetLength(0), tiles.GetLength(1)))
        {
            tiles[pos.Item1, pos.Item2] = new SudokuTile(pos.Item1, pos.Item2, _maxValue);
        }
    }

    private void SetupLineRules()
    {
        // Create rules for rows and columns
        for (int x = 0; x < Width; x++)
        {
            IEnumerable<SudokuTile> row = GetCol(x);
            rules.Add(new SudokuRule(row, "Row " + x.ToString()));
        }
        for (int y = 0; y < Height; y++)
        {
            IEnumerable<SudokuTile> col = GetRow(y);
            rules.Add(new SudokuRule(col, "Col " + y.ToString()));
        }
    }

    internal IEnumerable<SudokuTile> TileBox(int startX, int startY, int sizeX, int sizeY)
    {
        return from pos in SudokuFactory.box(sizeX, sizeY) select tiles[startX + pos.Item1, startY + pos.Item2];
    }

    private IEnumerable<SudokuTile> GetRow(int row)
    {
        for (int i = 0; i < tiles.GetLength(0); i++)
        {
            yield return tiles[i, row];
        }
    }
    private IEnumerable<SudokuTile> GetCol(int col)
    {
        for (int i = 0; i < tiles.GetLength(1); i++)
        {
            yield return tiles[col, i];
        }
    }

    private ISet<SudokuRule> rules = new HashSet<SudokuRule>();
    private SudokuTile[,] tiles;

    public int Width
    {
        get { return tiles.GetLength(0); }
    }

    public int Height {
        get { return tiles.GetLength(1); }
    }

    public void CreateRule(string description, params SudokuTile[] tiles)
    {
        rules.Add(new SudokuRule(tiles, description));
    }
    public void CreateRule(string description, IEnumerable<SudokuTile> tiles)
    {
        rules.Add(new SudokuRule(tiles, description));
    }

    public bool CheckValid()
    {
        return rules.All(rule => rule.CheckValid());
    }

    public IEnumerable<SudokuBoard> Solve()
    {
        ResetSolutions();
        SudokuProgress simplify = SudokuProgress.PROGRESS;
        while (simplify == SudokuProgress.PROGRESS) simplify = Simplify();

        if (simplify == SudokuProgress.FAILED)
            yield break;

        // Find one of the values with the least number of alternatives, but that still has at least 2 alternatives
        var query = from rule in rules
                    from tile in rule
                    where tile.PossibleCount > 1
                    orderby tile.PossibleCount ascending
                    select tile;

        SudokuTile chosen = query.FirstOrDefault();
        if (chosen == null)
        {
            // The board has been completed, we're done!
            yield return this;
            yield break;
        }

        Console.WriteLine("SudokuTile: " + chosen.ToString());

        foreach (var value in Enumerable.Range(1, _maxValue))
        {
            // Iterate through all the valid possibles on the chosen square and pick a number for it
            if (!chosen.IsValuePossible(value))
                continue;
            var copy = new SudokuBoard(this);
            copy.Tile(chosen.X, chosen.Y).Fix(value, "Trial and error");
            foreach (var innerSolution in copy.Solve()) 
                yield return innerSolution;
        }
        yield break;
    }

    public void Output()
    {
        for (int y = 0; y < tiles.GetLength(1); y++)
        {
            for (int x = 0; x < tiles.GetLength(0); x++)
            {
                Console.Write(tiles[x, y].ToStringSimple());
            }
            Console.WriteLine();
        }
    }

    public SudokuTile Tile(int x, int y)
    {
        return tiles[x, y];
    }

    private int _rowAddIndex;

    public void AddRow(string s)
    {
        // Method for initializing a board from string
        for (int i = 0; i < s.Length; i++)
        {
            var tile = tiles[i, _rowAddIndex];
            if (s[i] == '/')
            {
                tile.Block();
                continue;
            }
            int value = s[i] == '.' ? 0 : (int)Char.GetNumericValue(s[i]);
            tile.Value = value;
        }
        _rowAddIndex++;
    }

    internal void ResetSolutions()
    {
        foreach (SudokuTile tile in tiles)
            tile.ResetPossibles();
    }
    internal SudokuProgress Simplify()
    {
        SudokuProgress result = SudokuProgress.NO_PROGRESS;
        bool valid = CheckValid();
        if (!valid)
            return SudokuProgress.FAILED;

        foreach (SudokuRule rule in rules)
            result = SudokuTile.CombineSolvedState(result, rule.Solve());

        return result;
    }

    internal void AddBoxesCount(int boxesX, int boxesY)
    {
        int sizeX = Width / boxesX;
        int sizeY = Height / boxesY;

        var boxes = SudokuFactory.box(sizeX, sizeY);
        foreach (var pos in boxes)
        {
            IEnumerable<SudokuTile> boxTiles = TileBox(pos.Item1 * sizeX, pos.Item2 * sizeY, sizeX, sizeY);
            CreateRule("Box at (" + pos.Item1.ToString() + ", " + pos.Item2.ToString() + ")", boxTiles);
        }
    }

    internal void OutputRules()
    {
        foreach (var rule in rules)
        {
            Console.WriteLine(String.Join(",", rule) + " - " + rule.ToString());
        }
    }
}



public class SudokuFactory
{
    private const int DefaultSize = 9;
    private const int SamuraiAreas = 7;
    private const int BoxSize = 3;
    private const int HyperMargin = 1;

    public static IEnumerable<Tuple<int, int>> box(int sizeX, int sizeY)
    {
        foreach (int x in Enumerable.Range(0, sizeX))
        {
            foreach (int y in Enumerable.Range(0, sizeY))
            {
                yield return new Tuple<int, int>(x, y);
            }
        }
    }

    public static SudokuBoard Samurai()
    {
        SudokuBoard board = new SudokuBoard(SamuraiAreas*BoxSize, SamuraiAreas*BoxSize, DefaultSize);
        // Removed the empty areas where there are no tiles
        var queriesForBlocked = new List<IEnumerable<SudokuTile>>();
        queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2                            ));
        queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2 + DefaultSize * 2 - BoxSize));
        queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1                            , pos.Item2 + DefaultSize));
        queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1 + DefaultSize * 2 - BoxSize, pos.Item2 + DefaultSize));
        foreach (var query in queriesForBlocked) 
        {
            foreach (var tile in query) tile.Block();
        }

        // Select the tiles in the 3 x 3 area (area.X, area.Y) and create rules for them
        foreach (var area in box(SamuraiAreas, SamuraiAreas)) 
        {
            var tilesInArea = from pos in box(BoxSize, BoxSize) select board.Tile(area.Item1 * BoxSize + pos.Item1, area.Item2 * BoxSize + pos.Item2);
            if (tilesInArea.First().IsBlocked)
                continue;
            board.CreateRule("Area " + area.Item1.ToString() + ", " + area.Item2.ToString(), tilesInArea);
        }

        // Select all rows and create columns for them
        var cols = from pos in box(board.Width,  1) select new { X = pos.Item1, Y = pos.Item2 };
        var rows = from pos in box(1, board.Height) select new { X = pos.Item1, Y = pos.Item2 };
        foreach (var posSet in Enumerable.Range(0, board.Width))
        {
            board.CreateRule("Column Upper " + posSet, from pos in box(1, DefaultSize) select board.Tile(posSet, pos.Item2));
            board.CreateRule("Column Lower " + posSet, from pos in box(1, DefaultSize) select board.Tile(posSet, pos.Item2 + DefaultSize + BoxSize));

            board.CreateRule("Row Left "  + posSet, from pos in box(DefaultSize, 1) select board.Tile(pos.Item1, posSet));
            board.CreateRule("Row Right " + posSet, from pos in box(DefaultSize, 1) select board.Tile(pos.Item1 + DefaultSize + BoxSize, posSet));

            if (posSet >= BoxSize*2 && posSet < BoxSize*2 + DefaultSize)
            {
                // Create rules for the middle sudoku
                board.CreateRule("Column Middle " + posSet, from pos in box(1, 9) select board.Tile(posSet, pos.Item2 + BoxSize*2));
                board.CreateRule("Row Middle "    + posSet, from pos in box(9, 1) select board.Tile(pos.Item1 + BoxSize*2, posSet));
            }
        }
        return board;
    }

    public static SudokuBoard SizeAndBoxes(int width, int height, int boxCountX, int boxCountY)
    {
        SudokuBoard board = new SudokuBoard(width, height);
        board.AddBoxesCount(boxCountX, boxCountY);
        return board;
    }

    public static SudokuBoard ClassicWith3x3Boxes()
    {
        return SizeAndBoxes(DefaultSize, DefaultSize, DefaultSize / BoxSize, DefaultSize / BoxSize);
    }

    public static SudokuBoard ClassicWith3x3BoxesAndHyperRegions()
    {
        SudokuBoard board = ClassicWith3x3Boxes();
        const int HyperSecond = HyperMargin + BoxSize + HyperMargin;
        // Create the four extra hyper regions
        board.CreateRule("HyperA", from pos in box(3, 3) select board.Tile(pos.Item1 + HyperMargin, pos.Item2 + HyperMargin));
        board.CreateRule("HyperB", from pos in box(3, 3) select board.Tile(pos.Item1 + HyperSecond, pos.Item2 + HyperMargin));
        board.CreateRule("HyperC", from pos in box(3, 3) select board.Tile(pos.Item1 + HyperMargin, pos.Item2 + HyperSecond));
        board.CreateRule("HyperD", from pos in box(3, 3) select board.Tile(pos.Item1 + HyperSecond, pos.Item2 + HyperSecond));
        return board;
    }

    public static SudokuBoard ClassicWithSpecialBoxes(string[] areas)
    {
        int sizeX = areas[0].Length;
        int sizeY = areas.Length;
        SudokuBoard board = new SudokuBoard(sizeX, sizeY);
        var joinedString = String.Join("", areas);
        var grouped = joinedString.Distinct();

        // Loop through all the unique characters
        foreach (var ch in grouped)
        {
            // Select the rule tiles based on the index of the character
            var ruleTiles = from i in Enumerable.Range(0, joinedString.Length)
                    where joinedString[i] == ch // filter out any non-matching characters
                    select board.Tile(i % sizeX, i / sizeY);
            board.CreateRule("Area " + ch.ToString(), ruleTiles);
        }

        return board;
    }
}


程序:

static class Program
{
    [STAThread]
    static void Main()
    {
        SolveFail();
        SolveClassic();
        SolveSmall();
        SolveExtraZones();
        SolveHyper();
        SolveSamurai();
        SolveIncompleteClassic();
    }
    private static void SolveFail()
    {
        SudokuBoard board = SudokuFactory.SizeAndBoxes(4, 4, 2, 2);
        board.AddRow("0003");
        board.AddRow("0204"); // the 2 must be a 1 on this row to be solvable
        board.AddRow("1000");
        board.AddRow("4000");
        CompleteSolve(board);
    }
    private static void SolveExtraZones()
    {
        // http://en.wikipedia.org/wiki/File:Oceans_Hypersudoku18_Puzzle.svg
        SudokuBoard board = SudokuFactory.ClassicWith3x3BoxesAndHyperRegions();
        board.AddRow(".......1.");
        board.AddRow("..2....34");
        board.AddRow("....51...");
        board.AddRow(".....65..");
        board.AddRow(".7.3...8.");
        board.AddRow("..3......");
        board.AddRow("....8....");
        board.AddRow("58....9..");
        board.AddRow("69.......");
        CompleteSolve(board);
    }
    private static void SolveSmall()
    {
        SudokuBoard board = SudokuFactory.SizeAndBoxes(4, 4, 2, 2);
        board.AddRow("0003");
        board.AddRow("0004");
        board.AddRow("1000");
        board.AddRow("4000");
        CompleteSolve(board);
    }
    private static void SolveHyper()
    {
        // http://en.wikipedia.org/wiki/File:A_nonomino_sudoku.svg
        string[] areas = new string[]{
           "111233333",
           "111222333",
           "144442223",
           "114555522",
           "444456666",
           "775555688",
           "977766668",
           "999777888",
           "999997888"
        };
        SudokuBoard board = SudokuFactory.ClassicWithSpecialBoxes(areas);
        board.AddRow("3.......4");
        board.AddRow("..2.6.1..");
        board.AddRow(".1.9.8.2.");
        board.AddRow("..5...6..");
        board.AddRow(".2.....1.");
        board.AddRow("..9...8..");
        board.AddRow(".8.3.4.6.");
        board.AddRow("..4.1.9..");
        board.AddRow("5.......7");
        CompleteSolve(board);

    }
    private static void SolveSamurai()
    {
        // http://www.freesamuraisudoku.com/1001HardSamuraiSudokus.aspx?puzzle=42
        SudokuBoard board = SudokuFactory.Samurai();
        board.AddRow("6..8..9..///.....38..");
        board.AddRow("...79....///89..2.3..");
        board.AddRow("..2..64.5///...1...7.");
        board.AddRow(".57.1.2..///..5....3.");
        board.AddRow(".....731.///.1.3..2..");
        board.AddRow("...3...9.///.7..429.5");
        board.AddRow("4..5..1...5....5.....");
        board.AddRow("8.1...7...8.2..768...");
        board.AddRow(".......8.23...4...6..");
        board.AddRow("//////.12.4..9.//////");
        board.AddRow("//////......82.//////");
        board.AddRow("//////.6.....1.//////");
        board.AddRow(".4...1....76...36..9.");
        board.AddRow("2.....9..8..5.34...81");
        board.AddRow(".5.873......9.8..23..");
        board.AddRow("...2....9///.25.4....");
        board.AddRow("..3.64...///31.8.....");
        board.AddRow("..75.8.12///...6.14..");
        board.AddRow(".......2.///.31...9..");
        board.AddRow("..17.....///..7......");
        board.AddRow(".7.6...84///8...7..5.");
        CompleteSolve(board);
    }

    private static void SolveClassic()
    {
        var board = SudokuFactory.ClassicWith3x3Boxes();
        board.AddRow("...84...9");
        board.AddRow("..1.....5");
        board.AddRow("8...2146.");
        board.AddRow("7.8....9.");
        board.AddRow(".........");
        board.AddRow(".5....3.1");
        board.AddRow(".2491...7");
        board.AddRow("9.....5..");
        board.AddRow("3...84...");
        CompleteSolve(board);
    }

    private static void SolveIncompleteClassic()
    {
        var board = SudokuFactory.ClassicWith3x3Boxes();
        board.AddRow("...84...9");
        board.AddRow("..1.....5");
        board.AddRow("8...2.46."); // Removed a "1" on this line
        board.AddRow("7.8....9.");
        board.AddRow(".........");
        board.AddRow(".5....3.1");
        board.AddRow(".2491...7");
        board.AddRow("9.....5..");
        board.AddRow("3...84...");
        CompleteSolve(board);
    }

    private static void CompleteSolve(SudokuBoard board)
    {
        Console.WriteLine("Rules:");
        board.OutputRules();
        Console.WriteLine("Board:");
        board.Output();
        var solutions = board.Solve().ToList();
        Console.WriteLine("Base Board Progress:");
        board.Output();
        Console.WriteLine("--");
        Console.WriteLine("--");
        Console.WriteLine("All " + solutions.Count + " solutions:");
        var i = 1;
        foreach (var solution in solutions)
        {
            Console.WriteLine("----------------");
            Console.WriteLine("Solution " + i++.ToString() + " / " + solutions.Count + ":");
            solution.Output();
        }
    }
}


评论

Simon-André“ Extra Mile” Forsberg是名字!

#1 楼

这真太了不起了。特别是对于多年不使用C#的人。


我主要关心的是


public,有时可以将internal成员更改为internal。使用对特定成员有意义的最严格的访问级别。private中容易出错的方法AddRow。我希望将单个字符串数组传递给SudokuBoard构造函数,而不是多个SudokuFactory调用。调用此方法太多或太少很容易会得到运行时异常。
没有某种可用于单元测试的解决方案结果的字符串表示形式。
控制台输出方法,例如AddRowOutput核心课程。它们应该位于OutputRules中,因为它们仅用于控制台输出,仅此而已。
没有单元测试。我已将您的逻辑移至单独的库并添加了单元测试项目。这是我开始重构代码以确保重构不会破坏任何东西的第一件事。
我也将使用.NET Standard库类型。这将允许为网站​​,移动(Xamarin)和桌面应用程序重用相同的逻辑。


单元测试

要添加单元测试,我添加了Program string[] tileDefinitions构造函数的参数(也应该是SudokuBoard):

internal SudokuBoard(int width, int height, int maxValue, string[] tileDefinitions)
{
    _maxValue = maxValue;
    tiles = new SudokuTile[width, height];
    CreateTiles();
    if (_maxValue == width || _maxValue == height) // If maxValue is not width or height, then adding line rules would be stupid
        SetupLineRules();

    Populate(tileDefinitions);
}

internal SudokuBoard(int width, int height, string[] tileDefinitions) : this(width, height, Math.Max(width, height), tileDefinitions)
{
}


我也删除了internal字段,并将_rowAddIndex方法替换为AddRow: >
private void PopulateTiles(string[] tileDefinitions)
{
    for (int row = 0; row < tileDefinitions.Length; row++)
    {
        string tileDefinition = tileDefinitions[row];

        for (int column = 0; column < tileDefinition.Length; column++)
        {
            SudokuTile tile = _tiles[column, row];
            if (tileDefinition[column] == '/')
            {
                tile.Block();
                continue;
            }
            tile.Value = tileDefinition[column] == '.' ? SudokuTile.CLEARED : (int)char.GetNumericValue(tileDefinition[column]);
        }
    }
}


现在我已经将PopulateTiles添加到所有string[] tileDefinitions方法中。我在SudokuFactory中添加了string[] TileDefinitions公共属性:

public string[] TileDefinitions => tiles
    .Cast<SudokuTile>()
    .OrderBy(t => t.X)
    .ThenBy(t => t.Y)
    .GroupBy(t => t.Y)
    .Select(g => string.Join(string.Empty, g.Select(t => t.Value)))
    .ToArray();


现在我们的单元测试代码(我已将控制台输出中的代码重用于测试用例)。我在这里使用XUnit:

public class SudokuSolverTests
{
    [Fact]
    public void SudokuBoard_Solve_NoSolutionFound()
    {
        // Arrange
        SudokuBoard board = SudokuFactory.SizeAndBoxes(4, 4, 2, 2, new[]
        {
            "0003",
            "0204", // the 2 must be a 1 on this row to be solvable
            "1000",
            "4000"
        });

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.False(solutions.Any());
    }

    [Fact]
    public void SudokuBoard_Solve_ClassicWithSolution()
    {
        // Arrange
        SudokuBoard board = SudokuFactory.ClassicWith3x3Boxes(new[]
        {
            "...84...9",
            "..1.....5",
            "8...2146.",
            "7.8....9.",
            ".........",
            ".5....3.1",
            ".2491...7",
            "9.....5..",
            "3...84..."
        });

        string[] tileDefinitions = new[]
        {
            "632845179",
            "471369285",
            "895721463",
            "748153692",
            "163492758",
            "259678341",
            "524916837",
            "986237514",
            "317584926",
        };

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Single(solutions);
        Assert.Equal(tileDefinitions, solutions.First().TileDefinitions);
    }

    [Fact]
    public void SudokoBoard_Solve_ClassicWithMultipleSolutions()
    {
        // Arrange
        SudokuBoard board = SudokuFactory.ClassicWith3x3Boxes(new[]
        {
            "...84...9",
            "..1.....5",
            "8...2.46.", // Removed a "1" on this line
            "7.8....9.",
            ".........",
            ".5....3.1",
            ".2491...7",
            "9.....5..",
            "3...84..."
        });

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Equal(20, solutions.Count());
    }

    [Fact]
    public void SudukoBoard_Solve_SmallWithSolution()
    {
        // Arrange
        SudokuBoard board = SudokuFactory.SizeAndBoxes(4, 4, 2, 2, new[]
        {
            "0003",
            "0004",
            "1000",
            "4000"
        });

        string[] tileDefinitions = new[]
        {
            "2413",
            "3124",
            "1342",
            "4231"
        };

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Single(solutions);
        Assert.Equal(tileDefinitions, solutions.Single().TileDefinitions);
    }

    [Fact]
    public void SudokoBoard_Solve_ExtraZonesWithSolution()
    {
        // Arrange
        // http://en.wikipedia.org/wiki/File:Oceans_Hypersudoku18_Puzzle.svg
        SudokuBoard board = SudokuFactory.ClassicWith3x3BoxesAndHyperRegions(new[]
        {
            ".......1.",
            "..2....34",
            "....51...",
            ".....65..",
            ".7.3...8.",
            "..3......",
            "....8....",
            "58....9..",
            "69......."
        });

        string[] tileDefinitions = new[]
        {
            "946832715",
            "152697834",
            "738451296",
            "819726543",
            "475319682",
            "263548179",
            "327985461",
            "584163927",
            "691274358"
        };

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Single(solutions);
        Assert.Equal(tileDefinitions, solutions.First().TileDefinitions);
    }

    [Fact]
    public void SudokoBoard_Solve_HyperWithSolution()
    {
        // Arrange
        // http://en.wikipedia.org/wiki/File:A_nonomino_sudoku.svg
        string[] areas = new string[]
        {
            "111233333",
            "111222333",
            "144442223",
            "114555522",
            "444456666",
            "775555688",
            "977766668",
            "999777888",
            "999997888"
        };
        SudokuBoard board = SudokuFactory.ClassicWithSpecialBoxes(areas, new[]
        {
            "3.......4",
            "..2.6.1..",
            ".1.9.8.2.",
            "..5...6..",
            ".2.....1.",
            "..9...8..",
            ".8.3.4.6.",
            "..4.1.9..",
            "5.......7"
        });

        string[] tileDefinitions = new[]
        {
            "358196274",
            "492567138",
            "613978425",
            "175842693",
            "826453719",
            "249731856",
            "987324561",
            "734615982",
            "561289347"
        };

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Single(solutions);
        Assert.Equal(tileDefinitions, solutions.First().TileDefinitions);
    }

    [Fact]
    public void SudokoBoard_Solve_SamuraiWithSolution()
    {
        // Arrange
        // http://www.freesamuraisudoku.com/1001HardSamuraiSudokus.aspx?puzzle=42
        SudokuBoard board = SudokuFactory.Samurai(new[]
        {
            "6..8..9..///.....38..",
            "...79....///89..2.3..",
            "..2..64.5///...1...7.",
            ".57.1.2..///..5....3.",
            ".....731.///.1.3..2..",
            "...3...9.///.7..429.5",
            "4..5..1...5....5.....",
            "8.1...7...8.2..768...",
            ".......8.23...4...6..",
            "//////.12.4..9.//////",
            "//////......82.//////",
            "//////.6.....1.//////",
            ".4...1....76...36..9.",
            "2.....9..8..5.34...81",
            ".5.873......9.8..23..",
            "...2....9///.25.4....",
            "..3.64...///31.8.....",
            "..75.8.12///...6.14..",
            ".......2.///.31...9..",
            "..17.....///..7......",
            ".7.6...84///8...7..5."
        });

        string[] tileDefinitions = new[]
        {
            "674825931000142673859",
            "513794862000897425361",
            "982136475000563189472",
            "357619248000425916738",
            "298457316000918357246",
            "146382597000376842915",
            "469578123457689534127",
            "821963754689231768594",
            "735241689231754291683",
            "000000512748396000000",
            "000000497163825000000",
            "000000368592417000000",
            "746921835976142368597",
            "238456971824563497281",
            "159873246315978152346",
            "815237469000625749813",
            "923164758000314825769",
            "467598312000789631425",
            "694385127000431586972",
            "581742693000257914638",
            "372619584000896273154"
        };

        // Act
        IEnumerable<SudokuBoard> solutions = board.Solve();

        // Assert
        Assert.Single(solutions);
        Assert.Equal(tileDefinitions, solutions.First().TileDefinitions);
    }
}


重构

我已经从其他答案中应用了已经提到的重构想法,所以我将不单独提及它们。

此外,我还使用表达式体成员,字符串插值和来自C#较新版本的局部函数

SudokuTile

属性可以按SudokuBoard / readonly分组,并且可变:

internal const int CLEARED = 0;
private readonly int _maxValue;
private readonly int _x;
private readonly int _y;

private IEnumerable<int> _possibleValues = Enumerable.Empty<int>();
private int _value = 0;
private bool _blocked = false;


const应该以下划线开头,以便与您的命名约定保持一致。 _possibleValues的类型可以安全地从_possibleValues替换为更原始的ISet<int>IEnumerable<int>方法应简化为:

internal void ResetPossibles()
{
    if (HasValue)
        _possibleValues = Enumerable.Repeat(Value, 1);
    else
        _possibleValues = Enumerable.Range(1, _maxValue);
}



线

possibleValues = new HashSet<int>(possibleValues.Where(x => !existingNumbers.Contains(x)));


使用LINQ方法ResetPossibles可以轻松替换

_possibleValues = _possibleValues.Except(existingNumbers);


此外,您也可以在RemovePossibles方法中用Except语句替换双if。结果:

internal SudokuProgress RemovePossibles(IEnumerable<int> existingNumbers)
{
    if (_blocked)
        return SudokuProgress.NO_PROGRESS;

    // Takes the current possible values and removes the ones existing in `existingNumbers`
    _possibleValues = _possibleValues.Except(existingNumbers);

    switch (_possibleValues.Count())
    {
        case 0:
            return SudokuProgress.FAILED;
        case 1:
            Fix(_possibleValues.First(), "Only one possibility");
            return SudokuProgress.PROGRESS;
        default:
            return SudokuProgress.NO_PROGRESS;
    }
}


SudokuRule

在这里,我们应该将switch字段类型从RemovePossibles更改为_tiles,因为您将不会使用任何类似集合的修改方法ISet<SudokuTile>IEnumerable<SudokuTile>Add。收藏仅用于阅读。但是您可以在构造函数中保留行

_tiles = new HashSet<SudokuTile>(tiles);


,因为LINQ方法已推迟执行,并且此处的收集应立即计算。但我已将其替换为

_tiles = tiles.ToArray();


,因为它更短。

您的字段也应为Remove

private readonly IEnumerable<SudokuTile> _tiles;
private readonly string _description;



Clear方法中的行

IEnumerable<int> existingNumbers = new HashSet<int>(withNumber.Select(tile => tile.Value).Distinct().ToList());


有很多冗余:



readonly构造函数调用在这里没有意义,因为您已经使用RemovePossibles方法调用获得了物化集合
,但是HashSet<int>方法调用在这里也是多余的,因为此数字集合被传递给了ToList立即修改ToList集合。

SudokuTile.RemovePossibles调用在这里也是多余的,因为您已经在使用line

bool valid = _rules.All(rule => rule.CheckValid());


使用_possibleValues方法之前检查了所有规则,所以这里所有的tile值都已经不同。


所以这行可以缩短为

IEnumerable<int> existingNumbers = withNumber.Select(tile => tile.Value);



Distinct方法中的行也可以

IList<int> existingNumbers = _tiles.Select(tile => tile.Value).Distinct().ToList();


有问题


您使用SudokuBoard.Simplify作为集合仅用于使用CheckForOnlyOnePossibility方法读取,因此existingNumbers可以安全地替换为ContainsIList<int>调用在这里是多余的,因为IEnumerable<int>方法未使用延迟执行。
bool valid = _rules.All(rule => rule.CheckValid());


,因此您所有的图块值在这里已经是不同的。 >
SudokuBoard

我认为将所有字段定义移至带有ToList修饰符的类的顶部。另外,我在字段名称中添加了下划线以增强命名约定的一致性。 LINQ具有Contains方法,但没有Distinct)。可以使用单个LINQ语句简化SudokuBoard.Simplify复制构造函数中的双循环(通过将readonly返回类型从无面_rules更改为更多方式有意义的List<SudokuRule>):

IEnumerable<int> existingNumbers = _tiles.Select(tile => tile.Value);



这段大代码

private readonly List<SudokuRule> _rules = new List<SudokuRule>();
private readonly SudokuTile[,] _tiles;
private readonly int _maxValue;


可以替换只需几行:

internal SudokuBoard(SudokuBoard copy)
{
    _maxValue = copy._maxValue;
    _tiles = new SudokuTile[copy.Width, copy.Height];
    CreateTiles();
    // Copy the tile values
    foreach (Point pos in SudokuFactory.Box(Width, Height))
    {
        _tiles[pos.X, pos.Y] = new SudokuTile(pos.X, pos.Y, _maxValue)
        {
            Value = copy[pos.X, pos.Y].Value
        };
    }

    // Copy the rules
    _rules = copy._rules
        .Select(rule => new SudokuRule(rule.Select(tile => _tiles[tile.X, tile.Y]), rule.Description))
        .ToList();
}



ToList方法可以用索引器代替,以通过数组语法访问板的磁贴:

private void SetupLineRules()
{
    // Create rules for rows and columns
    for (int x = 0; x < Width; x++)
    {
        IEnumerable<SudokuTile> row = GetCol(x);
        rules.Add(new SudokuRule(row, "Row " + x.ToString()));
    }
    for (int y = 0; y < Height; y++)
    {
        IEnumerable<SudokuTile> col = GetRow(y);
        rules.Add(new SudokuRule(col, "Col " + y.ToString()));
    }
}

private IEnumerable<SudokuTile> GetRow(int row)
{
    for (int i = 0; i < tiles.GetLength(0); i++)
    {
        yield return tiles[i, row];
    }
}
private IEnumerable<SudokuTile> GetCol(int col)
{
    for (int i = 0; i < tiles.GetLength(1); i++)
    {
        yield return tiles[col, i];
    }
}



ToSet方法可以使用SudokuBoard LINQ方法和变量内联来简化:
所有简短的一次性使用过的东西,例如SudokuFactory.BoxTuplePointTileSimplify都应内联或用作局部函数。结果文件将在下面列出。

SudokuFactory

使用来自其他几个答案的建议重构Aggregate方法: >
CheckValid方法中替换了此代码

private void SetupLineRules()
{
    // Create rules for rows and columns
    for (int x = 0; x < Width; x++)
        _rules.Add(new SudokuRule(Enumerable.Range(0, _tiles.GetLength(1)).Select(i => _tiles[x, i]), $"Row {x}"));

    for (int y = 0; y < Height; y++)
        _rules.Add(new SudokuRule(Enumerable.Range(0, _tiles.GetLength(0)).Select(i => _tiles[i, y]), $"Col {y}"));
}


带有集合初始化程序和TileBox LINQ方法: br />
该文件中还有多个地方,其中LINQ方法的语法更短,并且比查询语法更易读。 >
public SudokuTile this[int x, int y] => _tiles[x, y];


SudokuTile

private SudokuProgress Simplify()
{
    bool valid = _rules.All(rule => rule.CheckValid());
    if (!valid)
        return SudokuProgress.FAILED;

    return _rules.Aggregate(SudokuProgress.NO_PROGRESS,
        (progress, rule) => SudokuTile.CombineSolvedState(progress, rule.Solve()));
}


SudokuRule

internal static IEnumerable<Point> Box(int sizeX, int sizeY)
{
    return
        from x in Enumerable.Range(0, sizeX)
        from y in Enumerable.Range(0, sizeY)
        select new Point(x, y);
}


SudokuBoard

SudokuBoard board = new SudokuBoard(SamuraiAreas*BoxSize, SamuraiAreas*BoxSize, DefaultSize);
// Removed the empty areas where there are no tiles
var queriesForBlocked = new List<IEnumerable<SudokuTile>>();
queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2                            ));
queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2 + DefaultSize * 2 - BoxSize));
queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1                            , pos.Item2 + DefaultSize));
queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1 + DefaultSize * 2 - BoxSize, pos.Item2 + DefaultSize));
foreach (var query in queriesForBlocked) 
{
    foreach (var tile in query) tile.Block();
}


SudokuFactory

SudokuBoard board = new SudokuBoard(SamuraiAreas * BoxSize, SamuraiAreas * BoxSize, DefaultSize, tileDefinitions);
// Removed the empty areas where there are no tiles
IEnumerable<SudokuTile> tiles = new[]
{
    Box(BoxSize, BoxSize * 2).Select(pos => board[pos.X + DefaultSize, pos.Y]),
    Box(BoxSize, BoxSize * 2).Select(pos => board[pos.X + DefaultSize, pos.Y + DefaultSize * 2 - BoxSize]),
    Box(BoxSize * 2, BoxSize).Select(pos => board[pos.X, pos.Y + DefaultSize]),
    Box(BoxSize * 2, BoxSize).Select(pos => board[pos.X + DefaultSize * 2 - BoxSize, pos.Y + DefaultSize])
}.SelectMany(t => t);

foreach (SudokuTile tile in tiles) tile.Block();


源代码

所有源代码为在Github上可用。

#2 楼

令人印象深刻。我是认真的。

观察结果:

您的枚举...

public enum SudokuProgress { FAILED, NO_PROGRESS, PROGRESS }


应该是:
/>
public enum SudokuProgress { Failed, NoProgress, Progress }



当看到的第一件事是:

public class SudokuBoard
{
    public SudokuBoard(SudokuBoard copy)
    {
        _maxValue = copy._maxValue;
        tiles = new SudokuTile[copy.Width, copy.Height];
        CreateTiles();


你想知道_maxValuetiles来自何处,为什么可以这样访问_maxValue(其命名约定是私有字段的约定)-我将其公开为仅获取属性。对我来说,从另一个对象访问私有字段似乎不是本能的。

说到魔鬼:

private int _maxValue;


这行恰好在上面使用它的构造函数(比第一次使用时少30行)。

box方法应命名为Box(实际上box是一个坏名字,因为它是CIL指令的名称, C#编译为),返回的不是很漂亮Tuple<T1,T2>-框架具有称为Point的类型,该类型具有XY属性;如果不合适的话,我不知道是什么。旁注,Point是一种值类型,因此,如果在引用类型Tuple上使用它,则实际上没有装箱操作(导致装箱)。最重要的是,使用Point并以其他方式调用该方法:

public static IEnumerable<Point> Box(int sizeX, int sizeY)
{
    foreach (int x in Enumerable.Range(0, sizeX))
    {
        foreach (int y in Enumerable.Range(0, sizeY))
        {
            yield return new Point(x, y);
        }
    }
}


您要滥用LINQ吗?怎么样:

private SudokuTile[,] tiles;
private void CreateTiles()
{
    foreach (var pos in SudokuFactory.box(tiles.GetLength(0), tiles.GetLength(1)))
    {
        tiles[pos.Item1, pos.Item2] = new SudokuTile(pos.Item1, pos.Item2, _maxValue);
    }
}


并将其转换为:

private Dictionary<Point, SudokuTile> tiles;
private void CreateTiles()
{
    tiles = SudokuFactory
                  .Box(tiles.GetLength(0), tiles.GetLength(1))
                  .Select(p => new KeyValuePair<Point, SudokuTile>{ Key = p, Value = new SudokuTile(p.X, p.Y, _maxValue)})
                  .ToDictionary(kvp => pkv.Key, kvp => kvp.Value);
}


返回IEnumerable<Point>通过修改后的Box方法,选择KeyKeyValuePair和新SudokuTile的每个点作为谷值,然后ToDictionary选择Enumerable进入字典,该字典被分配给tiles。 (C#:1,Java:0)代码行:1.


SudokuRule中,您的私有字段可以标记为readonly

这仅部分审查,我将在实现自己的解决方案后写更多内容-我故意没有查看您的难题解决代码:)

总体看起来还不错(除了所有不需要的东西,但这只是依赖注入,我在说,不会使C#变得更糟,但是使用非静态测试可能会更有趣依赖项),这周让您对C#感到非常高兴。我知道Visual Studio不是Eclipse,但是我可以向您保证VS与ReSharper的结合会带来类似的体验(并且可能会向您展示一些LINQ技巧!),至少在代码检查方面(R#使VS实际上比Eclipse更好...但是我有偏见,而且要随波逐流,所以我将其保持不变!)...


我喜欢您的static方法Solve()返回所有找到的方法解决方案。也就是说,如果将整个项目编译为1个单个程序集(.exe / .dll),则对yield访问修饰符的使用等效于internal-public基本上意味着“程序集作用域” ,因此无法从另一个程序集访问internal类型或方法;如果没有其他程序集,则项目中的所有内容都可以“看到”它,因此在这里我看不到internal的意义。

没什么可说的,只是方法internal可能更好如IsValuePossible,但这只是挑剔。非常整洁,我很嫉妒。

最后一件事-这一部分列表初始化代码:


var queriesForBlocked = new List<IEnumerable<SudokuTile>>();
queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2                            ));
queriesForBlocked.Add(from pos in box(BoxSize, BoxSize*2) select board.Tile(pos.Item1 + DefaultSize, pos.Item2 + DefaultSize * 2 - BoxSize));
queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1                            , pos.Item2 + DefaultSize));
queriesForBlocked.Add(from pos in box(BoxSize*2, BoxSize) select board.Tile(pos.Item1 + DefaultSize * 2 - BoxSize, pos.Item2 + DefaultSize));



可以使用集合初始值设定项,并编写如下:现在只有1条指令。

#3 楼

即使对我来说结构看起来不错,也无法在手机上读取所有这些代码!干得好!

我看到了。

if (value < CLEARED)
    throw new ArgumentOutOfRangeException("SudokuTile Value cannot be zero or smaller. Was " + value);


if设置为0,如果if检查'小于0',则可以设置该值到0。

还没有运行代码,为什么CLEAREDtoString()上有四个参数,仅使用三个?

我会仔细看看在电脑上,但是很好!

评论


\ $ \ begingroup \ $
是的,我曾经有一个ClearValue方法来分别处理清除,但是我意识到这没用,因为我只是在进行if-else检查是否应该设置值或调用ClearValue方法。忘记更改异常描述。还忘了从String.Format中删除该多余的参数。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月16日15:16

#4 楼

我在zilch旁边了解C#,因此在这次审查中不会有什么帮助,但是我可以说,它看起来经过深思熟虑并实现了一些很棒的功能。就像Retailcoder一样,我仍在使用Ruby来开发我的版本(希望将残酷部分最小化)。

如果C#允许枚举实现方法,我会将CombineSolvedState移入SudokuProgress。原谅我的Java语法,但是如果允许的话,我希望它会很容易翻译。在没有使用类而不是枚举的情况下是不可能直接实现的,而另一个则暗示可以使用扩展名来实现。

评论


\ $ \ begingroup \ $
C#中的枚举不能具有方法,但是确实可以。是的,您可以键入sw [itch],Tab(自动扩展),然后是枚举类型的变量/参数的名称,而VS将为所有成员提供大小写语句。
\ $ \ endgroup \ $
–鲁迪
2013年12月16日上午10:12

\ $ \ begingroup \ $
是的,我想使用枚举方法,但与Java不同,C#没有它们。不过,您对切换陈述是正确的。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月16日15:08

\ $ \ begingroup \ $
C#中的枚举可以具有扩展方法,这将使您实现相同的语法。
\ $ \ endgroup \ $
–breischl
2013年12月19日20:30在

\ $ \ begingroup \ $
@breischl-我上面链接的问题的第二个答案也提到了扩展方法,但是我无法从其示例中弄清楚语法。
\ $ \ endgroup \ $
– David Harkness
13年12月19日在21:24

\ $ \ begingroup \ $
啊,对。抱歉,我略过了答案的末尾而错过了。
\ $ \ endgroup \ $
–breischl
13年12月19日在21:34

#5 楼

您可以编写不带Box循环或foreachyield return方法: ,而不是那么冗长,创建Tuple的方法是Tuple.Create。它们之间的共享状态将不是问题。例如,两个武士Sudoku谜题确实共享规则。 Tile。您在某些地方致电Rule Board


Tile看起来像个孤儿。显然,它不属于该类。
Tuple<int,int>应该被调用确切的次数。
在调用其他任何东西之前。在我看来,就像一个构造函数。

拼图和求解方法之间也没有区别。难题是一成不变的结构。每个试图解决报纸上难题的人都试图解决同样的难题。他们使用不同的方法并使用不同的临时数据结构。

public static IEnumerable<Tuple<int, int>> Box(int sizeX, int sizeY)
{
    return 
        from x in Enumerable.Range(0, sizeX)
        from y in Enumerable.Range(0, sizeY)
        select Tuple.Create(x,y);
}


您正在这里汇总。可以更清楚地重写为:

public static IEnumerable<Tuple<int, int>> Box2(int sizeX, int sizeY)
{
    return 
        Enumerable.Range(0,sizeX).SelectMany(x => 
        Enumerable.Range(0,sizeY).Select(y => 
        Tuple.Create(x,y)));
}


评论


\ $ \ begingroup \ $
我不确定您在董事会之间共享状态的含义……如果它们是不可变的,那么我将需要编写大量额外的代码来解决地图,因为所有基本解决方案(非暴力部分)需要创建一个全新的地图。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月16日15:19

\ $ \ begingroup \ $
@SimonAndréForsberg这种分离将在以下两个类之间进行建模:1)董事会的布局和规则; 2)特定难题的初始已知单元; 3)用于解决该难题的可变模型。虽然很酷,但是要设计用于在单用户控制台应用程序中运行的某些东西会需要很多代码。
\ $ \ endgroup \ $
– David Harkness
2013年12月16日17:29

\ $ \ begingroup \ $
您有一些非常好的要点,对于以后的项目来说,这是个不错的主意,但是对于本周的挑战来说,我认为这会更加矫kill过正:)
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月16日17:52