# file cryptarithme.py
# brief solution générique des cryptarithme
# author Johan Lachenal
# date 17 Octobre 2022
# ce fichier contient l'algorithme générique résolvant les cryptarithmes, plus tard il contiendra un générateur aléatoire de cryptarithme

# Pour que le résolveur d'énigmes de cryptarithme fonctionne, il est nécessaire d'effectuer la commande :
# pip install cpmpy 

import numpy as np
import re
import itertools
from cpmpy import *

# brief résout un cryptarithme donné et affiche le résultat
# param ListeMots liste des mots du cryptarithme entré
# param ListeOperateurs liste des opérateurs du cryptarithme entré
# param ListeLettres liste des lettres associés à des variables du cryptarithme
# param ListePosition liste temporaire servant à prendre les positions des variables dans la liste ListeLettres pour un mot que l'on remet ensuite a null
# param ListePostion liste de liste de positions des variables dans la liste ListeLettres
# param exposant10 liste temporaire servant à prendre les exposants pour chacune des lettres d'un mot
# param exposants10 liste de liste d'exposant pour chque mot
# param ConstraintAssemblingList liste des operations entre les variables d'un mot
# param equalposition postion du egal dans la liste des opérateurs
# param BigFirstEquationConstraintPart partie de contrainte avant le egal
# param BigSecondEquationConstraintPart partie de contrainte après le egal
# param model, model auquel on ajoute les contraintes

def cryptarithmeGenerique (s):

	ListeMots=[]
	ListeOperateurs=[]
	ListeLettres=[]
	ListePosition=[]
	ListePositions=[]
	exposant10=[]
	exposants10=[]
	ConstraintAssemblingList=[]
	BigFirstEquationConstraintPart=[]
	equalposition=0
	# attrape la liste de lettres

	lettres = "".join(set(re.findall("[A-Z]", s)))

	# attrape la liste de mots

	mots = s.split()
	if(mots[0]=='-'):
		print("pas de - comme opérateur devant la chaîne de caractère")
		return
	for i in range(0,len(mots),2):
		ListeMots.append(mots[i])
	# print(ListeMots)

	# attrape la liste d'opérateurs
	for i in range(1,len(mots),2):
		ListeOperateurs.append(mots[i])
	# print(ListeOperateurs)

	# associe à une lettre ses possibilités
	for i in range(0,len(lettres)):
		if(lettres[i] in [ListeMots[y][0] for y in range(0,len(ListeMots))]):
			ListeLettres.append([lettres[i],intvar(1,9, shape=1)])
		else:
			ListeLettres.append([lettres[i],intvar(0,9, shape=1)])
	#print(ListeLettres)

	# associe pour chaque mot une liste des positions des variables contenu dans ListeLettres

	for i in ListeMots:
		# print(i)
		for y in i:
			# print(y)
			for w in range(0,len(ListeLettres)):
				# print(w)
				# print(ListeLettres[w][0])
				if(y==ListeLettres[w][0]):
					ListePosition.append(w)
					break
		ListePositions.append(ListePosition)
		ListePosition=[]
	# print(ListePositions)

	# associe pour chaque mot une liste d'exposant

	for y in ListeMots:
		for i in range(0,len(y)):
			exposant10.append(10**i)
		exposants10.append(exposant10)
		exposant10=[]
	# print(exposants10)

	# creation des parties de la contrainte globale du cryptarithme

	for i in range(0,len(ListeMots)):
		ConstraintAssemblingList.append(sum([ListeLettres[ListePositions[i][y]][1]*10**(len(ListeMots[i])-y-1) for y in range(0,len(ListeMots[i]))]))
	# print(ConstraintAssemblingList)

	# assemblage des parties de la contrainte globale du cryptarithme avant le =
	BigFirstEquationConstraintPart=ConstraintAssemblingList[0]
	for i in range(0,len(ListeOperateurs)):
		if(ListeOperateurs[i]=='+'):
			BigFirstEquationConstraintPart = BigFirstEquationConstraintPart + ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='-'):
			BigFirstEquationConstraintPart = BigFirstEquationConstraintPart - ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='*'):
			BigFirstEquationConstraintPart = BigFirstEquationConstraintPart * ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='/'):
			BigFirstEquationConstraintPart = BigFirstEquationConstraintPart / ConstraintAssemblingList[i+1]
			print(BigFirstEquationConstraintPart)
		if(ListeOperateurs[i]=='='):
			equalposition=i
			break
	# print(BigFirstEquationConstraintPart)

	# assemblage des parties de la contrainte globale du cryptarithme après le =

	BigSecondEquationConstraintPart=ConstraintAssemblingList[equalposition+1]
	for i in range(equalposition,len(ListeOperateurs)):
		if(ListeOperateurs[i]=='+'):
			BigSecondEquationConstraintPart = BigSecondEquationConstraintPart + ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='-'):
			BigSecondEquationConstraintPart = BigSecondEquationConstraintPart + -ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='*'):
			BigSecondEquationConstraintPart = BigSecondEquationConstraintPart * ConstraintAssemblingList[i+1]
		if(ListeOperateurs[i]=='/'):
			BigSecondEquationConstraintPart = BigSecondEquationConstraintPart / ConstraintAssemblingList[i+1]
			print(BigSecondEquationConstraintPart)
	# print(BigSecondEquationConstraintPart)

	# Création du model et de ses contraintes

	model = Model()

	# Mise en place de la contrainte globale

	model += (BigFirstEquationConstraintPart == BigSecondEquationConstraintPart)

	# Mise en place de la contrainte où toutes les lettres sont différentes

	model += AllDifferent(ListeLettres[i][1] for i in range(0,len(ListeLettres)))

	# Mise en place de la contrainte disant que les premières lettres des mots sont différentes de 0

	for i in range(0,len(ListeMots)):
		model += (ListeLettres[ListePositions[i][0]][1]) > 0


	if model.solve():
		print(s)
		for i in range(0,len(ListeMots)):
			print(ListeMots[i],"   =",[x.value() for x in [ListeLettres[ListePositions[i][y]][1] for y in range(0,len(ListeMots[i]))]])
	else:
		print("No solution found")
	
cryptarithmeGenerique("SEND + MORE = MONEY")
cryptarithmeGenerique("HUIT + HUIT = SEIZE")
cryptarithmeGenerique("UN + UN + NEUF = ONZE")
cryptarithmeGenerique("UN + TROIS - NEUF = ONZE")
cryptarithmeGenerique("UN * UN = ONZE")
cryptarithmeGenerique("UNN / UN = UN")