Usando operaciones matriciales para resolver el acertijo de Einstein. Parte II.

Usando operaciones matriciales para resolver el acertijo de Einstein. Parte II.
16 Noviembre 2015

En la primera parte de este artículo formulamos el acertijo de Einstein en términos de matrices, y se presentaron tres operaciones con matrices que serán la base para un algoritmo que permita resolver el enigma. En esta parte vamos a estar hablando de este algoritmo, y una implementación en JavaScript que permitirá resolver este enigma y cualquier otro enigma similar.

Reglas basadas en el uso de los operadores matriciales

El algoritmo es bastante sencillo, y se basa en tres reglas (el uso de los operadores definidos anteriormente, se ha puesto en negrita):

  1. Si una matriz de una pista tiene un valor común con la matriz de otra pista entonces: Si las dos matrices pueden ser fusionadas (no importa si nuevas filas deben ser añadidos a la primera matriz), entonces, fusionar ambas pistas, de lo contrario abortar el proceso.

  2. Si la matriz del acertijo tiene un valor común con una matriz de una pista entonces: Si la matriz de la pista puede ser fusionada con la matriz del acertijo sin necesidad de añadir nuevas filas a la matriz del acertijo, entonces fusionar la matriz de la pista con la matriz del acertijo, de lo contrario abortar el proceso.

  3. Si para una celda en blanco en la matriz del acertijo ubicada en la columna C, existe una y sólo una pista cuya matriz contiene una fila, donde la celda en la columna C no está en blanco y puede ser fusionada con la matriz del acertijo usando esa celda como el punto de fusión y sin necesidad de adicionar nuevas filas a la matriz del acertijo, entonces fusionar la matriz de la pista con la matriz del acertijo.

Las dos primeras reglas son muy intuitivas, la tercera es un poco más compleja, pero tiene sentido cuando ninguna de las matrices de las pistas se pueden fusionar entre ellas o con la matriz del acertijo.

El algoritmo

El algoritmo se puede describir usando el siguiente pseudocódigo:

  1. Obtener la matriz inicial (matriz del acertijo) y el conjunto de pistas (matrices de las pistas)

  2. Mientras que la matriz del acertijo tenga celdas vacías hacer

    • Comprobar la regla 1 para cada par de matrices de las pistas, y repetir el proceso hasta que ningún par de matrices se puedan combinar. Si se produce un aborto del proceso, entonces el problema no tiene solución para ese conjunto de pistas.

    • Comprobar la regla 2 para cada matriz de las pistas. Si se produce un aborto del proceso, entonces el problema no tiene solución para ese conjunto de pistas.

    • Si ninguna de las pistas pudo ser fusionada en los dos pasos anteriores, comprobar la regla 3 para cada celda en blanco en la matriz del acertijo.

Tenga en cuenta que si el problema no está bien definido, el algoritmo podría iterar infinitamente, por lo que es buena idea utilizar otra condición de parada que se base en la cantidad de iteraciones.

Cuando hay opciones en cualquiera de las pistas, normalmente no se considera la idea de generar todas las combinaciones posibles de pistas, en su lugar se trata de encontrar la opción correcta de cada pista durante el proceso de solución del acertijo; pero para el propósito del algoritmo que describimos, varias opciones dan lugar a diferentes conjuntos de pistas. El algoritmo probará cada conjunto de pistas, lo cual brinda la posibilidad de encontrar todas las soluciones posibles, como ocurre en uno de los acertijos utilizados al final de este artículo.

Implementación JavaScript

Hay algunos lenguajes de programación como Prolog, diseñados para funcionar directamente con hechos lógicos, donde se pueden resolver problemas lógicos de una manera natural. Mediante el uso de la formulación matricial podemos utilizar cualquier lenguaje de programación para resolver este tipo de acertijos. Aquí proporcionamos la implementación, en JavaScript, del objeto matriz con los tres operadores básicos utilizados para resolver los acertijos. La selección de JavaScript no es arbitraria, ya que el mismo permite mostrar la eficacia del algoritmo en este mismo artículo.

<script type="text/javascript">
var stringMatrix = function(m)
{
  this.values = m;
}

stringMatrix.prototype.totalRows = function(){
  return this.values.length;
}

stringMatrix.prototype.totalColumns = function(){
  return this.values[0].length;
}

stringMatrix.prototype.hasCommonValue = function(matrix){
  for (var i = 1 - matrix.totalRows(); i < this.totalRows(); i++){
    for (var k = 0; k < matrix.totalRows(); k++){
      for (var j = 0; j < this.totalColumns(); j++){
        if ((i + k) >= 0 && (i + k) < this.totalRows()){
          if (this.values[i + k][j] != '' && this.values[i + k][j] == matrix.values[k][j]){
            return {
              found: true,
              rowIndex: i + k,
              externalMatrixRowIndex: k,
              columnIndex: j
            };
          }
        }
      }
    }
  }
  return { found: false };
}

stringMatrix.prototype.canBeMerged = function(rowIndex, matrix, matrixRowIndex, failIfGoBeyond){
  if (failIfGoBeyond && ((rowIndex + matrix.totalRows() - matrixRowIndex > this.totalRows()) || (matrixRowIndex - rowIndex > 0))){
    return false;
  }

  var leftSteps = Math.min(rowIndex, matrixRowIndex);
  var totalRows = leftSteps + Math.min(this.totalRows() - rowIndex, matrix.totalRows() - matrixRowIndex);
  var minIndex = rowIndex - leftSteps;
  var minMatrixIndex = matrixRowIndex - leftSteps;

  for (var i=0; i<totalRows; i++){
    for (var j = 0; j < this.totalColumns(); j++){
      if (this.values[minIndex + i][j] != '' && matrix.values[minMatrixIndex + i][j] != '' && this.values[minIndex + i][j] != matrix.values[minMatrixIndex + i][j]){
        return false;
      }
    }
  }

  return true;
}

stringMatrix.prototype.newRow = function(){
  var newRow = new Array(this.totalColumns());
  for (var i=0; i < newRow.length; i++){
    newRow[i] = '';
  }
  return newRow;
}

stringMatrix.prototype.merge = function(rowIndex, matrix, matrixRowIndex){
  if (this.totalRows() < rowIndex + matrix.totalRows() - matrixRowIndex){
    for (var i = this.totalRows(); i < rowIndex + matrix.totalRows() - matrixRowIndex; i++){
      this.values.push(this.newRow());
    }
  }

  if (matrixRowIndex - rowIndex > 0){
    for (var i = 0; i < matrixRowIndex - rowIndex; i++){
      this.values.unshift(this.newRow());
    }
    rowIndex = matrixRowIndex
  }

  var i = rowIndex - matrixRowIndex;
  for (var k = 0; k < matrix.totalRows(); k++){
    for (var j = 0; j < this.totalColumns(); j++){
      if (matrix.values[k][j] != ''){
        this.values[i + k][j] = matrix.values[k][j];
      }
    }
  }
}
</script>

Puede descargar todo el código aquí: riddle-solver.js

Sólo tiene que llamar a la función: solveRiddle(riddle, elementId), donde el parámetro riddle es el objeto JSON que contiene toda la información necesaria (consulte más abajo) y elementId es el id de un objeto div en el que desea mostrar la solución.

Resolviendo los acertijos

Además del acertijo de Einstein, elegimos otros 5 acertijos similares, para demostrar la eficacia del algoritmo (los enlaces a los acertijos están en inglés):

  1. Los vecinos (acertijo de Einstein)
  2. El acertijo de los barcos
  3. Afrontar el desafío
  4. El Problema del fútbol
  5. ¿Quién vive en la ciudad?
  6. ¿Quién robó la galleta de jengibre del tarro de galletas?

Para el acertijo número 5, tenga en cuenta que el algoritmo encuentra dos soluciones válidas diferentes.

En todos los casos, al seleccionar el enigma, se puede ver la descripción del acertijo.

Si quieres ver los pasos y reglas usadas en cada paso, marque la casilla Solución detallada.

El cuadro de edición "JSON" muestra cómo se define el problema para que el algoritmo sea capaz de entender la matriz y las pistas. Para ello utiliza un objeto JSON que requiere 4 atributos:

header: Un arreglo con el nombre de las columnas

startMatrix: La matriz inicial utilizada para resolver el problema. Debe contener valores en al menos una de sus columnas, dependiendo del problema. La cantidad de columnas de esta matriz debe ser igual a la longitud del arreglo con el nombre de las columnas.

hints: Es una matriz de cuatro dimensiones, donde cada entrada se corresponde a una pista en la formulación verbal. Cuando la pista se puede expresar como una única sub-matriz, entonces la entrada en la matriz tetra dimensional será una matriz tridimensional que contiene sólo una entrada (la sub-matriz), pero si la pista derivada en más de una opción, entonces la entrada será una matriz tridimensional que contendrá tantas sub-matrices como la cantidad de opciones que se deriven de la pista.

singleHints: Este es valor booleano para especificar si las filas de cada sub-matriz que representan una pista, deben ser considerados como filas individuales o como una sub-matriz en sí misma. Por ejemplo, en el acertijo de Einstein la pista 6 se considera como una sub-matriz en sí, porque ambas filas deben permanecer siempre juntas, en cambio en las pistas del acertijo Afrontar el desafío, hay una pista que expresa: Pamela y su esposo pidieron prestado el libro que el señor y la señora Zajac trajeron; esta pista genera dos filas, pero esto no significa que las filas debe estar una a continuación de otra.

Si usted está familiarizado con la notación JSON, puede utilizar el cuadro de edición para introducir nuevos problemas cuando selecciona "Mi propio acertijo".

Si desea compartir alguna idea, por favor deje un comentario o escríbanos.

Acertijo
Descripción
JSON
Solución detallada

Entradas relacionadas

4 Comentarios


Tu comentario

Nunca compartiremos su dirección de correo.
Los comentarios son moderados antes de hacerlos visible para todos. La foto de perfil se obtiene de Gravatar usando su correo electrónico.
Antonio Ferrer
Antonio Ferrer
16 Marzo 2016
Impresionante la implementación para resolver los acertijos, he disfrutado mucho leyendo el artículo y estudiando los códigos, me gustaría ponerme en contacto con el autor e intercambiar algunas ideas. Gracias.
Boris
Boris
14 Marzo 2016
Interesante tu post. Me gusta mucho este sitio, soy un gran admirador. Los test psicométricos que bien!!!.
Marlon
Marlon
19 Noviembre 2015
Eres grande!!! que interesantes temas nos traes siempre, muy buen trabajo te felicito de veras
Roxy
Roxy
18 Noviembre 2015
Vaya que buen trabajo, que profesional, les admiro sigan así