str8ts_solver 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- str8ts_solver-0.1.0/LICENSE +21 -0
- str8ts_solver-0.1.0/PKG-INFO +30 -0
- str8ts_solver-0.1.0/README.md +3 -0
- str8ts_solver-0.1.0/pyproject.toml +34 -0
- str8ts_solver-0.1.0/setup.cfg +4 -0
- str8ts_solver-0.1.0/str8ts_solver/__init__.py +0 -0
- str8ts_solver-0.1.0/str8ts_solver/output_utils.py +82 -0
- str8ts_solver-0.1.0/str8ts_solver/solver.py +176 -0
- str8ts_solver-0.1.0/str8ts_solver.egg-info/PKG-INFO +30 -0
- str8ts_solver-0.1.0/str8ts_solver.egg-info/SOURCES.txt +13 -0
- str8ts_solver-0.1.0/str8ts_solver.egg-info/dependency_links.txt +1 -0
- str8ts_solver-0.1.0/str8ts_solver.egg-info/requires.txt +15 -0
- str8ts_solver-0.1.0/str8ts_solver.egg-info/top_level.txt +1 -0
- str8ts_solver-0.1.0/tests/test_solve.py +33 -0
- str8ts_solver-0.1.0/tests/test_to_string.py +63 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Piotr Idzik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: str8ts_solver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: str8ts solver using z3
|
|
5
|
+
Author: Piotr Idzik
|
|
6
|
+
Author-email: vil02_str8ts_solver@10g.pl
|
|
7
|
+
Project-URL: repository, https://github.com/vil02/str8ts_solver
|
|
8
|
+
Keywords: str8ts,str8ts solver,z3
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: z3-solver==4.15.4.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: black==25.12.0; extra == "dev"
|
|
15
|
+
Requires-Dist: coverage==7.13.0; extra == "dev"
|
|
16
|
+
Requires-Dist: flake8-bugbear==25.11.29; extra == "dev"
|
|
17
|
+
Requires-Dist: flake8-pytest-style==2.2.0; extra == "dev"
|
|
18
|
+
Requires-Dist: flake8==7.3.0; extra == "dev"
|
|
19
|
+
Requires-Dist: isort==7.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy==1.19.1; extra == "dev"
|
|
21
|
+
Requires-Dist: pylint==4.0.4; extra == "dev"
|
|
22
|
+
Requires-Dist: pyright[nodejs]==1.1.407; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff==0.14.9; extra == "dev"
|
|
25
|
+
Requires-Dist: xenon==0.9.3; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# str8ts_solver/
|
|
29
|
+
|
|
30
|
+
[Str8ts](https://en.wikipedia.org/wiki/Str8ts) solver using [z3](https://github.com/Z3Prover/z3).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "str8ts_solver"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "str8ts solver using z3"
|
|
5
|
+
authors = [
|
|
6
|
+
{email = "vil02_str8ts_solver@10g.pl"},
|
|
7
|
+
{name = "Piotr Idzik"}
|
|
8
|
+
]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
keywords = ["str8ts", "str8ts solver", "z3"]
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
"z3-solver==4.15.4.0"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"black==25.12.0",
|
|
20
|
+
"coverage==7.13.0",
|
|
21
|
+
"flake8-bugbear==25.11.29",
|
|
22
|
+
"flake8-pytest-style==2.2.0",
|
|
23
|
+
"flake8==7.3.0",
|
|
24
|
+
"isort==7.0.0",
|
|
25
|
+
"mypy==1.19.1",
|
|
26
|
+
"pylint==4.0.4",
|
|
27
|
+
"pyright[nodejs]==1.1.407",
|
|
28
|
+
"pytest==9.0.2",
|
|
29
|
+
"ruff==0.14.9",
|
|
30
|
+
"xenon==0.9.3"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
repository = "https://github.com/vil02/str8ts_solver"
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
def _bottom_row_to_string(
|
|
2
|
+
in_row_num: int, row_size: int, blocked: set[tuple[int, int]]
|
|
3
|
+
) -> str:
|
|
4
|
+
res = []
|
|
5
|
+
for x_pos in range(row_size):
|
|
6
|
+
if (x_pos, in_row_num) in blocked:
|
|
7
|
+
res.append("###")
|
|
8
|
+
else:
|
|
9
|
+
res.append(" ")
|
|
10
|
+
return "|" + "|".join(res) + "|"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _top_row_to_string(
|
|
14
|
+
in_row_num: int,
|
|
15
|
+
row_size: int,
|
|
16
|
+
blocked: set[tuple[int, int]],
|
|
17
|
+
known: dict[tuple[int, int], int],
|
|
18
|
+
) -> str:
|
|
19
|
+
res = []
|
|
20
|
+
for x_pos in range(row_size):
|
|
21
|
+
cur_pos = (x_pos, in_row_num)
|
|
22
|
+
if cur_pos in blocked:
|
|
23
|
+
res.append("###")
|
|
24
|
+
elif cur_pos in known:
|
|
25
|
+
res.append(" !")
|
|
26
|
+
else:
|
|
27
|
+
res.append(" ")
|
|
28
|
+
return "|" + "|".join(res) + "|"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _mid_row_to_string(
|
|
32
|
+
in_row_num: int,
|
|
33
|
+
row_size: int,
|
|
34
|
+
blocked: set[tuple[int, int]],
|
|
35
|
+
known: dict[tuple[int, int], int],
|
|
36
|
+
solved: dict[tuple[int, int], int],
|
|
37
|
+
) -> str:
|
|
38
|
+
res = []
|
|
39
|
+
for x_pos in range(row_size):
|
|
40
|
+
cur_pos = (x_pos, in_row_num)
|
|
41
|
+
if cur_pos in blocked:
|
|
42
|
+
if cur_pos in known:
|
|
43
|
+
res.append(f"#{known[cur_pos]}#")
|
|
44
|
+
else:
|
|
45
|
+
res.append("###")
|
|
46
|
+
else:
|
|
47
|
+
if cur_pos in known:
|
|
48
|
+
res.append(f" {known[cur_pos]} ")
|
|
49
|
+
else:
|
|
50
|
+
res.append(f" {solved.get(cur_pos, ' ')} ")
|
|
51
|
+
return "|" + "|".join(res) + "|"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _row_to_string(
|
|
55
|
+
in_row_num: int,
|
|
56
|
+
row_size: int,
|
|
57
|
+
blocked: set[tuple[int, int]],
|
|
58
|
+
known: dict[tuple[int, int], int],
|
|
59
|
+
solved: dict[tuple[int, int], int],
|
|
60
|
+
) -> str:
|
|
61
|
+
top_row = _top_row_to_string(in_row_num, row_size, blocked, known)
|
|
62
|
+
mid_row = _mid_row_to_string(in_row_num, row_size, blocked, known, solved)
|
|
63
|
+
bottom_row = _bottom_row_to_string(in_row_num, row_size, blocked)
|
|
64
|
+
return "\n".join((top_row, mid_row, bottom_row))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _row_separator(in_row_size: int) -> str:
|
|
68
|
+
return str("+---" * in_row_size) + "+"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def to_string(
|
|
72
|
+
size: tuple[int, int],
|
|
73
|
+
blocked: set[tuple[int, int]],
|
|
74
|
+
known: dict[tuple[int, int], int],
|
|
75
|
+
solved: dict[tuple[int, int], int],
|
|
76
|
+
) -> str:
|
|
77
|
+
row_separator = _row_separator(size[0])
|
|
78
|
+
res = [row_separator]
|
|
79
|
+
for y_pos in range(size[1]):
|
|
80
|
+
res.append(_row_to_string(y_pos, size[0], blocked, known, solved))
|
|
81
|
+
res.append(row_separator)
|
|
82
|
+
return "\n".join(res)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
|
|
3
|
+
import z3 # type: ignore
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _shift(pos: tuple[int, int], shift: tuple[int, int]) -> tuple[int, int]:
|
|
7
|
+
res = tuple(sum(_) for _ in zip(pos, shift, strict=True))
|
|
8
|
+
assert len(res) == 2
|
|
9
|
+
return res
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _extract_block(
|
|
13
|
+
unknowns, pos: tuple[int, int], direction: tuple[int, int]
|
|
14
|
+
) -> list[tuple[int, int]]:
|
|
15
|
+
assert pos in unknowns
|
|
16
|
+
res = [pos]
|
|
17
|
+
cur_pos = _shift(pos, direction)
|
|
18
|
+
while cur_pos in unknowns:
|
|
19
|
+
res.append(cur_pos)
|
|
20
|
+
cur_pos = _shift(cur_pos, direction)
|
|
21
|
+
return res
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _gen_horizontal_blocks(size: tuple[int, int], unknowns):
|
|
25
|
+
for y_pos in range(size[1]):
|
|
26
|
+
x_pos = 0
|
|
27
|
+
while x_pos < size[0]:
|
|
28
|
+
if (x_pos, y_pos) in unknowns:
|
|
29
|
+
cur_block = _extract_block(unknowns, (x_pos, y_pos), (1, 0))
|
|
30
|
+
if len(cur_block) > 1:
|
|
31
|
+
yield cur_block
|
|
32
|
+
x_pos += len(cur_block)
|
|
33
|
+
else:
|
|
34
|
+
x_pos += 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _gen_vertical_blocks(size: tuple[int, int], unknowns):
|
|
38
|
+
for x_pos in range(size[0]):
|
|
39
|
+
y_pos = 0
|
|
40
|
+
while y_pos < size[1]:
|
|
41
|
+
if (x_pos, y_pos) in unknowns:
|
|
42
|
+
cur_block = _extract_block(unknowns, (x_pos, y_pos), (0, 1))
|
|
43
|
+
if len(cur_block) > 1:
|
|
44
|
+
yield cur_block
|
|
45
|
+
y_pos += len(cur_block)
|
|
46
|
+
else:
|
|
47
|
+
y_pos += 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _define_unknowns(size: tuple[int, int], blocked: set[tuple[int, int]]):
|
|
51
|
+
return {
|
|
52
|
+
(x_pos, y_pos): z3.Int(f"u_{x_pos}_{y_pos}")
|
|
53
|
+
for x_pos, y_pos in itertools.product(range(size[0]), range(size[1]))
|
|
54
|
+
if (x_pos, y_pos) not in blocked
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _add_basic_constrains(solver, unknowns, known, value_limit) -> None:
|
|
59
|
+
for pos, unknown in unknowns.items():
|
|
60
|
+
if pos in known:
|
|
61
|
+
solver.add(unknown == known[pos])
|
|
62
|
+
else:
|
|
63
|
+
solver.add(0 < unknown)
|
|
64
|
+
solver.add(unknown <= value_limit)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _add_different_than_if_exists(
|
|
68
|
+
solver, unknowns, unknown_id: tuple[int, int], value: int
|
|
69
|
+
) -> None:
|
|
70
|
+
if unknown_id in unknowns:
|
|
71
|
+
solver.add(unknowns[unknown_id] != value)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _add_constrains_from_blocked(
|
|
75
|
+
solver,
|
|
76
|
+
unknowns,
|
|
77
|
+
size: tuple[int, int],
|
|
78
|
+
blocked: set[tuple[int, int]],
|
|
79
|
+
known: dict[tuple[int, int], int],
|
|
80
|
+
) -> None:
|
|
81
|
+
for cur_blocked in blocked:
|
|
82
|
+
if cur_blocked in known:
|
|
83
|
+
cur_blocked_x, cur_blocked_y = cur_blocked
|
|
84
|
+
for x_pos in range(size[0]):
|
|
85
|
+
_add_different_than_if_exists(
|
|
86
|
+
solver, unknowns, (x_pos, cur_blocked_y), known[cur_blocked]
|
|
87
|
+
)
|
|
88
|
+
for y_pos in range(size[1]):
|
|
89
|
+
_add_different_than_if_exists(
|
|
90
|
+
solver, unknowns, (cur_blocked_x, y_pos), known[cur_blocked]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _add_block_constrain(
|
|
95
|
+
solver, unknowns, block: list[tuple[int, int]], block_id: str
|
|
96
|
+
) -> None:
|
|
97
|
+
cur_limit = z3.Int(block_id)
|
|
98
|
+
solver.add(cur_limit > 0)
|
|
99
|
+
for _ in block:
|
|
100
|
+
solver.add(cur_limit <= unknowns[_])
|
|
101
|
+
solver.add(unknowns[_] < cur_limit + len(block))
|
|
102
|
+
solver.add(z3.Distinct(*(unknowns[_] for _ in block)))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _add_all_block_constrains(solver, unknowns, size: tuple[int, int]) -> None:
|
|
106
|
+
for block_num, cur_block in enumerate(_gen_horizontal_blocks(size, unknowns)):
|
|
107
|
+
_add_block_constrain(solver, unknowns, cur_block, f"h_{block_num}")
|
|
108
|
+
|
|
109
|
+
for block_num, cur_block in enumerate(_gen_vertical_blocks(size, unknowns)):
|
|
110
|
+
_add_block_constrain(solver, unknowns, cur_block, f"v_{block_num}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _add_entries_in_all_rows_are_unique_constrains(
|
|
114
|
+
solver, unknowns, size: tuple[int, int]
|
|
115
|
+
):
|
|
116
|
+
for y_pos in range(size[1]):
|
|
117
|
+
cur_row = [
|
|
118
|
+
unknowns[(x_pos, y_pos)]
|
|
119
|
+
for x_pos in range(size[0])
|
|
120
|
+
if (x_pos, y_pos) in unknowns
|
|
121
|
+
]
|
|
122
|
+
solver.add(z3.Distinct(*cur_row))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _add_entries_in_all_cols_are_unique_constrains(
|
|
126
|
+
solver, unknowns, size: tuple[int, int]
|
|
127
|
+
):
|
|
128
|
+
for x_pos in range(size[0]):
|
|
129
|
+
cur_col = [
|
|
130
|
+
unknowns[(x_pos, y_pos)]
|
|
131
|
+
for y_pos in range(size[1])
|
|
132
|
+
if (x_pos, y_pos) in unknowns
|
|
133
|
+
]
|
|
134
|
+
solver.add(z3.Distinct(*cur_col))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _create_solver(
|
|
138
|
+
size: tuple[int, int],
|
|
139
|
+
blocked: set[tuple[int, int]],
|
|
140
|
+
known: dict[tuple[int, int], int],
|
|
141
|
+
):
|
|
142
|
+
solver = z3.Solver()
|
|
143
|
+
unknowns = _define_unknowns(size, blocked)
|
|
144
|
+
_add_basic_constrains(solver, unknowns, known, max(size))
|
|
145
|
+
_add_constrains_from_blocked(solver, unknowns, size, blocked, known)
|
|
146
|
+
_add_all_block_constrains(solver, unknowns, size)
|
|
147
|
+
_add_entries_in_all_cols_are_unique_constrains(solver, unknowns, size)
|
|
148
|
+
_add_entries_in_all_rows_are_unique_constrains(solver, unknowns, size)
|
|
149
|
+
|
|
150
|
+
return unknowns, solver
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _read_solution(unknowns, solver) -> dict[tuple[int, int], int]:
|
|
154
|
+
assert solver.check() == z3.sat
|
|
155
|
+
model = solver.model()
|
|
156
|
+
return {_: model[unknowns[_]].as_long() for _ in unknowns}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def solve(
|
|
160
|
+
size: tuple[int, int],
|
|
161
|
+
blocked: set[tuple[int, int]],
|
|
162
|
+
known: dict[tuple[int, int], int],
|
|
163
|
+
) -> dict[tuple[int, int], int] | None:
|
|
164
|
+
unknowns, solver = _create_solver(size, blocked, known)
|
|
165
|
+
if solver.check() != z3.sat:
|
|
166
|
+
return None
|
|
167
|
+
solved = _read_solution(unknowns, solver)
|
|
168
|
+
assert all(solved[_] == known[_] for _ in solved if _ in known)
|
|
169
|
+
for cur_block in itertools.chain(
|
|
170
|
+
_gen_horizontal_blocks(size, unknowns), _gen_vertical_blocks(size, unknowns)
|
|
171
|
+
):
|
|
172
|
+
solved_values = {solved[_] for _ in cur_block}
|
|
173
|
+
assert len(solved_values) == len(cur_block)
|
|
174
|
+
assert solved_values == set(range(min(solved_values), max(solved_values) + 1))
|
|
175
|
+
|
|
176
|
+
return solved
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: str8ts_solver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: str8ts solver using z3
|
|
5
|
+
Author: Piotr Idzik
|
|
6
|
+
Author-email: vil02_str8ts_solver@10g.pl
|
|
7
|
+
Project-URL: repository, https://github.com/vil02/str8ts_solver
|
|
8
|
+
Keywords: str8ts,str8ts solver,z3
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: z3-solver==4.15.4.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: black==25.12.0; extra == "dev"
|
|
15
|
+
Requires-Dist: coverage==7.13.0; extra == "dev"
|
|
16
|
+
Requires-Dist: flake8-bugbear==25.11.29; extra == "dev"
|
|
17
|
+
Requires-Dist: flake8-pytest-style==2.2.0; extra == "dev"
|
|
18
|
+
Requires-Dist: flake8==7.3.0; extra == "dev"
|
|
19
|
+
Requires-Dist: isort==7.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: mypy==1.19.1; extra == "dev"
|
|
21
|
+
Requires-Dist: pylint==4.0.4; extra == "dev"
|
|
22
|
+
Requires-Dist: pyright[nodejs]==1.1.407; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff==0.14.9; extra == "dev"
|
|
25
|
+
Requires-Dist: xenon==0.9.3; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# str8ts_solver/
|
|
29
|
+
|
|
30
|
+
[Str8ts](https://en.wikipedia.org/wiki/Str8ts) solver using [z3](https://github.com/Z3Prover/z3).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
str8ts_solver/__init__.py
|
|
5
|
+
str8ts_solver/output_utils.py
|
|
6
|
+
str8ts_solver/solver.py
|
|
7
|
+
str8ts_solver.egg-info/PKG-INFO
|
|
8
|
+
str8ts_solver.egg-info/SOURCES.txt
|
|
9
|
+
str8ts_solver.egg-info/dependency_links.txt
|
|
10
|
+
str8ts_solver.egg-info/requires.txt
|
|
11
|
+
str8ts_solver.egg-info/top_level.txt
|
|
12
|
+
tests/test_solve.py
|
|
13
|
+
tests/test_to_string.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
z3-solver==4.15.4.0
|
|
2
|
+
|
|
3
|
+
[dev]
|
|
4
|
+
black==25.12.0
|
|
5
|
+
coverage==7.13.0
|
|
6
|
+
flake8-bugbear==25.11.29
|
|
7
|
+
flake8-pytest-style==2.2.0
|
|
8
|
+
flake8==7.3.0
|
|
9
|
+
isort==7.0.0
|
|
10
|
+
mypy==1.19.1
|
|
11
|
+
pylint==4.0.4
|
|
12
|
+
pyright[nodejs]==1.1.407
|
|
13
|
+
pytest==9.0.2
|
|
14
|
+
ruff==0.14.9
|
|
15
|
+
xenon==0.9.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
str8ts_solver
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from str8ts_solver.solver import solve
|
|
4
|
+
|
|
5
|
+
from . import samples
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _extract_test_case(
|
|
9
|
+
sample: samples.Sample,
|
|
10
|
+
) -> tuple[
|
|
11
|
+
tuple[int, int],
|
|
12
|
+
set[tuple[int, int]],
|
|
13
|
+
dict[tuple[int, int], int],
|
|
14
|
+
dict[tuple[int, int], int],
|
|
15
|
+
]:
|
|
16
|
+
return sample.size, sample.blocked, sample.known, sample.solved
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize(
|
|
20
|
+
("size", "blocked", "known", "expected"),
|
|
21
|
+
[
|
|
22
|
+
_extract_test_case(samples.SAMPLE_1),
|
|
23
|
+
_extract_test_case(samples.SAMPLE_2),
|
|
24
|
+
_extract_test_case(samples.SAMPLE_3),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_solve(
|
|
28
|
+
size: tuple[int, int],
|
|
29
|
+
blocked: set[tuple[int, int]],
|
|
30
|
+
known: dict[tuple[int, int], int],
|
|
31
|
+
expected: dict[tuple[int, int], int],
|
|
32
|
+
) -> None:
|
|
33
|
+
assert solve(size, blocked, known) == expected
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from str8ts_solver.output_utils import to_string
|
|
4
|
+
|
|
5
|
+
from . import samples
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _extract_test_case_blank(
|
|
9
|
+
sample: samples.Sample,
|
|
10
|
+
) -> tuple[
|
|
11
|
+
tuple[int, int],
|
|
12
|
+
set[tuple[int, int]],
|
|
13
|
+
dict[tuple[int, int], int],
|
|
14
|
+
str,
|
|
15
|
+
]:
|
|
16
|
+
return sample.size, sample.blocked, sample.known, sample.blank_str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize(
|
|
20
|
+
("size", "blocked", "known", "expected"),
|
|
21
|
+
[
|
|
22
|
+
_extract_test_case_blank(samples.SAMPLE_1),
|
|
23
|
+
_extract_test_case_blank(samples.SAMPLE_2),
|
|
24
|
+
_extract_test_case_blank(samples.SAMPLE_3),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_to_string_with_blank(
|
|
28
|
+
size: tuple[int, int],
|
|
29
|
+
blocked: set[tuple[int, int]],
|
|
30
|
+
known: dict[tuple[int, int], int],
|
|
31
|
+
expected: str,
|
|
32
|
+
) -> None:
|
|
33
|
+
assert to_string(size, blocked, known, {}) == expected
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _extract_test_case_solved(
|
|
37
|
+
sample: samples.Sample,
|
|
38
|
+
) -> tuple[
|
|
39
|
+
tuple[int, int],
|
|
40
|
+
set[tuple[int, int]],
|
|
41
|
+
dict[tuple[int, int], int],
|
|
42
|
+
dict[tuple[int, int], int],
|
|
43
|
+
str,
|
|
44
|
+
]:
|
|
45
|
+
return sample.size, sample.blocked, sample.known, sample.solved, sample.solved_str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize(
|
|
49
|
+
("size", "blocked", "known", "solved", "expected"),
|
|
50
|
+
[
|
|
51
|
+
_extract_test_case_solved(samples.SAMPLE_1),
|
|
52
|
+
_extract_test_case_solved(samples.SAMPLE_2),
|
|
53
|
+
_extract_test_case_solved(samples.SAMPLE_3),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_to_string_with_solved(
|
|
57
|
+
size: tuple[int, int],
|
|
58
|
+
blocked: set[tuple[int, int]],
|
|
59
|
+
known: dict[tuple[int, int], int],
|
|
60
|
+
solved: dict[tuple[int, int], int],
|
|
61
|
+
expected: str,
|
|
62
|
+
) -> None:
|
|
63
|
+
assert to_string(size, blocked, known, solved) == expected
|