The Numbers Game – the AI version
Having got my web based HTML and Javascript version working perfectly I didn’t think I would be adding a new page to this story, but here I am.
I got a book out of the library a couple of weeks ago, about using AI to write software (Coding with AI by Chris Minnick).I really tried to read it and get something from it, but I think it was beyond me.However AI is supposed to be easy, and I have been designing and programming software of all types since 1972, so I decided to give it a go.
I used ChatGTP for my first and only go (I don’t give AI too many chances, its not impressed me so far).
So I asked it something like this
“Write a python program that will take 6 integers entered by the user, and use the add, subtract,multiply and divide operations to calculate a a match for a 3 digit integer entered by the user. All intermediate answers must be positive integers.”
Its first try was a good effort, but seriously flawed. It took all the permutations of 6 numbers (6! or 720) and all the possible combinations of 5 operators from the 4 possible (4**5 or 1024), and worked out every answer until it found a match, or process all 737,280 sequences and not find a match. The flaw is this does not take account of the hierarchy of operators, or BODMAS as I was taught (Brackets, Orders, Division, Multiplication, Addition, Subtraction).
I asked whether the solution used BODMAS to do this, and it came back immediately with an admission of the error, a proposed solution which it seemed reluctant to implement as it would be a massive number of complicated ‘infix’ type sequences using all possible sequences of numbers, signs and brackets.
My next question was whether Reverse Polish Notation would help. Now it got very excited, congratulating me on my insight, explaining how RPN and the use of stacks would make a much faster and efficient method, BUT – the number of possible RPN sequances would be very large and still not possible on domestic type hardware. Something to do with a large Catalan number – well beyond my mathematical abilities
Obviously I know this is possible, having spent days figuring how to do it.
My next communication was that with 6 numbers and 5 signs there are only 256 RPN sequences available, and only 42 of them are valid. Not a question this time, just a statement.
If it was excited last time, it nearly blew a valve this time. It saw the point immediately and explained how it worked and calculated a new Catalan number of 42. From this it generated the number of possible combinations as 30,965,760 – a number I am very familiar with as it is the raw number I came up with. I have refined this to a million or so lower, removing impossible combinations of signs (such as a combination of 5 divisions and subtractions).
At this point it asked my if I wanted to generate a python program with a Tinker GUI, or just go straight to a web version, using HTML with embedded javascript that removes the need for a server or a compliler/interpreter. As this is how my version works, I went with this one.
Here is what it generated. This whole process took less that an hour of my time, and initial testing shows it working perfectly and almost instantly. It only finds one answer, compared to the 10- 100 mine does, but it is pretty damn slick! Processing all 31 million possibilities takes just 23 seconds!
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>RPN Number Solver</title>
<style>
body { font-family: Arial; text-align: center; padding: 30px; }
input { width: 50px; margin: 5px; }
button { padding: 10px 20px; font-size: 16px; }
#result { margin-top: 20px; font-weight: bold; color: blue; }
</style>
</head>
<body>
<h2>RPN-Based Number Solver</h2>
<p>Enter 6 numbers and a 3-digit target. All intermediate results must be positive integers.</p>
<div>
<input type=”number” id=”num0″>
<input type=”number” id=”num1″>
<input type=”number” id=”num2″>
<input type=”number” id=”num3″>
<input type=”number” id=”num4″>
<input type=”number” id=”num5″>
</div>
<p>Target: <input type=”number” id=”target” style=”width: 80px;”></p>
<button onclick=”solve()”>Solve</button>
<div id=”result”></div>
<script>
const operators = {
‘+’: (a, b) => a + b,
‘-‘: (a, b) => (a > b ? a – b : null),
‘*’: (a, b) => a * b,
‘/’: (a, b) => (b !== 0 && a % b === 0 ? a / b : null)
};
function generateRpnTemplates() {
const results = [];
function backtrack(seq, nCount, oCount) {
if (seq.length === 11) {
results.push([…seq]);
return;
}
if (nCount < 6) backtrack([…seq, ‘N’], nCount + 1, oCount);
if (oCount < 5 && oCount < nCount – 1) backtrack([…seq, ‘O’], nCount, oCount + 1);
}
backtrack([], 0, 0);
return results;
}
const rpnTemplates = generateRpnTemplates();
function permutations(arr) {
if (arr.length <= 1) return [arr];
let result = [];
for (let i = 0; i < arr.length; i++) {
const rest = […arr.slice(0, i), …arr.slice(i + 1)];
for (const p of permutations(rest)) {
result.push([arr[i], …p]);
}
}
return result;
}
function cartesianProduct(arr, repeat) {
if (repeat === 1) return arr.map(x => [x]);
const result = [];
const prev = cartesianProduct(arr, repeat – 1);
for (const val of arr) {
for (const seq of prev) {
result.push([val, …seq]);
}
}
return result;
}
function evaluateRPN(template, numbers, opsSeq) {
const stack = [];
let nIdx = 0, oIdx = 0;
for (const token of template) {
if (token === ‘N’) {
stack.push(numbers[nIdx++]);
} else {
if (stack.length < 2) return null;
const b = stack.pop();
const a = stack.pop();
const result = operators[opsSeq[oIdx++]](a, b);
if (result == null || result <= 0 || !Number.isInteger(result)) return null;
stack.push(result);
}
}
return stack.length === 1 ? stack[0] : null;
}
function buildExpression(template, numbers, opsSeq) {
const stack = [];
let nIdx = 0, oIdx = 0;
for (const token of template) {
if (token === ‘N’) {
stack.push(numbers[nIdx++].toString());
} else {
const b = stack.pop();
const a = stack.pop();
stack.push(`(${a} ${opsSeq[oIdx++]} ${b})`);
}
}
return stack[0];
}
function solve() {
const nums = [];
for (let i = 0; i < 6; i++) {
const val = parseInt(document.getElementById(‘num’ + i).value);
if (isNaN(val)) {
document.getElementById(“result”).textContent = “Please enter all 6 numbers.”;
return;
}
nums.push(val);
}
const target = parseInt(document.getElementById(“target”).value);
if (isNaN(target) || target < 100 || target > 999) {
document.getElementById(“result”).textContent = “Target must be a 3-digit number.”;
return;
}
const numPerms = permutations(nums);
const opPerms = cartesianProduct(Object.keys(operators), 5);
for (const n of numPerms) {
for (const o of opPerms) {
for (const tmpl of rpnTemplates) {
const result = evaluateRPN(tmpl, n, o);
if (result === target) {
const expr = buildExpression(tmpl, n, o);
document.getElementById(“result”).textContent = `✔ ${expr} = ${target}`;
return;
}
}
}
}
document.getElementById(“result”).textContent = “❌ No valid expression found.”;
}
</script>
</body>
</html>