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+
222import timeit
323from sys import argv
424from 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
928HOST = TypeDB .DEFAULT_ADDRESS
1029DATABASE_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
70112def 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 ()
0 commit comments