Skip to content

Commit b9de438

Browse files
Move stuff to bazel. Seems to work
1 parent 3f52c89 commit b9de438

File tree

8 files changed

+274
-74
lines changed

8 files changed

+274
-74
lines changed

sudoku/BUILD

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#
2+
# Copyright (C) 2022 Vaticle
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing,
15+
# software distributed under the License is distributed on an
16+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
# KIND, either express or implied. See the License for the
18+
# specific language governing permissions and limitations
19+
# under the License.
20+
#
21+
exports_files(["requirements.txt"])
22+
load("@rules_python//python:python.bzl", "py_binary", "py_test")
23+
24+
25+
py_library(
26+
name = "solve",
27+
srcs = ["solve.py"],
28+
deps = [
29+
"@vaticle_typedb_client_python//:client_python",
30+
]
31+
)
32+
33+
py_binary(
34+
name = "solver",
35+
srcs = ["solve.py"],
36+
deps = [
37+
"solve",
38+
"@vaticle_typedb_client_python//:client_python"
39+
],
40+
data = [
41+
"sudoku6x6_schema.tql",
42+
"sudoku6x6_data.tql",
43+
44+
"sample/sudoku1.txt",
45+
"sample/sudoku2.txt",
46+
"sample/sudoku3.txt",
47+
"sample/sudoku4.txt",
48+
],
49+
)
50+
51+
py_test(
52+
name = "test",
53+
srcs = ["test.py"],
54+
deps = [
55+
"solve",
56+
"@vaticle_typedb_client_python//:client_python"
57+
],
58+
data = [
59+
"sudoku6x6_schema.tql",
60+
"sudoku6x6_data.tql",
61+
62+
"sample/sudoku1.txt",
63+
"sample/sudoku2.txt",
64+
"sample/sudoku3.txt",
65+
"sample/sudoku4.txt",
66+
"sample/solution1.txt",
67+
"sample/solution2.txt",
68+
"sample/solution3.txt",
69+
"sample/solution4.txt",
70+
]
71+
)
72+
load("@vaticle_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test")
73+
checkstyle_test(
74+
name = "checkstyle",
75+
include = glob(["*"]),
76+
exclude = glob(["*.txt", "*.md", "sample/*"]),
77+
license_type = "apache-header",
78+
size = "small",
79+
)

sudoku/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typedb-client

sudoku/sample/solution1.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
4 5 3 6 2 1
2+
1 2 6 5 3 4
3+
5 1 4 3 6 2
4+
6 3 2 1 4 5
5+
3 4 5 2 1 6
6+
2 6 1 4 5 3

sudoku/sample/solution2.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
4 2 3 5 1 6
2+
5 6 1 3 2 4
3+
1 5 4 2 6 3
4+
2 3 6 4 5 1
5+
3 1 2 6 4 5
6+
6 4 5 1 3 2

sudoku/sample/solution3.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2 1 6 4 5 3
2+
4 3 5 1 2 6
3+
3 6 2 5 4 1
4+
1 5 4 3 6 2
5+
6 4 3 2 1 5
6+
5 2 1 6 3 4

sudoku/sample/solution4.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
4 2 3 5 1 6
2+
5 6 1 3 2 4
3+
1 5 4 2 6 3
4+
2 3 6 4 5 1
5+
3 1 2 6 4 5
6+
6 4 5 1 3 2

sudoku/solve.py

Lines changed: 112 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,141 @@
1-
import math
1+
#
2+
# Copyright (C) 2022 Vaticle
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing,
15+
# software distributed under the License is distributed on an
16+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
# KIND, either express or implied. See the License for the
18+
# specific language governing permissions and limitations
19+
# under the License.
20+
#
21+
222
import timeit
323
from sys import argv
424
from typing import List
525

6-
from typedb.client import TypeDB, TypeDBClient, TypeDBOptions, SessionType, TransactionType
7-
26+
from typedb.client import TypeDB, TypeDBOptions, SessionType, TransactionType
827

928
HOST = TypeDB.DEFAULT_ADDRESS
1029
DATABASE_NAME = "sudoku6x6"
11-
QUERY_TEMPLATE = """
12-
match
13-
$connector-hack = -1 isa connector-hack;
14-
{0}
15-
(
16-
{1}
17-
) isa solution;
18-
limit 1;
19-
"""
20-
21-
def format_sudoku(sudoku: List[List[int]]):
22-
return "\n".join(" ".join(map(str, row)) for row in sudoku)
23-
24-
def database_exists(client: TypeDBClient, db_name: str):
25-
return client.databases().contains(db_name)
26-
27-
def setup(client: TypeDBClient, db_name: str):
28-
if database_exists(client, db_name):
29-
client.databases().get(db_name).delete()
30-
client.databases().create(db_name)
31-
32-
with open("sudoku6x6_schema.tql") as f:
33-
schema = f.read()
34-
35-
with client.session(db_name, SessionType.SCHEMA) as session:
36-
with session.transaction(TransactionType.WRITE) as tx:
37-
tx.query().define(schema)
38-
tx.commit()
39-
40-
with open("sudoku6x6_data.tql") as f:
41-
data = f.read()
42-
43-
with client.session(db_name, SessionType.DATA) as session:
44-
with session.transaction(TransactionType.WRITE) as tx:
45-
tx.query().insert(data)
46-
tx.commit()
47-
48-
def solve(client: TypeDBClient, db_name: str, sudoku: List[List[int]]):
49-
# create_query
50-
non_zero = [(i,j,v) for i,row in enumerate(sudoku, 1) for j,v in enumerate(row, 1) if v != 0]
51-
value_assignments = ["$v%d%d = %d isa number; $v%d%d != $connector-hack;"%(i,j,v,i,j) for (i,j,v) in non_zero]
52-
role_players = [ ["pos%d%d: $v%d%d"%(i,j,i,j) for j in range(1,7)] for i in range(1,7) ]
53-
54-
query = QUERY_TEMPLATE.format(
55-
"\n ".join(value_assignments),
56-
",\n ".join(", ".join(rp) for rp in role_players)
57-
)
58-
59-
with client.session(db_name, SessionType.DATA) as session:
60-
with session.transaction(TransactionType.READ, TypeDBOptions().set_infer(True)) as tx:
61-
result = list(tx.query().match(query))
62-
63-
if result:
64-
return [ [result[0].get("v%d%d"%(i,j)).get_value() for j in range(1,7)] for i in range(1,7) ]
65-
else:
66-
return None
6730

31+
class Solver:
32+
SCHEMA_FILE = "sudoku/sudoku6x6_schema.tql"
33+
DATA_FILE = "sudoku/sudoku6x6_data.tql"
34+
QUERY_TEMPLATE = """
35+
match
36+
$connector-hack = -1 isa connector-hack;
37+
{0}
38+
(
39+
{1}
40+
) isa solution;
41+
limit 1;
42+
"""
43+
44+
def __init__(self, host: str, db_name: str):
45+
self.client = TypeDB.core_client(host)
46+
self.db_name = db_name
47+
48+
def read_sudoku(self, filename: str):
49+
with open(filename) as sudoku_file:
50+
sudoku = [list(map(int, row.split())) for row in sudoku_file if row.strip()]
51+
assert len(sudoku) == 6 and all(len(row)==6 for row in sudoku)
52+
return sudoku
53+
54+
def format_sudoku(self, sudoku: List[List[int]]):
55+
return "\n".join(" ".join(map(str, row)) for row in sudoku)
56+
57+
def database_exists(self):
58+
return self.client.databases().contains(self.db_name)
59+
60+
def setup(self, force=False):
61+
if self.client.databases().contains(self.db_name):
62+
if force:
63+
self.client.databases().get(self.db_name).delete()
64+
else:
65+
return
66+
67+
print("Setting up in database: '%s'..." % self.db_name)
68+
self.client.databases().create(self.db_name)
69+
70+
with open(Solver.SCHEMA_FILE) as f:
71+
schema = f.read()
72+
73+
with self.client.session(self.db_name, SessionType.SCHEMA) as session:
74+
with session.transaction(TransactionType.WRITE) as tx:
75+
tx.query().define(schema)
76+
tx.commit()
77+
78+
with open(Solver.DATA_FILE) as f:
79+
data = f.read()
80+
81+
with self.client.session(self.db_name, SessionType.DATA) as session:
82+
with session.transaction(TransactionType.WRITE) as tx:
83+
tx.query().insert(data)
84+
tx.commit()
85+
86+
def cleanup(self, delete_database=False):
87+
if delete_database and self.client.databases().contains(self.db_name):
88+
self.client.databases().get(self.db_name).delete()
89+
self.client.close()
90+
91+
def solve(self, sudoku: List[List[int]]):
92+
# create_query
93+
non_zero = [(i,j,v) for i,row in enumerate(sudoku, 1) for j,v in enumerate(row, 1) if v != 0]
94+
value_assignments = ["$v%d%d = %d isa number; $v%d%d != $connector-hack;"%(i,j,v,i,j) for (i,j,v) in non_zero]
95+
role_players = [ ["pos%d%d: $v%d%d"%(i,j,i,j) for j in range(1,7)] for i in range(1,7) ]
96+
97+
query = Solver.QUERY_TEMPLATE.format(
98+
"\n ".join(value_assignments),
99+
",\n ".join(", ".join(rp) for rp in role_players)
100+
)
101+
102+
with self.client.session(self.db_name, SessionType.DATA) as session:
103+
with session.transaction(TransactionType.READ, TypeDBOptions().set_infer(True)) as tx:
104+
result = list(tx.query().match(query))
105+
106+
if result:
107+
return [ [result[0].get("v%d%d"%(i,j)).get_value() for j in range(1,7)] for i in range(1,7) ]
108+
else:
109+
return None
68110

69111

70112
def main():
71113
if len(argv) != 2:
72114
print("Usage:")
73115
print("python3 %s setup: Loads required schema & data" % argv[0])
74116
print("python3 %s <sudoku_file>: Reads & solves the sudoku in <sudoku_file>" % argv[0])
117+
return
75118

76-
client = TypeDB.core_client(HOST)
119+
solver = Solver(HOST, DATABASE_NAME)
77120
if argv[1] == "setup":
78-
print("Setting up in database: '%s'..." % DATABASE_NAME)
79-
setup(client, DATABASE_NAME)
121+
solver.setup(force=True)
80122
return
81-
if not database_exists(client, DATABASE_NAME):
82-
print("Database '%s' does not exist. Setting up..." % DATABASE_NAME)
83-
setup(client, DATABASE_NAME)
84-
85-
with open(argv[1]) as sudoku_file:
86-
sudoku = [list(map(int, row.split())) for row in sudoku_file if row.strip()]
87123

88-
assert len(sudoku) == 6 and all(len(row)==6 for row in sudoku)
124+
solver.setup()
125+
sudoku = solver.read_sudoku(argv[1])
89126

90127
print("Solving:")
91-
print(format_sudoku(sudoku), "\n")
128+
print(solver.format_sudoku(sudoku), "\n")
92129

93130
time_start = timeit.default_timer()
94-
solution = solve(client, DATABASE_NAME, sudoku)
95-
time_taken_ms = math.ceil((timeit.default_timer() - time_start) * 1000)
131+
solution = solver.solve(sudoku)
132+
time_taken_ms = int((timeit.default_timer() - time_start) * 1000 + 1)
96133
if solution:
97134
print("Found solution in " + str(time_taken_ms) + " ms:")
98-
print(format_sudoku(solution))
135+
print(solver.format_sudoku(solution))
99136
else:
100137
print("No solution (took " + str(time_taken_ms) + " ms)")
101138

139+
solver.cleanup()
102140

103-
if __name__=="__main__": main()
141+
if __name__=="__main__": main()

sudoku/test.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#
2+
# Copyright (C) 2022 Vaticle
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing,
15+
# software distributed under the License is distributed on an
16+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
# KIND, either express or implied. See the License for the
18+
# specific language governing permissions and limitations
19+
# under the License.
20+
#
21+
import timeit
22+
from os.path import join as path_join
23+
import unittest
24+
25+
from typedb.client import TypeDB
26+
27+
from solve import Solver
28+
29+
30+
class Test(unittest.TestCase):
31+
DATABASE_NAME = "test_sudoku6x6"
32+
SAMPLE_PATH = "sudoku/sample"
33+
SAMPLES = [("sudoku1.txt", "solution1.txt"),
34+
("sudoku2.txt", "solution2.txt"),
35+
("sudoku3.txt", "solution3.txt"),
36+
("sudoku4.txt", "solution4.txt")
37+
]
38+
39+
def setUp(self):
40+
self.solver = Solver(TypeDB.DEFAULT_ADDRESS, Test.DATABASE_NAME)
41+
self.solver.setup(True)
42+
print("Loaded the " + Test.DATABASE_NAME + " schema")
43+
44+
def test_samples(self):
45+
for (sample_file, solution_file) in Test.SAMPLES:
46+
sudoku = self.solver.read_sudoku(path_join(Test.SAMPLE_PATH, sample_file))
47+
time_start = timeit.default_timer()
48+
solver_solution = self.solver.solve(sudoku)
49+
print("Solved %s in %d ms"% (sample_file, int(1 + 1000 * (timeit.default_timer() - time_start))))
50+
expected_solution = self.solver.read_sudoku(path_join(Test.SAMPLE_PATH, solution_file))
51+
self.assertEqual(expected_solution, solver_solution)
52+
53+
def tearDown(self):
54+
self.solver.cleanup(True)
55+
56+
57+
if __name__ == '__main__':
58+
unittest.main()

0 commit comments

Comments
 (0)