lunes, 30 de enero de 2017

Generador Sudokus III.

En entradas anteriores he descrito como hacer un generador de sudokus en excel, sin programar. El único problema es que en ambas entradas generamos sudokus ya resueltos, sin las correspondientes celdas en blanco que caracterizan a todo sudoku.
Esta vez, por último, vamos a colocar aleatoriamente las celdas en blanco que todo sudoku debe tener

Entradas anteriores sobre este tema:
Generador elemental de Sudokus I
Generador de Sudokus II




He preparado un libro excel que coloca, aleatóriamente, n celdas en blanco, sustituyendo el valor correspondiente por un espacio nulo.
Numero las 81 celdas del sudoku. Numero de izquierda a derecha y de arriba abajo:

1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18
19 20 21 22 23 24 25 26 27
28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45
46 47 48 49 50 51 52 53 54
55 56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81
  • Necesito crear un indice con todas las celdas, con todos los números que indica la dirección de cada celda.
  • Por otra parte, para poder localizar un determinado valor, necesito que todos los números que me indica el número de celda tenga el mismo número de dígitos. El truco utilizado es empezar a contar en 10 y terminar en 90. De esta manera todas esas direcciones tienen dos dígitos.
  • Ademas, añado o concateno un * antes y otro * después. Si no hacemos esto generamos direcciones adicionales. Si concatenamos 11 y 12 y 13, p.e., 111213 y tomamos los números de 2 en 2 vemos que apare un 21. Si concatenamos *11**12**13* evitamos ese error.
En Aux2:
  • La concatenación la hice en un libro auxiliar y luego la copié el valor en Aux2!D2.
  • En C2 busco un valor aleatorio entre 0 y 80. Con ese valor busco la dirección de la celda en el indice con =EXTRAE(D2;C2*4+1;4).
  • En d3, reemplazo el valor encontrado en el paso anterior por un nulo. Voy repitiendo estos dos pasos hasta la última celda.
  • En la columna F paso a valor numérico las claves encontradas con =SUSTITUIR(E2;"*";"")+0 (equivale a =VALOR(SUSTITUIR(E2;"*";"")))
  • En G2 indico el numero de celdas en blanco del sudoku. Con ese valor genero en H2 un rango, el rango que contiene las direcciones de las n celdas que aparecerán en blanco.
  • Necesito conocer las celdas que aparecerán en blanco, pero con sus direcciones ordenadas. 
  • Creo el rango con nombre  RanBlan (=INDIRECTO(Aux2!$H$2)). Este rango incluye solamente las n primeras direcciones de la columna F, que son las que quedaran en blanco.
  • Para cada dirección, la ordenada, en la columna B, cuento el número de veces que aparece en RanBlan, con =CONTAR.SI(RanBlan;$B2). Solo hay dos valores posibles, cero o uno.
  • Para pasar los valores del sudoku generado, el que tiene los 81 valores posibles, necesito pasar los valores que hay en la matriz de 9x9 a una matriz de una columna.
  • Para trasponer la matriz cálculo la fila y la columna que se corresponde con la dirección de la celda con =ENTERO(($B2-10)/9)+1 para fila y con =RESIDUO(($B2-10);9)+1 para columna. Recordemos el truco de empezar en 10 la numeración de las celdas.
  • Utilizo el rango con nombre, ver administrador de nombres, SudoF =Generador!$B$14:$J$22.
  • El valor que debe aparecer en el sudoku final, para cada dirección, es =SI(I2=1;"";INDICE(SudoF;$J2;$K2))
  • Ya tengo los valores en una matriz de una columna, queda trasponer esa columna a una matriz de 9*9. En Aux2!o3:w11 paso las direcciones de columna a matriz con =$N3*9+O$2.
  • En o14:w22 paso los valores de columna a matriz 9*9 con =INDICE($L$2:$L$82;O3)
  • Este método no garantiza que la quinta regla del sudoku se cumpla, puede que al final de todo este proceso la solución no sea única. Cuantos mas espacios en blanco haya mas probable es que no nos salga un sudoku de solución única.
En la portada, hoja Generador:
  • Sitúo una barra de desplazamiento con la que se puede modificar el número de celdas en blanco, vinculada con la celda Aux2!$G$2. Limitada entre 20 y 60 blancos. Lógicamente estos límites se pueden cambiar a gusto del usuario.
  • El sudoku final queda en el rango m14:u22. Paso los valores de la hoja Aux2 a la principal con =Aux2!O14.

Hasta el momento he encontrado un tipo de combinación de datos que hace que el sudoku pueda presentar mas de un resultado, pero para resolverlo tendría, a simple vista, que hacerlo programando. Creo que es muy complejo como para resolverlo solo con fórmulas. Imaginemos dos columnas distintas, incluidas en la misma terna de columnas. 

  • En la primera columna tenemos el valor A, en la segunda columna y en la misma fila tenemos un valor B. En otra fila, en otra región, tenemos en la primera columna un valor B y en la segunda un valor A. 
  • Si intercambiamos A por B y B por A:
  • Tanto la primera columna como la segunda columna mantienen A y B, sin repetirse ni en la columna ni en las filas afectadas.
  • Las dos filas afectadas mantienen A y B sin repetirse sin afectar ni a las columnas ni a las regiones.
  • En estos casos no se puede tapar con un espacio los cuatro valores (2 A y 2 B).










martes, 17 de enero de 2017

Permutaciones aleatorias de 9 elementos en excel. Generador aleatorio de sudokus.


Como complemento a la entrada anterior, generador elemental de sudokus, desarrollo una manera de generar una permutación aleatoria de 9 elementos. Al pulsar F9, tecla de recalculo, nos da un nuevo valor a la permutación.
Parto de la cadena alfanumérica Cad="123456789".
  •  La función  =ALEATORIO.ENTRE(1;9) da un valor entre uno y nueve. Para el valor aleatorio de la permutación me vale ese valor. Vease la hoja Aux rango o1:w3 del libro excel.
  • Un segundo paso consiste en retirar el primer valor encontrado de la cadena Cad. Esto lo hago con la función =SUSTITUIR(O1;O3;""), que se puede leer sustituye en Cad el valor o3 por una cadena nula.
  • Genero un número aleatorio entre 1 y 8, con =ALEATORIO.ENTRE(1;8).
  • Con ese valor extraigo el numero que se encuentre en esa posición de la cadena Cad.
  • Repito este cálculo hasta llegar a la última posición de la permutación.
Como puede verse en el libro repito este proceso para "barajar" las columnas, las filas y las ternas del sudoku original.

<<Anterior                  Siguiente>>








viernes, 13 de enero de 2017

Generador elemental de sudokus con Excel

Nunca me había planteado este tema, como generar un sudoku, me había limitado a resolver alguno. 
Como en la anterior entrada, en el entorno de como utilizar o resolver algunos eventos de hoja en excel, describo como hacer, con programación, una ayuda para resolver sudokus.
Nunca me lo había planteado, ni siquiera pensado en el tema ¿Como puedo generar uno?


De momento un primer paso, un generador elemental, y muy sencillo, de sudukus.

Partimos de un sudoku ya resuelto, con todos sus números correctamente puestos. La pregunta que me hice es ¿Y si convierto esos números absolutos en indices de un rango?
  • Creo un rango de celdas en el que coloco los nueve números del sudoku. Aleatoriamente  (en el libro anexo, de momento a mano) los números se reparten en el rango B1:J1. 
  • Utilizo la variable con nombre Num=Generador!$B$1:$J$1
  • El sudoku ya resuelto lo pongo en la hoja Aux, rango B2:j10
  • Con =INDICE(Num;Aux!B2) utilizo el valor del sudoku ya resuelto como indice.
  • Esto nos da un sudoku resuelto, sin celdas en blanco. Solo nos queda copiar los valores en otro lugar y borrar aquellas celdas que deseemos que aparezcan en blanco.
Consideraciones de cara a realizar un generador un poco mas complejo. Las supongo ciertas, de momento sin comprobar.

  • El sudoku esta compuesto de tres ternas de tres filas, y de tres ternas de tres columnas. Las tres ternas, tanto de filas de de columnas (cada una por su lado) se pueden reordenar. La primera terna se puede poner como segunda o como tercera, la segunda como primera o como tercera, etc. Lo mismo para las ternas en columna. Al movérlas en bloque, si es fila, no modificamos los números incluidos en cada columna o, si es columna, las filas.
  • A su vez, dentro de una terna   fila podemos reordenar sus tres filas. De manera similar podemos actuar con las columnas.

jueves, 12 de enero de 2017

Eventos en hojas excel con VBasic.


He preparado una herramienta para facilitar, facilitar no resolver, la resolución de sudokus. En una hoja excel pongo a la izquierda el sudoku, con sus valores y sus separaciones, y a la derecha pongo un cuadro similar que da los posibles valores de cada celda si en el original está en blanco o el valor de la celda original si esta no está en blanco. ¿Como lo hago? Al cambiar el valor de una celda lanzo el proceso que encuentra los posibles valores que puede tomar esa celda. Como respuesta a ese evento lanzo un procedimiento. 

Estamos en lo de siempre cuando nos referimos a los eventos. Un evento es cualquier acción reconocible por la aplicación que realicemos sobre, en este caso, una hoja excel. Los eventos son actuaciones como seleccionar o deseleccionar una hoja, hacer doble clic, modificar un valor, recalcular el valor de las fórmulas, etc.

Como respuesta a cualquiera de estas acciones podemos querer un cierto tipo de respuesta, normalmente una respuesta mas compleja que la que se pueda dar con formulas y formatos condicionales. En definitiva una respuesta que necesite programación.

La respuesta a los eventos "de hoja" tiene un nombre concreto para cada evento y debe estar situada en módulo de la propia hoja. Pinchando con el botón derecho del ratón en la solapa de la hoja podemos entrar en el módulo con "ver código". Como ya he dicho hay una serie de procedimientos con su propio nombre, y con sus propios parámetros, aunque el nombre de las variables se puede cambiar, que responden al evento correspondiente.



Cuando la hoja activa pierde el foco. Al cambiar de hoja.
******************************************************
Private Sub Worksheet_deactivate()

'MsgBox "Adios"

End Sub
******************************************************
Al seleccionar la hoja.
******************************************************
Private Sub Worksheet_Activate()

'MsgBox "Hola"

End Sub
******************************************************
Al hacer doble clic
******************************************************
Private Sub Worksheet_BeforeDoubleClick(ByVal Ran As Range, Cancel As Boolean)
'MsgBox "Clic-clic"
End Sub
******************************************************
Al recalcular una hoja.
*****************************************************
Private Sub Worksheet_Calculate()
'    Columns("A:F").AutoFit
End Sub
******************************************************
******************************************************
Al utilizar el botón derecho del ratón.
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, _
        Cancel As Boolean)
        
       ' MsgBox "no lo hagas"
        End Sub

Al hacer doble clic: Al hacer doble clic nos lleva de la celda con posibles contenidos a la correspondiente celda en el sudoku.
******************************************************
Private Sub Worksheet_BeforeDoubleClick(ByVal Celda As Range, Cancel As Boolean)

F = Celda.Row
C = Celda.Column
If C >= 13 And C <= 21 And F >= 2 And F <= 10 Then Celda.Offset(0, -11).Select
End Sub

******************************************************
*****************************************************  Private Sub Worksheet_SelectionChange(ByVal Celda As Range)

'MsgBox Celda.Address


End Sub

******************************************************

En mi facilitador de sudokus manejo el evento "Al cambiar el valor de una celda:"
******************************************************
Private Sub Worksheet_Change(ByVal Celda As Range)
Dim D, F, C

' la variable pasada,Celda, es un rango, se refiere a la celda que acabamos de cambiar el valor.
' No todas las celdas de la hoja forman parte del sudoku. Lo primero que tengo que hacer es conocer si la celda modificada está o no esta en el rango del sudoku, es una de celdas que desencadenan el procedimiento "Al cambiar". Para ello debo conocer la fila y la columna, dentro de la hoja, que ocupa la celda.
F = Celda.Row ' Fila de la celda.
C = Celda.Column ' Columna de la celda.
D = Celda.Address ' Dirección de la celda. Aunque luego no la utilice

' El rango del sudoku es B2:J10. Por tanto, las celdas útiles están situadas entre la columna 2, fila 2 y la columna 10, fila 10

If C >= 2 And C <= 10 And F >= 2 And F <= 10 Then
RangoCompuesto ' Proceso datos.

End If
End Sub
******************************************************

Cuando se incluye o modifica un número en una celda:
  • El proceso recorre todo el rango del sudoku, pasando por todas las celdas.
  • Las reglas del sudoku dicen que un número no debe repetirse ni en la línea, ni en la columna, ni en el rango de 3*3 que contiene a la celda en cuestión. 
  • Debo, antes de ver si un número se repite o no, encontrar el rango compuesto de la celda en cuestión.

Sub RangoCompuesto()

Dim D, V, F As Integer, C, Ran, R1, R2, Cad, Num, Val
Cad = "123456789"
With ActiveSheet

For Each Celda In .Range("b2:j10") ' Rango del Sudoku

Val = Trim(Celda.Value) 'Valor de la celda

If Val = "" Then

'***********************************************
'Encontrar fila y columna de la celda procesada se puede hacer de dos maneras, a partir de la dirección o directamente con row y column. En este caso, puede que resulte mas sencillo utilizar la dirección.
D = Celda.Address 'Dirección de la celda.
V = Split(D, "$")  'Convierte o pasa la dirección a una matriz columna (en letra) y fila


'F = Celda.Row
'C = Celda.Column - 2


F = V(2)
C = Asc(V(1)) - 66 ' En este caso la primera columna del sudoku es la B, ascii 66. En este caso interesa que la primera columna, en número, sea cero.


Ran = "b" & F & ":" & "J" & F & "," & V(1) & 2 & ":" & V(1) & 10
R1 = Int((F - 2) / 3) * 3 ' Esta operación permite encontrar la celda de mas arriba y mas a la izquierda de cada grupo 3*3 de celdas del sudoku.
R2 = Int(C / 3) * 3
D = .Range(.Cells(R1 + 2, R2 + 2), .Cells(R1 + 4, R2 + 4)).Address
Ran = Ran & "," & D
.Range(Ran).Select

' Encontrado el rango, recorre todas las celdas de ese rango con un "para cada celda en un rango"
'******************************************************
For Each Celda2 In .Range(Ran)
Num = Celda2.Value
Cad = Replace(Cad, Num, "") ' La cadena Cad contiene los nueve números utilizados en el sudoku. El proceso Reemplaza todo numero encontrado con una cadena nula ""
Next
'******************************************************

End If
If Val = "" Then
Celda.Offset(0, 11) = "*" & Cad & "*" ''Escribe lo que queda de la cadena Cad
Else
''Si la celda contiene un número, en la imagen escribe ese número.
Celda.Offset(0, 11) = Val 
End If

Cad = "123456789"

Next
'  .Columns("m:u").AutoFit
End With

'
End Sub