const LETTERS = "ABCDEFGHIJKLMNOPQRST";
const ROWS = [
[...("ABCDE")],
[...("FGHIJ")],
[...("KLMNO")],
[...("PQRST")]
];
class Marple{
constructor(clues){
this.clues = []
for(let clue of clues){
if(clue[1] === '^'){
this.clues.push(["same", clue[0], clue[2]]);
} else if(clue[0] == clue[2]){
this.clues.push(["adjacent", clue[0], clue[1]]);
} else if(clue[1] === '<'){
this.clues.push(["left", clue[0], clue[2]])
} else {
this.clues.push(["between", clue[0], clue[1], clue[2]])
}
}
this.pos = {};
for(let letter of LETTERS){
this.pos[letter] = new Set([...Array(5).keys()].map(i => i));
}
}
updateSame(a, b){
let updated = false;
let commons = [...this.pos[a]].filter(ele => this.pos[b].has(ele));
if(this.pos[a].size > commons.length){
this.pos[a] = new Set(commons);
updated = true;
}
if(this.pos[b].size > commons.length){
this.pos[b] = new Set(commons);
updated = true;
}
return updated;
}
updateAdjacent(a, b){
let updated = false;
let ca = [...this.pos[a]].filter(ele => this.pos[b].has(ele - 1) || this.pos[b].has(ele + 1));
if(this.pos[a].size > ca.length){
this.pos[a] = new Set(ca);
updated = true;
}
let cb = [...this.pos[b]].filter(ele => this.pos[a].has(ele - 1) || this.pos[a].has(ele + 1));
if(this.pos[b].size > cb.length){
this.pos[b] = new Set(cb);
updated = true;
}
}
updateLeft(a, b){
let updated = false;
let [m, M] = [Math.min(...this.pos[a]), Math.max(...this.pos[b])];
let ca = [...this.pos[a]].filter(ele => ele < M);
let cb = [...this.pos[b]].filter(ele => ele > m);
if(this.pos[a].size > ca.length){
this.pos[a] = new Set(ca);
updated = true;
}
if(this.pos[b].size > cb.length){
this.pos[b] = new Set(cb);
updated = true;
}
return updated;
}
updateBetween(a, b, c){
let updated = false;
let cb = [];
for(let eb of this.pos[b]){
let found = false;
for(let ea of this.pos[a]){
for(let ec of this.pos[c]){
if((ea < eb && eb < ec) || (ec < eb && eb < ea)){
found = true;
break;
}
}
if(found) break;
}
if(found){
cb.push(eb)
}
}
if(cb.length < this.pos[b].size){
this.pos[b] = new Set(cb);
updated = true;
}
let ca = [];
for(let ea of this.pos[a]){
let found = false;
for(let eb of this.pos[b]){
for(let ec of this.pos[c]){
if((ea < eb && eb < ec) || (ec < eb && eb < ea)){
found = true;
break;
}
}
if(found) break;
}
if(found){
ca.push(ea);
}
}
if(ca.length < this.pos[a].size){
this.pos[a] = new Set(ca);
updated = true;
}
let cc = [];
for(let ec of this.pos[c]){
let found = false;
for(let ea of this.pos[a]){
for(let eb of this.pos[b]){
if((ea < eb && eb < ec) || (ec < eb && eb < ea)){
found = true;
break;
}
}
if(found) break;
}
if(found){
cc.push(ec);
}
}
if(cc.length < this.pos[c].size){
this.pos[c] = new Set(cc);
updated = true;
}
return updated;
}
updateRow(row){
let updated = false;
for(let letter of row){
if(this.pos[letter].size === 1){
let ele = [...this.pos[letter]].pop()
for(let l of row){
if(l === letter) continue;
if(this.pos[l].has(ele)){
this.pos[l].delete(ele);
updated = true;
}
}
}
}
for(let v = 0; v < 5; v++){
// Candidate letters that have position v
let cv = [];
for(let letter of row){
if(this.pos[letter].has(v)){
cv.push(letter);
}
}
// Only one letter has position v
if(cv.length == 1){
let l = cv[0];
if(this.pos[l].size > 1){
this.pos[l] = new Set([v]);
updated = true;
for(let other of row){
if(other == l) continue;
this.pos[other].delete(v);
}
}
}
}
return updated;
}
solve(){
let updated = true;
while(updated){
updated = false;
let [same, adjacent, left, between] = [false, false, false, false];
for(let [type, ...o] of this.clues){
switch(type){
case "same":{
let [a, b] = o;
same = this.updateSame(a, b);
if(same) updated = true;
break;
}
case "adjacent":{
let [a, b] = o;
adjacent = this.updateAdjacent(a, b);
if(adjacent) updated = true;
break;
}
case "left":{
let [a, b] = o;
left = this.updateLeft(a, b);
if(left) updated = true;
break;
}
case "between":{
let [a, b, c] = o;
between = this.updateBetween(a, b, c);
if(between) updated = true;
break;
}
}
}
for(let row of ROWS){
let ru = this.updateRow(row);
if(ru) updated = true;
}
}
let ans = [...Array(20).keys()].fill(' ');
for(let i = 0; i < 4; i++){
let row = ROWS[i];
for(let letter of row){
ans[5 * i + [...this.pos[letter]].pop()] = letter;
}
}
return ans.join('');
}
}
function solve(clues) {
let marple = new Marple(clues);
return marple.solve();
}
let tests = [
// ["MRT", "ABH", "LKO", "OKP", "JIM", "OPE", "GDO", "RAQ", "J^A", "M^P", "A<Q", "D<K", "OQO"],
["HJL","POA","DHJ","KMA","DRG","PHD","AMQ","H<F","M<K","F<E","M<I","T<E","CPC","SOS"]
// ["PBJ", "KDO", "DHG", "AOR", "INM", "EMB", "GTD", "O^T", "P<Q", "T<P", "A<L", "P<F", "RIR", "IDI"],
// ["EMJ", "DJO", "AMN", "ADC", "CIL", "END", "GQS", "SAB", "Q<B", "RPR", "SAS", "FNF", "NPN", "SCS"],
// ["LCI", "CQH", "NOF", "AEC", "APG", "NGL", "EQB", "F^P", "M^S", "E<J", "B<F", "T<P", "F<A", "P<K", "S<Q", "LCL"]
];
for(let test of tests){
let marple = new Marple(test);
let res = marple.solve();
console.log("res :", res);
}
To embed this program on your website, copy the following code and paste it into your website's HTML: