Skip to content

Instantly share code, notes, and snippets.

@jakewilliami
Last active January 15, 2021 00:20
Show Gist options
  • Save jakewilliami/3f4d5bca2d7f5c976d64a75d221ec75e to your computer and use it in GitHub Desktop.
Save jakewilliami/3f4d5bca2d7f5c976d64a75d221ec75e to your computer and use it in GitHub Desktop.
mutable struct Sudoku{T <: Integer} <: AbstractMatrix{T}
A::AbstractMatrix{T} where {T <: Integer}
function Sudoku(A::AbstractMatrix{T}) where {T <: Integer}
size(A, 1) == size(A, 2) || throw(error("Sodokus must be square; received matrix of size $(size(A, 1))×$(size(A, 2))."))
isequal(sqrt(size(A, 1)), isqrt(size(A, 1))) || throw(error("Sudokus must be able to split into equal boxes (e.g., a 9×9 Sudoku has three 3×3 squares). Size given is $(size(A, 1))×$(size(A, 2))."))
new{T}(A)
end
# fill in blank sudoku if needed
Sudoku(::Type{T}, n::Int) where {T <: Integer} = new{T}(fill(zero(T), n, n))
Sudoku(n::Int) = new{Int}(Sudoku(Int, n))
# Use "standard" 9×9 if no size provided
Sudoku(::Type{T}) where {T <: Integer} = new{T}(Sudoku(T, 9))
Sudoku() = new{Int}(Sudoku(9))
# Construct a sudoku given coordinates and values
function Sudoku(n::Int, P::Pair{Tuple{Int, Int}, T}...) where {T <: Integer}
A = zeros(T, n, n)
for (i, v) in P
A[i...] = v
end
new{T}(A)
end
# again, default to 9×9
Sudoku(P::Pair{Tuple{Int, Int}, T}...) where {T <: Integer} = new{T}(9, P...)
end
# abstract array interface for Sudoku struct
Base.size(S::Sudoku) = size(S.A)
Base.getindex(S::Sudoku, i::Int) = getindex(S.A, i)
Base.getindex(S::Sudoku, I::Vararg{Int, N}) where {N} = getindex(S.A, I...)
Base.setindex!(S::Sudoku, v, i::Int) = setindex!(S.A, v, i)
Base.setindex!(S::Sudoku, v, I::Vararg{Int, N}) where {N} = setindex!(S.A, v, I...)
const up_right_corner = '┐'
const up_left_corner = '┌'
const bottom_left_corner = '└'
const bottom_right_corner = '┘'
const up_intersection = '┬'
const left_intersection = '├'
const right_intersection = '┤'
const middle_intersection = '┼'
const bottom_intersection = '┴'
const column = '│'
const row = '─'
const blank = '⋅' # this is the character used for 0s in a Sudoku puzzle
function _format_val(a::Integer)
return iszero(a) ? blank : string(a)
end
function _format_line_segment(r::AbstractVector, col_pos::Int, M::AbstractMatrix)
sep_length = length(r)
line = string()
for k in axes(r, 1)
n_spaces = 1
Δ = maximum((ndigits(i) for i in M[:, (col_pos * sep_length) + k])) - ndigits(r[k])
if Δ ≥ 0
n_spaces = Δ + 1
end
line *= repeat(' ', n_spaces) * _format_val(r[k])
end
return line * ' ' * column
end
function _format_line(r::AbstractVector, M::AbstractMatrix)
sep_length = isqrt(length(r))
line = column
for i in 1:sep_length
abs_sep_pos = sep_length * i
line *= _format_line_segment(r[(abs_sep_pos - sep_length + 1):abs_sep_pos], i - 1, M)
end
return line
end
function _get_sep_line(s::Int, pos_row::Int, M::AbstractMatrix)
sep_length = isqrt(s)
# deal with left-most edges
sep_line = string()
if pos_row == 1
sep_line *= up_left_corner
elseif mod(pos_row, sep_length) == 0
if pos_row == s
sep_line *= bottom_left_corner
else
sep_line *= left_intersection
end
end
# rest of row seperator; TODO: make less convoluted. ATM it works, but I think it can be simplified.
for pos_col in 1:s
sep_line *= repeat(row, maximum((ndigits(i) for i in M[:, pos_col])) + 1)
if mod(pos_col, sep_length) == 0
sep_line *= row
if pos_col == s
if pos_row == 1
sep_line *= up_right_corner
elseif pos_row == s
sep_line *= bottom_right_corner
else
sep_line *= right_intersection
end
elseif pos_row == 1
sep_line *= up_intersection
elseif pos_row == s
sep_line *= bottom_intersection
else
sep_line *= middle_intersection
end
end
end
return sep_line
end
function Base.display(io::IO, S::Sudoku)
sep_length = isqrt(size(S, 1))
max_n_digits = maximum((ndigits(i) for i in S))
println(io, _get_sep_line(size(S, 1), 1, S))
for (i, r) in enumerate(eachrow(S))
println(io, _format_line(r, S))
if iszero(mod(i, sep_length))
println(io, _get_sep_line(size(S, 1), i, S))
end
end
return nothing
end
# fall back on stdout
Base.display(S::Sudoku) = display(stdout, S)
### Examples
const sudoku1 = [
9 3 0 0 0 0 0 4 0
0 0 0 0 4 2 0 9 0
8 0 0 1 9 6 7 0 0
0 0 0 4 7 0 0 0 0
0 2 0 0 0 0 0 6 0
0 0 0 0 2 3 0 0 0
0 0 8 5 3 1 0 0 2
0 9 0 2 8 0 0 0 0
0 7 0 0 0 0 0 5 3
];
const sudoku2 = [
0 0 9 0 1 0 0 6 0
0 0 0 0 6 7 0 0 3
0 3 5 0 0 0 0 0 0
3 0 0 0 0 2 0 0 0
5 1 7 0 0 0 2 3 4
0 0 0 3 0 0 0 0 7
0 0 0 0 0 0 9 1 0
1 0 0 6 8 0 0 0 0
0 7 0 0 3 0 5 0 0
];
const sudoku3 = [
0 3 7 8 6 0 0 4 0
0 0 6 0 0 7 0 0 0
0 2 0 0 3 0 0 0 6
0 8 0 2 0 0 0 0 0
9 0 0 0 0 0 0 0 1
0 0 0 0 0 6 0 9 0
5 0 0 0 7 0 0 3 0
0 0 0 3 0 0 8 0 0
0 1 0 0 8 4 2 6 0
];
const sudoku_small = [
2 0 0 3
0 0 0 1
1 0 0 0
3 0 0 2
];
const sudoku_large = [
1 0 0 2 3 4 0 0 12 0 6 0 0 0 7 0
0 0 8 0 0 0 7 0 0 3 0 0 9 10 6 11
0 12 0 0 10 0 0 1 0 13 0 11 0 0 14 0
3 0 0 15 2 0 0 14 0 0 0 9 0 0 12 0
13 0 0 0 8 0 0 10 0 12 2 0 1 15 0 0
0 11 7 6 0 0 0 16 0 0 0 15 0 0 5 13
0 0 0 10 0 5 15 0 0 4 0 8 0 0 11 0
16 0 0 5 9 12 0 0 1 0 0 0 0 0 8 0
0 2 0 0 0 0 0 13 0 0 12 5 8 0 0 3
0 13 0 0 15 0 3 0 0 14 8 0 16 0 0 0
5 8 0 0 1 0 0 0 2 0 0 0 13 9 15 0
0 0 12 4 0 6 16 0 13 0 0 7 0 0 0 5
0 3 0 0 12 0 0 0 6 0 0 4 11 0 0 16
0 7 0 0 16 0 5 0 14 0 0 1 0 0 2 0
11 1 15 9 0 0 13 0 0 2 0 0 0 14 0 0
0 14 0 0 0 11 0 2 0 0 13 3 5 0 0 12
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment