2010-10-06 21:29:00 +00:00
|
|
|
|
# -*- coding: latin-1 -*-
|
2018-02-19 15:21:05 +01:00
|
|
|
|
# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com
|
2010-10-06 21:29:00 +00:00
|
|
|
|
#
|
2012-01-16 10:40:52 +00:00
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
|
# You may obtain a copy of the License at
|
2010-10-06 21:29:00 +00:00
|
|
|
|
#
|
2012-01-16 10:40:52 +00:00
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
2010-10-06 21:29:00 +00:00
|
|
|
|
#
|
2012-01-16 10:40:52 +00:00
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
|
# limitations under the License.
|
2010-10-06 21:29:00 +00:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
Word square in Google CP Solver.
|
|
|
|
|
|
|
|
|
|
|
|
From http://en.wikipedia.org/wiki/Word_square
|
|
|
|
|
|
'''
|
2012-01-16 10:40:52 +00:00
|
|
|
|
A word square is a special case of acrostic. It consists of a set of words,
|
|
|
|
|
|
all having the same number of letters as the total number of words (the
|
|
|
|
|
|
'order' of the square); when the words are written out in a square grid
|
2010-10-06 21:29:00 +00:00
|
|
|
|
horizontally, the same set of words can be read vertically.
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
Compare with the following models:
|
|
|
|
|
|
* MiniZinc: http://www.hakank.org/minizinc/word_square.mzn
|
|
|
|
|
|
* Comet : http://www.hakank.org/comet/word_square.co
|
|
|
|
|
|
* Choco : http://www.hakank.org/choco/WordSquare.java
|
|
|
|
|
|
* Gecode : http://www.hakank.org/gecode/word_square.cpp
|
|
|
|
|
|
* Gecode : http://www.hakank.org/gecode/word_square2.cpp
|
|
|
|
|
|
* JaCoP : http://www.hakank.org/JaCoP/WordSquare.java
|
|
|
|
|
|
* Zinc: http://hakank.org/minizinc/word_square.zinc
|
|
|
|
|
|
|
2018-02-19 15:21:05 +01:00
|
|
|
|
This model was created by Hakan Kjellerstrand (hakank@gmail.com)
|
2014-05-22 20:13:16 +00:00
|
|
|
|
Also see my other Google CP Solver models:
|
|
|
|
|
|
http://www.hakank.org/google_or_tools/
|
2010-10-06 21:29:00 +00:00
|
|
|
|
"""
|
2016-01-15 01:57:22 +01:00
|
|
|
|
from __future__ import print_function
|
2014-05-22 20:13:16 +00:00
|
|
|
|
import sys
|
|
|
|
|
|
import re
|
2013-12-24 11:35:01 +00:00
|
|
|
|
from ortools.constraint_solver import pywrapcp
|
2010-10-06 21:29:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(words, word_len, num_answers=20):
|
2012-01-16 10:40:52 +00:00
|
|
|
|
|
2014-05-22 20:13:16 +00:00
|
|
|
|
# Create the solver.
|
|
|
|
|
|
solver = pywrapcp.Solver("Problem")
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
# data
|
|
|
|
|
|
#
|
|
|
|
|
|
num_words = len(words)
|
|
|
|
|
|
n = word_len
|
|
|
|
|
|
d, rev = get_dict()
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
# declare variables
|
|
|
|
|
|
#
|
|
|
|
|
|
A = {}
|
|
|
|
|
|
for i in range(num_words):
|
|
|
|
|
|
for j in range(word_len):
|
|
|
|
|
|
A[(i, j)] = solver.IntVar(0, 29, "A(%i,%i)" % (i, j))
|
|
|
|
|
|
|
|
|
|
|
|
A_flat = [A[(i, j)] for i in range(num_words) for j in range(word_len)]
|
|
|
|
|
|
|
|
|
|
|
|
E = [solver.IntVar(0, num_words, "E%i" % i) for i in range(n)]
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
# constraints
|
|
|
|
|
|
#
|
|
|
|
|
|
solver.Add(solver.AllDifferent(E))
|
|
|
|
|
|
|
|
|
|
|
|
# copy the words to a Matrix
|
|
|
|
|
|
for I in range(num_words):
|
|
|
|
|
|
for J in range(word_len):
|
|
|
|
|
|
solver.Add(A[(I, J)] == d[words[I][J]])
|
|
|
|
|
|
|
|
|
|
|
|
for i in range(word_len):
|
|
|
|
|
|
for j in range(word_len):
|
|
|
|
|
|
# This is what I would like to do:
|
|
|
|
|
|
# solver.Add(A[(E[i],j)] == A[(E[j],i)])
|
|
|
|
|
|
|
|
|
|
|
|
# We must use Element explicitly
|
2018-06-11 11:51:18 +02:00
|
|
|
|
solver.Add(
|
|
|
|
|
|
solver.Element(A_flat, E[i] * word_len +
|
|
|
|
|
|
j) == solver.Element(A_flat, E[j] * word_len + i))
|
2014-05-22 20:13:16 +00:00
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
# solution and search
|
|
|
|
|
|
#
|
|
|
|
|
|
solution = solver.Assignment()
|
|
|
|
|
|
solution.Add(E)
|
|
|
|
|
|
|
|
|
|
|
|
# db: DecisionBuilder
|
2018-06-11 11:51:18 +02:00
|
|
|
|
db = solver.Phase(E + A_flat, solver.CHOOSE_FIRST_UNBOUND,
|
2014-05-22 20:13:16 +00:00
|
|
|
|
solver.ASSIGN_MIN_VALUE)
|
|
|
|
|
|
|
|
|
|
|
|
solver.NewSearch(db)
|
|
|
|
|
|
num_solutions = 0
|
|
|
|
|
|
while solver.NextSolution():
|
|
|
|
|
|
# print E
|
|
|
|
|
|
print_solution(E, words)
|
|
|
|
|
|
num_solutions += 1
|
2012-01-16 10:40:52 +00:00
|
|
|
|
|
2014-05-22 20:13:16 +00:00
|
|
|
|
solver.EndSearch()
|
2012-01-16 10:40:52 +00:00
|
|
|
|
|
2016-01-15 01:57:22 +01:00
|
|
|
|
print()
|
|
|
|
|
|
print("num_solutions:", num_solutions)
|
|
|
|
|
|
print("failures:", solver.Failures())
|
|
|
|
|
|
print("branches:", solver.Branches())
|
|
|
|
|
|
print("WallTime:", solver.WallTime())
|
2010-10-06 21:29:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
# convert a character to integer
|
|
|
|
|
|
#
|
|
|
|
|
|
def get_dict():
|
2014-05-22 20:13:16 +00:00
|
|
|
|
alpha = "abcdefghijklmnopqrstuvwxyz<EFBFBD><EFBFBD><EFBFBD>"
|
|
|
|
|
|
d = {}
|
|
|
|
|
|
rev = {}
|
|
|
|
|
|
count = 1
|
|
|
|
|
|
for a in alpha:
|
|
|
|
|
|
d[a] = count
|
|
|
|
|
|
rev[count] = a
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
return d, rev
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_solution(E, words):
|
|
|
|
|
|
# print E
|
|
|
|
|
|
for e in E:
|
2016-01-15 01:57:22 +01:00
|
|
|
|
print(words[e.Value()])
|
|
|
|
|
|
print()
|
2014-05-22 20:13:16 +00:00
|
|
|
|
|
2010-10-06 21:29:00 +00:00
|
|
|
|
|
|
|
|
|
|
def read_words(word_list, word_len, limit):
|
2014-05-22 20:13:16 +00:00
|
|
|
|
dict = {}
|
|
|
|
|
|
all_words = []
|
|
|
|
|
|
count = 0
|
|
|
|
|
|
words = open(word_list).readlines()
|
|
|
|
|
|
for w in words:
|
2016-01-15 01:57:22 +01:00
|
|
|
|
w = w.strip().lower()
|
2014-05-22 20:13:16 +00:00
|
|
|
|
# if len(w) == word_len and not dict.has_key(w) and not re.search("[^a-z<><7A><EFBFBD>]",w) and count < limit:
|
|
|
|
|
|
# Later note: The limit is not needed anymore with Mistral
|
2018-06-11 11:51:18 +02:00
|
|
|
|
if len(w) == word_len and w not in dict and not re.search("[^a-z<><7A><EFBFBD>]", w):
|
2014-05-22 20:13:16 +00:00
|
|
|
|
dict[w] = 1
|
|
|
|
|
|
all_words.append(w)
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
return all_words
|
2010-10-06 21:29:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
word_dict = "/usr/share/dict/words"
|
|
|
|
|
|
word_len = 2
|
|
|
|
|
|
limit = 1000000
|
|
|
|
|
|
num_answers = 20
|
|
|
|
|
|
|
2014-05-22 20:13:16 +00:00
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
if len(sys.argv) > 1:
|
|
|
|
|
|
word_dict = sys.argv[1]
|
|
|
|
|
|
if len(sys.argv) > 2:
|
|
|
|
|
|
word_len = int(sys.argv[2])
|
|
|
|
|
|
if len(sys.argv) > 3:
|
|
|
|
|
|
limit = int(sys.argv[3])
|
|
|
|
|
|
if len(sys.argv) > 4:
|
|
|
|
|
|
num_answers = int(sys.argv[4])
|
|
|
|
|
|
|
|
|
|
|
|
# Note: I have to use a limit, otherwise it seg faults
|
|
|
|
|
|
words = read_words(word_dict, word_len, limit)
|
2016-01-15 01:57:22 +01:00
|
|
|
|
print("It was", len(words), "words")
|
2014-05-22 20:13:16 +00:00
|
|
|
|
main(words, word_len, num_answers)
|