aboutsummaryrefslogtreecommitdiff
path: root/challenge-086/lubos-kolouch/python/ch-2.py
blob: f827eec8d61d9d970333815606bf4f4f1312fdab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!env python
"""
#===============================================================================
#
#         FILE: ch-2.py
#
#        USAGE: ./ch-2.py
#
#  DESCRIPTION: Perl Weekly Challenge 086
#  				Task 2
#  				Sudoku Puzzle
#
#  				Uses backtrack algorithm
#
#       AUTHOR: Lubos Kolouch
#      CREATED: 11/15/2020 01:27:06 PM
#===============================================================================
"""

import re
from copy import deepcopy


def load_puzzle(puzzle_input):
    """ load the puzzle from the input """

    puzzle = []
    for row in puzzle_input:
        row = re.sub("_", "0", row)

        puzzle.append(list(map(int, row.split(' '))))

    return puzzle


def get_valid_options(puzzle, row, col):
    """ eliminate numbers 1-9 that are already taken """

    valid_options = {}

    for i in range(10):
        valid_options[i] = 1

    # eliminate already taken numbers
    for i in range(9):
        try:
            del valid_options[puzzle[row][i]]
        except:
            pass

    for i in range(9):
        try:
            del valid_options[puzzle[i][col]]
        except:
            pass

    if not valid_options:
        return

    # eliminate quadrant

    for row_shift in range(3):
        for col_shift in range(3):
            row_pos = row // 3 * 3 + row_shift
            col_pos = col // 3 * 3 + col_shift

            try:
                del valid_options[puzzle[row_pos][col_pos]]
            except:
                pass

    return valid_options


def solve_position(puzzle_input):
    # backtrack recursively to find the solution

    next_puzzle = deepcopy(puzzle_input)

    for row, row_data in enumerate(next_puzzle):
        for col, item in enumerate(row_data):

            if item != 0:
                continue

            valid_options = get_valid_options(puzzle=next_puzzle, row=row, col=col)

            if not valid_options:
                # nothing left to try, check if the solution is complete

                for test_row in next_puzzle:
                    for test_item in test_row:
                        if item == 0:
                            return

                return next_puzzle

            for next_key in valid_options:
                next_puzzle[row][col] = next_key

                next_iteration = solve_position(deepcopy(next_puzzle))

                if next_iteration:
                    return next_iteration

    # great, we could fill all empty positions!
    return next_puzzle


def print_puzzle(puzzle):
    """ print the puzzle """

    for row in puzzle:
        print(' '.join(list(map(str, row))))
    print()


def solve_sudoku(puzzle):
    """ let's solve the puzzle! """

    puzzle = load_puzzle(puzzle)
    print_puzzle(puzzle)
    solution = solve_position(puzzle)
    print_puzzle(solution)
    return solution


assert solve_sudoku(['_ _ _ 2 6 _ 7 _ 1',
                     '6 8 _ _ 7 _ _ 9 _',
                     '1 9 _ _ _ 4 5 _ _',
                     '8 2 _ 1 _ _ _ 4 _',
                     '_ _ 4 6 _ 2 9 _ _',
                     '_ 5 _ _ _ 3 _ 2 8',
                     '_ _ 9 3 _ _ _ 7 4',
                     '_ 4 _ _ 5 _ _ 3 6',
                     '7 _ 3 _ 1 8 _ _ _']) == [
                        [4, 3, 5, 2, 6, 9, 7, 8, 1],
                        [6, 8, 2, 5, 7, 1, 4, 9, 3],
                        [1, 9, 7, 8, 3, 4, 5, 6, 2],
                        [8, 2, 6, 1, 9, 5, 3, 4, 7],
                        [3, 7, 4, 6, 8, 2, 9, 1, 5],
                        [9, 5, 1, 7, 4, 3, 6, 2, 8],
                        [5, 1, 9, 3, 2, 6, 8, 7, 4],
                        [2, 4, 8, 9, 5, 7, 1, 3, 6],
                        [7, 6, 3, 4, 1, 8, 2, 5, 9], ]