This year I have been participating in the Advent of Code series of challenges, and it just so happened that I'd be doing them in Javascript. Not my usual weapon of choice, but I do have some experience in it.
Day 11 asks us to find the next valid password given a set of three requirements. The idea is that you'd be incrementing the given password until it matched all three criteria, but I wanted to do something more efficient. But the task "find the next string that has a straight of length three" turned out a bit more difficult than I anticipated, and the code happened to be a bit more convoluted than I'd like, whooping 38% of the whole challenge.
A straight is defined as
like
abc
,bcd
,cde
, and so on, up toxyz
. They cannot skip letters;abd
doesn't count.
My main concern is that my code isn't "obviously bug-free" and that the logic is hard to follow. How can I clean my logic up without sacrificing performance? Of course, reviewers are welcome to comment on other aspects of the code as well.
So, here we go:
//jshint esversion: 6
function nextChar(x){return String.fromCharCode(x.charCodeAt(0)+1);}
var requirements = [
{name: "straight3", nextMatch: function nextMatch(str){
var prefix = str;
while(prefix.length > 2){
if(prefix === straighten(prefix)) return str;
prefix = prefix.slice(0,-1);
}
prefix = str;
while(true){
if(prefix.length <= str.length - 3){
return str.replace(/(.)([z]*)...$/, function(_, c, zs){
return nextChar(c) + zs.replace(/z/g, 'a') + "abc";
});
} else if(prefix.match(/[yz]..$|xz.$/)) prefix = prefix.slice(0,-1);
else if(str < straighten(prefix)) {
var rv = straighten(prefix);
while(straighten(rv).slice(0,-1) === straighten(rv.slice(0,-1))) rv = rv.slice(0,-1)
while(rv.length < str.length) rv += 'a';
return rv;
} else {
prefix = prefix.replace(/.(?=..$)/, nextChar).replace(/..$/, "aa");
while(prefix.length < str.length) prefix += 'a';
return nextMatch(prefix);
}
}
function straighten(str){
var ord = str.slice(-3).charCodeAt(0);
return str.slice(0,-3) + String.fromCharCode(ord, ord+1, ord+2);
}
}},
{name: "noIOL", nextMatch: function(x){
return x.replace(/([iol])(.*)$/g, function(_, c, xs){
return nextChar(c) + xs.replace(/./g, 'a');
});
}},
{name: "twoPairs", nextMatch: function(str){
if(str.match(/(.)\1.*(.)\2/)) return str;
else if(str.match(/(.)\1../)){
var rv = str.slice(0,-2) + nextPair(str.slice(-2));
return rv.replace(/(.)\1\1$/, "$1$1a");
} else {
var rv = str.slice(0,-4) + nextPair(str.slice(-4,-2)) + "aa";
return rv.replace(/(.)\1\1aa$/,"$1$1aaa");
}
function nextPair(x){
if(x[0] > x[1]) return x[0] + x[0];
else return nextChar(x[0]) + nextChar(x[0]);
}
}}
];
var pwd = "hxbxxzaa";
var toSat = requirements.length;
while(toSat){
var oldPwd = pwd;
pwd = requirements[0].nextMatch(pwd);
if(pwd === oldPwd){
toSat --;
}else{
toSat = requirements.length;
console.log(oldPwd + " -> " + requirements[0].name + " -> " + pwd);
}
if(pwd.match(/[^a-z]/)) throw "oops";
requirements.push(requirements.shift());
}
console.log(pwd);