Оказывается, это не должно быть слишком плохо для грубой силы, по крайней мере, если ваша доска не слишком большая.
Вы можете определить свой набор бомб, где каждая ячейка может быть Present
, Not Present
или Unexplored
, где Unexplored
означает, что там может быть или не быть бомба. Затем вы можете написать метод, который берет вашу доску и этот набор бомб, и определяет, является ли доска недействительной или может быть действительной в зависимости от фактического значения любых Unexplored
ячеек.
Тогда вы начинаете ходить по доске. Установите в первой ячейке значение Present
и посмотрите, не приведет ли это к доске, которая может быть действительной (или определенно недействительной). Если это возможно, установите recurse на следующую ячейку. Затем установите для первой ячейки значение NotPresent
и посмотрите, может ли это быть действительным, и рекурсивна ли она в следующую ячейку.
Эта обрезка досок, которые имеют небольшую площадь, которая является недействительной, должна значительно сократить пространство поиска по сравнению с полной грубой силой.
Когда вы проверяете, может ли доска быть действительной, вы можете оптимизировать ее, проверяя только ячейки в квадрате вокруг ячейки, которую вы изменили, поскольку это единственные ячейки, на которые можно повлиять.
Это не полноценное динамическое программирование, и, вероятно, выиграет от некоторого запоминания: если в правом нижнем углу есть комбинация бомб, которая недопустима (нижний правый является последней исследованной областью), она продолжит пробовать их снова и снова с различными (действительными) комбинациями бомб в других местах.
Это также застрянет, если у вашей доски большая открытая область, так как будет большое количество комбинаций, и это тщательно изучит каждую из них.
Я собрал немного C #, чтобы проиллюстрировать мою идею. Это не опрятно или не совсем ясно (и за это я прошу прощения - у меня не хватило времени, чтобы привести его в порядок), но он находит 4 решения для вашего второго примера.
Это написано с использованием рекурсии, поэтому взорвется стек с большими досками. Перепишите его, чтобы оно было итеративным.
class Program
{
static void Main(string[] args)
{
var board = new Board(new CellState[,]
{
{ CellState.Odd, CellState.None },
{ CellState.Odd, CellState.None },
{ CellState.Odd, CellState.Even }
});
var bombs = new BombState[board.Height, board.Width];
int numSolutions = 0;
Explore(board, bombs, 0, 0, ref numSolutions);
}
private static void Explore(Board board, BombState[,] bombs, int x, int y, ref int numSolutions)
{
int nextX = x + 1;
int nextY = y;
if (nextX >= board.Width)
{
nextX = 0;
nextY++;
}
bombs[y, x] = BombState.Present;
if (board.DetermineValidity(bombs, x, y))
{
if (nextY >= board.Height)
numSolutions++;
else
Explore(board, bombs, nextX, nextY, ref numSolutions);
}
bombs[y, x] = BombState.NotPresent;
if (board.DetermineValidity(bombs, x, y))
{
if (nextY >= board.Height)
numSolutions++;
else
Explore(board, bombs, nextX, nextY, ref numSolutions);
}
bombs[y, x] = BombState.Unexplored;
}
}
public enum CellState
{
Odd,
Even,
None,
}
public enum BombState
{
Unexplored,
Present,
NotPresent,
}
public class Board
{
private readonly CellState[,] cells;
public int Width { get; }
public int Height { get; }
public Board(CellState[,] cells)
{
this.cells = cells;
this.Width = cells.GetLength(1);
this.Height = cells.GetLength(0);
}
// Takes a board of bombs, and the position of a bomb to inspect, and determines
// whether that bomb position is definitely valid, or is unknown/invalid
public bool DetermineValidity(BombState[,] bombs, int changedX, int changedY)
{
// We only need to consider the cells in a square around the cell which was just changed
for (int x = Math.Max(0, changedX - 1); x < Math.Min(this.Width, changedX + 1); x++)
{
for (int y = Math.Max(0, changedY - 1); y < Math.Min(this.Height, changedY + 1); y++)
{
var cellState = this.cells[y, x];
// If this is a "None", there's nothing to check
if (cellState == CellState.None)
continue;
// For each cell, check its neighbours... If they're all specified, get the number of boms
int numBombs = 0;
bool areAllSpecified = true;
if (x > 0)
InspectNeighbour(bombs[y, x - 1], ref numBombs, ref areAllSpecified);
if (areAllSpecified && x < this.Width - 1)
InspectNeighbour(bombs[y, x + 1], ref numBombs, ref areAllSpecified);
if (areAllSpecified && y > 0)
InspectNeighbour(bombs[y - 1, x], ref numBombs, ref areAllSpecified);
if (areAllSpecified && y < this.Height - 1)
InspectNeighbour(bombs[y + 1, x], ref numBombs, ref areAllSpecified);
if (areAllSpecified && ((numBombs % 2) == 0) != (cellState == CellState.Even))
return false;
}
}
return true;
}
private static void InspectNeighbour(BombState state, ref int numBombs, ref bool areAllSpecified)
{
switch (state)
{
case BombState.NotPresent:
break;
case BombState.Present:
numBombs++;
break;
case BombState.Unexplored:
areAllSpecified = false;
break;
}
}
}
Вам необходимо скрыть консоль за интерфейсом (Это можно считать полезным в любом случае)
Написать тест
[TestMethod]
public void HelloWorld_WritesHelloWorldToConsole()
{
// Arrange
IConsole consoleMock = MockRepository.CreateMock<IConsole>();
// primitive injection of the console
Program.Console = consoleMock;
// Act
Program.HelloWorld();
// Assert
consoleMock.AssertWasCalled(x => x.WriteLine("Hello World"));
}
Написать программу
public static class Program
{
public static IConsole Console { get; set; }
// method that does the "logic"
public static void HelloWorld()
{
Console.WriteLine("Hello World");
}
// setup real environment
public static void Main()
{
Console = new RealConsoleImplementation();
HelloWorld();
}
}
Refactor на что-то более полезное; -)
Презентатор-просмотр? (модель не кажется строго необходимой)
Представление - это класс, который передает вывод на консоль (простые однострочные методы).
Presenter - это интерфейс, который вызывает view.ShowText («Hello World»), Вы можете проверить это, предоставив представление заглушки.
Для повышения производительности я бы просто написал чертову программу:)
Достаточно одного теста (в псевдокоде):
IView view = Stub<IView>();
Expect( view.ShowText("Hello World") );
Presenter p = new Presenter( view );
p.Show();
Assert.IsTrue( view.MethodsCalled );
Ну ... Я не видел TDD-версию Hello World. Но, чтобы увидеть такую же простую проблему, к которой подходили с учетом TDD и управляемости, вы можете взглянуть на Enterprise FizzBuzz ( код ). По крайней мере, это позволит вам увидеть уровень чрезмерной инженерии, которого вы могли бы достичь в мире приветствия.
Псевдокод:
В рабочем коде вместо макета вы используете подсказку.
Правило большого пальца:
Очень интересный вопрос. Я не большой пользователь TDD, но я выскажу некоторые мысли.
Я предполагаю, что приложение, которое вы хотите протестировать, таково:
public static void Main()
{
Console.WriteLine("Hello World");
}
Теперь, так как я не могу придумать какой-либо хороший способ проверить это напрямую, я бы разбил задачу записи на интерфейс.
public interface IOutputWriter
{
void WriteLine(string line);
}
public class ConsoleWriter : IOutputWriter
{
public void WriteLine(string line)
{
Console.WriteLine(line);
}
}
И разбить приложение следующим образом:
public static void Main()
{
IOutputWriter consoleOut = new ConsoleWriter();
WriteHelloWorldToOutput(consoleOut);
}
public static void WriteHelloWorldToOutput(IOutputWriter output)
{
output.WriteLine("Hello World");
}
Теперь у вас есть точка внедрения в метод, который позволяет вам использовать выбранную вами среду для моделирования, чтобы утверждать, что метод WriteLine вызывается с параметром «Hello World».
Проблемы, которые я оставил нерешенными (и я был бы заинтересован во вводе):
Как протестировать класс ConsoleWriter, я думаю, вам все еще нужна некоторая среда тестирования пользовательского интерфейса, чтобы добиться этого, и если у вас это было, то В любом случае вся проблема в споре ...
Тестирование основного метода.
Почему я чувствую, что чего-то достиг, изменив одну строку непроверенного кода на семь строк кода, только одна из которых фактически протестирована (хотя я предполагаю, что охват увеличился )
В Java вы можете захватить («перенаправить») поток System.out и прочитать его содержимое. Я уверен, что то же самое можно сделать в C #. Это всего лишь несколько строк кода в Java, так что я уверен, что в C # этого не будет намного больше
Я действительно должен возразить против вопроса! Все методологии имеют свое место, и TDD хорош во многих местах. Но пользовательские интерфейсы - это первое, что я действительно отхожу от TDD. Это, по моему скромному мнению, является одним из лучших обоснований шаблона проектирования MVC: протестируйте свои модели и контроллер программно; визуально осмотреть свой взгляд. То, о чем вы говорите, - это жесткое кодирование данных «Hello World» и тестирование их попадания на консоль. Чтобы выполнить этот тест на одном и том же исходном языке, вам, скорее всего, потребуется фиктивный объект консоли, , который является единственным объектом, который вообще что-либо делает .
С другой стороны, вы можете написать свой тест в bash:
echo `java HelloWorldProgram`|grep -c "^Hello World$"
Немного сложно добавить в набор тестов JUnit, но что-то подсказывает мне, что это никогда не было планом ...
Я думаю, что-то вроде этого:
using NUnit.Framework;
using System.Diagnostics;
[TestFixture]
public class MyTestClass {
[Test]
public void SayHello() {
string greet = "Hello World!";
Debug.WriteLine(greet);
Assert.AreEqual("Hello World!", greet);
}
}
Я согласен с Дэвидом Бергером; отделите интерфейс и протестируйте модель. Похоже, что "модель" в данном случае - это простой класс, который возвращает "Hello, world!". Тест выглядел бы так (на Java):
Greeter greeter = new Greeter();
assertEquals("Hello World!", greeter.greet());
Я создал запись решения Hello World
в стиле TDD на http://ziroby.wordpress.com/2010/04/18/tdd_hello_world/ .