const fs = require("fs"); let [rules, messages] = fs .readFileSync("input", "utf-8") .trim() .split("\n\n") .map((part) => part.split("\n")); rules = Object.fromEntries(rules.map((line) => line.split(": "))); // unecessary, but might reveal an overlooked loop rules["8"] = "42 | 42 8"; rules["11"] = "42 31 | 42 11 31"; // console.log(rules); // console.log(messages); function getRegex(idx, visited = []) { const rule = rules[idx]; if (rule.startsWith('"')) { return rule.slice(1, rule.length - 1); } let regex = ""; let multiple = false; for (const symbol of rule.split(" ")) { if (symbol === "|") { regex = "(" + regex + "|"; multiple = true; } else { regex = regex + getRegex(parseInt(symbol), visited.concat(idx)); } } regex = multiple ? regex + ")" : regex; rules[idx] = '"' + regex + '"'; return regex; } // Rule 0 is "8 11" // These rules are the only looping rules through self referencing // rule 8: 42 | 42 8 // expects rule 42 one or more times // rule 11: 42 31 | 42 11 31 // recursively expects R42 R11 R31 // translated: // expects rule 42 one or more times followed by 31 by the same quantity // combined: // rule 0: 8 11 // start of string matches 42 (one or more) // end of string matches 31 (one or more) // the quantity of R42 matches has to be more than the quantity of R31 const rule42 = getRegex(42); const rule31 = getRegex(31); const possibleEndings = []; // 10 is a guessed maximum repetition based message length // if this value is too high the regex wont compile // for larger messages a recursive approach is necessary for (let reps = 1; reps <= 10; reps++) { possibleEndings.push(`((${rule42.repeat(reps)})(${rule31.repeat(reps)}))`); } const patternRaw = `^(${rule42})+(${possibleEndings.join("|")})$`; // console.log("Pattern:", patternRaw); console.log("Compiling RegExp..."); const pattern = new RegExp(patternRaw); console.log("done."); console.log("Checking messages..."); let sum = 0; for (let message of messages) { if (pattern.test(message)) sum++; } console.log(sum);