El primer avión de AoC ya está en vuelo, y nos lleva en transitohacia el aeropuerto donde cogeremos otro avión intercontinental, mucho más grande, que nos desplazará hasta nuestro ansiado destino final en el trópico. Durante la travesía, la tripulación reparte los formularios de aduanas donde debemos responder 26 preguntas, de la a
a la z
, de tipo Si-No. No había ninguna dificultad hasta que el pasajero de al lado, que esta con un grupo de extranjeros que no entienden muy bien el idioma, nos pide ayuda y nos pasa todos los formularios de su grupo para que los revisemos. Para colmo, se corre la voz de que hay un tipo que esta comprobando los formularios y todos los grupos del avión nos hacen llegar sus papeles rellenos para que los verifiquemos. Para cada grupo, apuntamos en una lista a qué preguntas ha respondido sí cada persona, y terminamos con una ristra de letras inmensa….
Parte 1 – Yo no understand….
Nuestra lista de respuestas por grupo tiene esta pinta:
abc a b c ab ac
En este ejemplo tenemos las respuestas de tres grupos:
- El primer grupo consta solo de una persona que ha respondido sí a las preguntas
a
,b
yc
- El segundo grupo consta de tres personas, donde la primera ha respondido afirmativamente a la
a
, la segunda a lab
y la tercera a lac
- El tercer grupo está formado por dos personas. La primera ha marcado sí en las preguntas
a
yb
y la segunda en las preguntasa
yc
El primer problema del día consiste en calcular la suma del número de preguntas que han sido respondidas afirmativamente al menos por una persona de cada grupo. Si una pregunta ha sido respondida por más de una persona del grupo cuenta solo una vez. En el ejemplo anterior, para el tercer grupo las preguntas respondidas por al menos una persona serían a
, b
, y c
. La pregunta a
ha sido respondida afirmativamente por dos personas, pero solo cuenta una vez.
Eso quiere decir que de la lista de entrada necesitaríamos obtener otra con una sola linea por grupo con todas las preguntas respondidas afirmativamente por al menos una persona. Al ejemplo anterior le correspondería:
abc abc abc
Voy a optar por la opción de calcular, para cada grupo, una lista de todas las preguntas respondidas por cualquier persona. Después solo habrá que eliminar los duplicados de dicha lista y obtener una cadena con las letras restantes. Lo haré mediante un objeto GroupAnswers
, que representa las respuestas de las personas de un grupo:
export class GroupAnswers { private answers: string[]; constructor(private questionsAnswered: string[]) { this.answers = questionsAnswered .map((personAnswers) => Array.from(personAnswers)) .flat(); } } export class AnswersDatabase { public read = (file: string): GroupAnswers[] => fs .readFileSync(file, "utf8") .split("\r\n\r\n") .map((answers) => new GroupAnswers(answers.split("\r\n"))); }
La clase AnswersDatabase
sirve para leer el fichero de texto. Por cada grupo de respuestas, que están separadas por dos saltos de linea, pasa al constructor de GroupAnswers
un array donde cada elemento son las respuestas de una persona.
El constructor, ademas de almacenar este array en una variable, genera un nuevo array plano mediante Array.flat
con todas las respuestas del grupo utilizando Array.from(personAnswers)
. Es decir, un array unidimensional donde cada elemento es una letra. Para el tercer grupo obtendríamos this.answers=["a","b","a","c"]
.
Ahora solo queda eliminar los duplicados de dicha lista y devolver una cadena con las letras únicas, algo muy sencillo de conseguir con un Set:
/** * Devuelve una sola cadena con las respuestas sin duplicados */ public get uniqueAnswers(): string { return [...new Set<string>(this.answers)].join(""); }
Un Set es una colección que no admite duplicados, por lo que basta con crear un Set a partir de nuestro array y después el Set de nuevo a un array para limpiar las letras repetidas. Por último, concatenamos las letras de este array mediante join("")
y ya tenemos la cadena de letras única.
La respuesta a esta primera parte consiste en sumar la longitud de las cadenas de cada grupo, para lo que usamos Array.reduce
:
const groups = new AnswersDatabase().read("./answers.txt"); console.log( "Answer:", groups.reduce((a, b) => a + b.uniqueAnswers.length, 0) );
Parte 2 – Se ha equivocado aquí….
La segunda parte del problema nos propone una variación a lo anterior: en vez de contar las preguntas a las que al menos una persona de cada grupo a contestado afirmativamente tenemos que contar aquellas a las que todas las personas del grupo hayan contestado.
En el ejemplo, en el primer grupo, puesto que solo hay una persona, todas las respuestas cuentan (3). En el segundo grupo ninguna pregunta ha sido respondida por todas las personas, luego la respuesta sería 0. por último, para el tercero solo la pregunta a
ha sido respondido por todas las personas, por lo que la respuesta para ese grupo sería 1.
Es decir, nos está solicitando para cada grupo el conjunto de letras contenido en la intersección de los conjuntos de respuestas de cada persona. Calcular la intersección de dos conjuntos en typescript / javascript es bastante sencillo con Array.includes
y Array.filter
. Como tenemos varios conjuntos (uno por cada persona del grupo), tendremos que iterar sobre ellos ejecutando la intersección del conjunto de la persona con el resultado de la intersección anterior:
/** * Devuelve una cadena con las preguntas respondidas por todo el grupo (intersección) */ public get commonAnswers(): string { let commonAnswers = Array.from(this.questionsAnswered[0]); this.questionsAnswered.slice(1).forEach((person) => { commonAnswers = commonAnswers.filter((answer) => Array.from(person).includes(answer) ); }); return commonAnswers.join(""); }
De nuevo, tan solo nos falta sumar el numero de respuestas de cada grupo para obtener el resultado final:
const groups = new AnswersDatabase().read("./answers.txt"); console.log( "Answer:", groups.reduce((a, b) => a + b.commonAnswers.length, 0) );
¿Qué te ha parecido este sexto día del advent of code 2020?