Task: Solve a Numbrix puzzle



C#: using System.Collections;
using System.Collections.Generic;
using static System.Console;
using static System.Math;
using static System.Linq.Enumerable;

public class Solver
{
    private static readonly (int dx, int dy)[]
        
        numbrixMoves = {(1,0),(0,1),(-1,0),(0,-1)};

    private (int dx, int dy)[] moves;
        
    public static void Main()
    {
        var numbrixSolver = new Solver(numbrixMoves);
        Print(numbrixSolver.Solve(false, new [,] {
            {  0,  0,  0,  0,  0,  0,  0,  0,  0 },
            {  0,  0, 46, 45,  0, 55, 74,  0,  0 },
            {  0, 38,  0,  0, 43,  0,  0, 78,  0 },
            {  0, 35,  0,  0,  0,  0,  0, 71,  0 },
            {  0,  0, 33,  0,  0,  0, 59,  0,  0 },
            {  0, 17,  0,  0,  0,  0,  0, 67,  0 },
            {  0, 18,  0,  0, 11,  0,  0, 64,  0 },
            {  0,  0, 24, 21,  0,  1,  2,  0,  0 },
            {  0,  0,  0,  0,  0,  0,  0,  0,  0 },
        }));

        Print(numbrixSolver.Solve(false, new [,] {
            {  0,  0,  0,  0,  0,  0,  0,  0,  0 },
            {  0, 11, 12, 15, 18, 21, 62, 61,  0 },
            {  0,  6,  0,  0,  0,  0,  0, 60,  0 },
            {  0, 33,  0,  0,  0,  0,  0, 57,  0 },
            {  0, 32,  0,  0,  0,  0,  0, 56,  0 },
            {  0, 37,  0,  1,  0,  0,  0, 73,  0 },
            {  0, 38,  0,  0,  0,  0,  0, 72,  0 },
            {  0, 43, 44, 47, 48, 51, 76, 77,  0 },
            {  0,  0,  0,  0,  0,  0,  0,  0,  0 },
        }));
    }

    public Solver(params (int dx, int dy)[] moves) => this.moves = moves;

    public int[,] Solve(bool circular, params string[] puzzle)
    {
        var (board, given, count) = Parse(puzzle);
        return Solve(board, given, count, circular);
    }

    public int[,] Solve(bool circular, int[,] puzzle)
    {
        var (board, given, count) = Parse(puzzle);
        return Solve(board, given, count, circular);
    }

    private int[,] Solve(int[,] board, BitArray given, int count, bool circular)
    {
        var (height, width) = (board.GetLength(0), board.GetLength(1));
        bool solved = false;
        for (int x = 0; x < height && !solved; x++) {
            solved = Range(0, width).Any(y => Solve(board, given, circular, (height, width), (x, y), count, (x, y), 1));
            if (solved) return board;
        }
        return null;
    }

    private bool Solve(int[,] board, BitArray given, bool circular,
        (int h, int w) size, (int x, int y) start, int last, (int x, int y) current, int n)
    {
        var (x, y) = current;
        if (x < 0 || x >= size.h || y < 0 || y >= size.w) return false;
        if (board[x, y] < 0) return false;
        if (given[n - 1]) {
            if (board[x, y] != n) return false;
        } else if (board[x, y] > 0) return false;
        board[x, y] = n;
        if (n == last) {
            if (!circular || AreNeighbors(start, current)) return true;
        }
        for (int i = 0; i < moves.Length; i++) {
            var move = moves[i];
            if (Solve(board, given, circular, size, start, last, (x + move.dx, y + move.dy), n + 1)) return true;
        }
        if (!given[n - 1]) board[x, y] = 0;
        return false;

        bool AreNeighbors((int x, int y) p1, (int x, int y) p2) => moves.Any(m => (p2.x + m.dx, p2.y + m.dy).Equals(p1));
    }

    private static (int[,] board, BitArray given, int count) Parse(string[] input)
    {
        (int height, int width) = (input.Length, input[0].Length);
        int[,] board = new int[height, width];
        int count = 0;
        for (int x = 0; x < height; x++) {
            string line = input[x];
            for (int y = 0; y < width; y++) {
                board[x, y] = y < line.Length && char.IsDigit(line[y]) ? line[y] - '0' : -1;
                if (board[x, y] >= 0) count++;
            }
        }
        BitArray given = Scan(board, count, height, width);
        return (board, given, count);
    }

    private static (int[,] board, BitArray given, int count) Parse(int[,] input)
    {
        (int height, int width) = (input.GetLength(0), input.GetLength(1));
        int[,] board = new int[height, width];
        int count = 0;
        for (int x = 0; x < height; x++)
            for (int y = 0; y < width; y++)
                if ((board[x, y] = input[x, y]) >= 0) count++;
        BitArray given = Scan(board, count, height, width);
        return (board, given, count);
    }

    private static BitArray Scan(int[,] board, int count, int height, int width)
    {
        var given = new BitArray(count + 1);
        for (int x = 0; x < height; x++)
            for (int y = 0; y < width; y++)
                if (board[x, y] > 0) given[board[x, y] - 1] = true;
        return given;
    }

    private static void Print(int[,] board)
    {
        if (board == null) {
            WriteLine("No solution");
        } else {
            int w = board.Cast<int>().Where(i => i > 0).Max(i => (int?)Ceiling(Log10(i+1))) ?? 1;
            string e = new string('-', w);
            foreach (int x in Range(0, board.GetLength(0)))
                WriteLine(string.Join(" ", Range(0, board.GetLength(1))
                    .Select(y => board[x, y] < 0 ? e : board[x, y].ToString().PadLeft(w, ' '))));
        }
        WriteLine();
    }

}



Java: import java.util.*;

public class Numbrix {

    final static String[] board = {
        "00,00,00,00,00,00,00,00,00",
        "00,00,46,45,00,55,74,00,00",
        "00,38,00,00,43,00,00,78,00",
        "00,35,00,00,00,00,00,71,00",
        "00,00,33,00,00,00,59,00,00",
        "00,17,00,00,00,00,00,67,00",
        "00,18,00,00,11,00,00,64,00",
        "00,00,24,21,00,01,02,00,00",
        "00,00,00,00,00,00,00,00,00"};

    final static int[][] moves = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    static int[][] grid;
    static int[] clues;
    static int totalToFill;

    public static void main(String[] args) {
        int nRows = board.length + 2;
        int nCols = board[0].split(",").length + 2;
        int startRow = 0, startCol = 0;

        grid = new int[nRows][nCols];
        totalToFill = (nRows - 2) * (nCols - 2);
        List<Integer> lst = new ArrayList<>();

        for (int r = 0; r < nRows; r++) {
            Arrays.fill(grid[r], -1);

            if (r >= 1 && r < nRows - 1) {

                String[] row = board[r - 1].split(",");

                for (int c = 1; c < nCols - 1; c++) {
                    int val = Integer.parseInt(row[c - 1]);
                    if (val > 0)
                        lst.add(val);
                    if (val == 1) {
                        startRow = r;
                        startCol = c;
                    }
                    grid[r][c] = val;
                }
            }
        }

        clues = lst.stream().sorted().mapToInt(i -> i).toArray();

        if (solve(startRow, startCol, 1, 0))
            printResult();
    }

    static boolean solve(int r, int c, int count, int nextClue) {
        if (count > totalToFill)
            return true;

        if (grid[r][c] != 0 && grid[r][c] != count)
            return false;

        if (grid[r][c] == 0 && nextClue < clues.length)
            if (clues[nextClue] == count)
                return false;

        int back = grid[r][c];
        if (back == count)
            nextClue++;

        grid[r][c] = count;
        for (int[] move : moves)
            if (solve(r + move[1], c + move[0], count + 1, nextClue))
                return true;

        grid[r][c] = back;
        return false;
    }

    static void printResult() {
        for (int[] row : grid) {
            for (int i : row) {
                if (i == -1)
                    continue;
                System.out.printf("%2d ", i);
            }
            System.out.println();
        }
    }
}