Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Con estas dos funciones podemos generar billones de Sudokus así como solucionar
cualquier Sudoku por difícil que sea. Están escritas en Actionscript 2 para Flash.
Aunque no es un lenguaje muy apropiado para ello, se obtienen unos resultados de
alta calidad, tanto en uso de recursos como en velocidad.
GENERAR UN SUDOKU
Las 2 soluciones más comúnmente empleadas para presentar sudokus son las bases de
datos y los generadores.
Las bases de datos ocupan un volumen y ficheros externos con su tratamiento y, aun
siendo una solución sencilla, está limitada a una cantidad. Los generadores de origen
aleatorio + comprobación producen, en general, Sudokus simples y en la mayoría de
las ocasiones de solución múltiple.
Debemos recordar que, por definición, un Sudoku debe de tener una y sólo una
solución.
El generador que presento produce Sudokus correctos con solución única mezclando
ambas características:
Por un lado una compleja mini base de datos comprimida en una cadena Unicode y por
otros varios algoritmos de mezcla de dichos datos aplicando Teoría de Grupos.
“Si un sudoku tiene solución única, al añadir más elementos de esa misma solución,
esta se mantiene única”.
De este modo si tenemos un sudoku base de, por ejemplo, 20 elementos y dificultad 9,
y le añadimos 4 elementos más, tendremos otro de 24 elementos y menor o igual
dificultad.
Veamos 4 sudokus generados desde la misma fuente y con dificultades iguales o
diferentes:
Estos son los sistemas mezclados que usaremos, junto con la eliminación o
conmutación de grupos iguales de valores, en la formación de la cadena Unicode que
nos sirve de base de datos:
Un primer grupo con un sudoku de un nivel determinado y múltiples subgrupos
asociados de elementos que podemos añadir al primero, manteniendo esos valores
dentro de la solución única, para “rebajar” su dificultad y añadiendo una mezcla
combinatoria congruente.
Este es el código:
generar(nivel).
La salida se entrega como una cadena de 81 elementos denotando con ".", las casillas
vacías y con su número, las ocupadas.
Ejemplo:
...6.3.5.......2.95....4...34......578.......2..89..1..18....7..32.6.........2.3.
Podemos aplicarle un split("") para convertirlo en array y usar los datos en nuestro
programa.
RESOLVER UN SUDOKU
• Fuerza Bruta: Eficaz, pero si sólo se usa un sistema de condición para poder
resolver Sudokus extremos resulta realmente tedioso y lento.
• Backtracking de fuerza bruta: Mejor, pero hay que considerar que el nivel de
recursividad de AS2 está en 256 de profundidad y puede alcanzarse fácilmente
si no se toman muchas precaucione. AlgoritmoX y DLX.
• Devorador: Usando heurísticos, saltamos muchas ramas del árbol. Tiene el
peligro de que, si no se programan muy atinadamente dichos heurísticos,
podemos no encontrar la solución al terminar el ciclo de búsqueda.
• Devorador+Backtracking: Si el Devorador no dio solución, pasamos al
Backtracking para asegurar la solución.
• Ramificación y poda: Es un tipo de devorador donde los heurísticos tienden a
eliminar, más que a encontrar, las no soluciones.
Es entonces claro que el mejor camino para el estudio de Sudokus es el último citado.
function resolver(m) {
if (!ini) {
tablas();
ini = true;
}
s = []; q = []; ct = 0; act = []; aq = []; // inicializando o
creando arrays
for (var j = 0; j<81; j++) {
s[j] = 511;
}
e = m.split("");
for (var h = 0; h<e.length; h++) {
if (e[h]>="1" && e[h]<="9") {
colocar(s, h, e[h]);
}
}
q[0] = resolver2(s).slice();
cons(q[0]);
while (ct<81 && ct>-1) {
if (ct2<0) {
return q[ct].slice(81, 162).join("");
}
colocar(q[ct], ct2, act[ct].pop());
if (!resolver2(q[ct])) {
q[ct] = aq[ct].slice();
while (!act[ct][0] && ct>-1) {
ct--;
ct2 = q[ct][162];
q[ct] = aq[ct].slice();
}
} else {
ct++;
q[ct] = q[ct-1].slice();
cons(q[ct]);
}
}
return "sin solución";
function resolver2(s) {
do {
sp = [];
for (var j = 0; j<81; j++) {
if (!s[j+81]) {
if (t4[s[j]] == 0) {
return false;
}
if (t4[s[j]] == 1) {
sp.push(j, t3[s[j]]);
}
}
}
for (var j = 0; j<27; j++) {
s1 = [];
s2 = 0;
for (var h = 0; h<9; h++) {
if (!s[t1[j][h]+81]) {
s1[s2] = t1[j][h];
s2++;
}
}
s5 = s1.length;
if (!s5) {
continue;
}
r1 = 0;
r2 = s[s1[0]];
for (var h = 1; h<s5; h++) {
r1 = (s[s1[h]] & r2) | r1;
r2 = (s[s1[h]] ^ r2) & ~r1;
}
if (s5 != t4[r1]+t4[r2]) {
return false;
}
if (r2) {
for (var h = 0; h<9; h++) {
if (s[s1[h]] & r2) {
s3 = s1[h];
s4 = t3[s[s1[h]] & r2];
if (s4.length>1) {
return false;
}
if (s3) {
sp.push(s3, s4);
}
}
}
}
}
for (var v = 0; v<sp.length; v += 2) {
for (var j = 0; j<21; j++) {
sa = t[sp[v]][j];
if (sa != sp[v] && s[sa+81] == sp[v+1]) {
return false;
}
}
colocar(s, sp[v], sp[v+1]);
}
} while (sp[0]);
return s;
}
function cons(s) {
min = 9;
ct2 = -1;
for (var j = 0; j<81; j++) {
if (!s[j+81] && t4[s[j]]<min) {
min = t4[s[j]];
ct2 = j;
}
if (min == 2) {
break;
}
}
s[162] = ct2;
aq[ct] = s.slice();
act[ct] = t3[s[ct2]].split("");
}
function colocar(s, a, b) {
s[81+a] = b;
for (var h = 0; h<21; h++) {
s[t[a][h]] &= ~(1 << (b-1));
}
}
function tablas() {
t = [];
t1 = [];
t2 = [];
t3 = [];
t4 = [];
for (var j = 0; j<512; j++) {
rt = "";
for (var h = 0; h<9; h++) {
if (j & (1 << h)) {
rt += h+1;
}
}
t3[j] = rt;
t4[j] = t3[j].length;
}
for (var j = 0; j<81; j++) {
t[j] = [];
t2[j] = [Math.floor(j/9), 9+j%9,
3*Math.floor(j/27)+Math.floor(j%9/3)+18];
for (var v = 0; v<3; v++) {
if (!t1[t2[j][v]]) {
t1[t2[j][v]] = [];
}
t1[t2[j][v]].push(j);
}
}
for (var j = 0; j<81; j++) {
t[j] =
t1[t2[j][0]].concat(t1[t2[j][1]]).concat(t1[t2[j][2]]);
n = 0;
t[j].sort(16);
while (n<t[j].length-1) {
t[j][n+1] == t[j][n] ? t[j].splice(n, 1) : n++;
}
}
}
}
//Ejemplo:
m1 =
"...6.3.5.......2.95....4...34......578.......2..89..1..18....7..32.6.
........2.3.";
trace(m1);
b = resolver(m1);
trace(b);
// salida 1
:194623857863571249527984163349216785781435926256897314618359472432768
591975142638
for (var j = 0; j<81; j += 9) {
trace(b.substr(j, 9));
}
/*
Salida 2 :
194623857
863571249
527984163
349216785
781435926
256897314
618359472
432768591
975142638
*/
Como base usa un array de 162 elementos donde almacenamos los datos de cada
posición-estado del Sudoku. Los primeros 81 (0 a 80) contienen un valor binario que
nos dice los números válidos para cada casilla, también numeradas de (0 a 80), y del
modo siguiente:
Los 81 valores siguientes del mismo array son los números ya asignados en cada
casilla, de modo que un valor en el array Q[100]=6; nos indica que en la casilla 19 (100-
81) tenemos colocado un 6. El usar un array no multidimensional tiene en este caso
sus ventajas:
S = Q;
S = Q.slice();
De este modo sólo hacemos una copia del puntero de Q rápidamente. Si modificamos
algún valor en Q, en S no cambiará… ¡en su primer nivel solamente! Esto quiere decir
que si Q es un array multidimensional, sólo los elementos del array inicial (luego
creamos la/las siguientes dimensiones “colgándolas“ de la inicial) quedan desligados,
el resto de niveles mantendrá el valor de Q y si los variamos, cambiará S.
Para conseguir hacer una copia S desligada de Q, siendo este un array
multidimensional, tendremos que hacer una copia o clon elemento a elemento. Por
eso es mejor en este caso, por cuestiones de velocidad y sencillez, usar un array simple
con doble tipo de datos:
Vamos a rellenar unos arrays con los valores y datos que necesitamos:
Un array t3[ ] donde almacenamos los 512 valores (2^9 binario) posibles de cada
casilla, pero expresados como cadena de elementos que quedan:
Ejemplos:
t3[ 511] contiene 123456789 (todos valen, inicio) luego guardamos la cadena
“123456789”
t3[25] contiene 145 (valen, está su flag, el 1 2^0=1 el 4 2^3=8 y el 5 2^4=16,
1+8+16=25) guardamos “145”;
En t4[ ] guardamos la longitud de cada t[3] para saber el número de elementos que
valen en cada casilla, en cada momento.
Ejemplos:
t4[511] = 9 y t4[25] = 3
En t2[ ][ ] vamos a almacenar tres valores por elemento. Cada una de las 81 casillas
pertenece a una fila(9), una columna(9) y un bloque(9). Esos 3 valores son los que
guardamos teniendo en cuenta que esos 27 elementos los ordenamos así: 0 a 8 las
filas, 9 a 17 las columnas y 18 a 26 los bloques.
Ejemplos:
t2[27] = [ 3,9,21]
t[43]=[7,16,25,33,34,35,36,37,38,39,40,41,42,43,44,51,52,53,61,70,79]
Que nos es muy útil tanto para asignar un valor a una casilla como para comprobar,
pues cuando cambia un valor de válidos (o candidatos), esas son las 21 casillas que
también cambiarán o han de actualizarse.
Consultando esos arrays que conforman las tablas y el array principal de estado,
tendremos, mediante condicionales, el control del estado del sudoku en todo
momento.
Para utilizar la función hay que enviar a la misma como parámetro una cadena del
mismo tipo que empleamos en la salida del generador: una cadena de 81 elementos (0
a 80) donde las casillas vacías se representen por cualquier elemento, que no sea un
número, y las válidas por el número.
Ejemplo:
...6.3.5.......2.95....4...34......578.......2..89..1..18....7..32.6.........2.3.
194623857863571249527984163349216785781435926256897314618359472432768
591975142638
Las funciones principales (generar y resolver) están algo modificadas para permitir
extraer datos de ayuda.
Cuatro de las ayudas están sin implementar... (¿Alguna idea para otra versión?) .
Todo ello con solo código, como veréis en el FLA. No utiliza ningún MC, sólo y
exclusivamente un campo de texto polivalente como array y creado con en el script.