Se acerca el día de nuestra partida, pero al ir a alquilar un trineo para el viaje resulta que los sistemas informáticos de la tienda de alquiler no funcionan, ¿os ha pasado alguna vez?. El gerente de la tienda no consigue identificarse en su terminal porque la base de datos de contraseñas se ha corrompido, así que ni cortos ni perezosos vamos a arremangarnos y echar una mano a ver que ha pasado.

Una de passwords – video creado por ManicD7

Parte 1

Dada una lista de contraseñas y sus reglas de generación, tenemos que verificar cuantas contraseñas de la lista cumplen su regla. Por ejemplo, para la linea 1-3 a: abcde se debe comprobar que la contraseña contenga entre 1 y 3 letras a.

Barajo tres opciones para solucionarlo:

  • Eliminar la letra en la contraseña y verificar que la longitud de la cadena resultante ha decrecido entre los limites indicados.
  • Partir la contraseña por la letra indicada y verificar que el array resultante tiene un tamaño entre los limites indicados.
  • Utilizar una expresión regular para identificar cuantas ocurrencias de la letra indicada hay en la contraseña.

Como las dos primeras variantes se me antojan más sencillas opto por la tercera, generando una clase para parsear las lineas y generar un objeto Password con una propiedad que indique si es valida según la regla indicada:

export class Password {
  private value: string;
  private minOcurrences: number;
  private maxOcurrences: number;
  private character: string;

  constructor(definition: string) {
    const dimensions = definition.match(/(\d+)-(\d+)\s([a-zA-Z]):\s(.*)/);

    this.character = dimensions[3];
    this.minOcurrences = parseInt(dimensions[1]);
    this.maxOcurrences = parseInt(dimensions[2]);
    this.value = dimensions[4];
  }
  get isValidFirstPolicy(): boolean {
    const policy = new RegExp(this.character, "g");
    const ocurrences = this.value.match(policy)?.length ?? 0;
    return ocurrences >= this.minOcurrences && ocurrences <= this.maxOcurrences;
  }

Al constructor se le pasa la linea del juego de datos de entrada, y este separa en cuatro propiedades cada uno de los valores que nos interesan.

La propiedad que comprueba si la contraseña es válida es un getter que genera una expresión regular global que contiene únicamente la letra a buscar. Después ejecuta el método match contra la cadena de la contraseña y cuenta el numero de ocurrencias encontradas. Si este método no devuelve nada (null), debemos evitar leer su propiedad length, para lo que utilizamos el operador de encadenamiento opcional .?. Si eso ocurre, para asignar 0 al numero de ocurrencias utilizamos el operador de coalescencia nulo ??. Ya solo queda comprobar si el número de ocurrencias está entre el máximo y mínimo indicado en la definición.

Solo nos queda crear los objetos Password y contar los válidos:

const passwords = passwordsDatabase.map(definition => new Password(definition));
console.log("Answer:",passwords.filter(p => p.isValidFirstPolicy).length);

Parte 2

Nada más resolver la primera parte nos enteramos de que la regla que hemos aplicado para verificar la validez de la contraseña no es la correcta: los números no indican el número mínimo y máximo de ocurrencias de la letra, sino las posiciones donde debe aparecer de manera exclusiva. Para la linea 1-3 a: abcde la letra a debe aparecer en la posición 1 (siendo esta la primera de la cadena) o en la 3, pero no en ambas a la vez.

Esta condición cuadra muy bien con una operacion XOR, pero para nuestra desgracia Typescript / Javascript no dispone de este operador lógico, por lo que nos toca implementarlo en una función:

  private xor = (a: boolean, b: boolean) => (a ? !b : b);

Con esta operación disponible, la solución a la segunda parte, reutilizando el código de la clase que ya tenemos, es inmediata. Tan solo hay que comprobar si los caracteres de las posiciones indicadas son el buscado y aplicar el XOR, teniendo en cuenta que la cadena comienza en 1 y no en 0:

get isValidSecondPolicy(): boolean {
    return this.xor(
      this.value[this.minOcurrences - 1] == this.character,
      this.value[this.maxOcurrences - 1] == this.character
    );
  }

En el caso de los ejercicios de hoy no me voy a meter con la optimización. Para la primera parte estoy seguro de que soluciones como split o replace pueden ser más rápidas que utilizar una expresión regular tan básica como la usada, pero el rendimiento, para el juego de datos de entrada, es suficiente.

Puedes descargar el código completo de este ejemplo desde GitHub: oddbytes.net/adventofcode
Free WordPress Themes, Free Android Games