I have been iterating with both Claude 3.7 Sonnet, and ChatGPT 4o, for days and days, trying to get this to be exactly how I want it, but it keeps making mistakes in the algorithm, and on an on in making the same mistakes in basically a circle.
How do I make a "layout" (array of integers), which represents a grid, which has the following constraints:
generateGridLayout(n, minColumns, maxColumns)
, where practically speaking it is usually called as gen(n, 3, 7)
, but could in theory be anything which would make a nice grid, say gen(n, 2, 256)
as the max range.Number.MAX_SAFE_INTEGER
, which is 9007199254740991
, but practically my "grid layouts" will only have mainly up to 1000 items).n
is odd, then each row should only have an odd number of values. If n
is even, then each row can have either even or odd numbers (think 30, can be 10 rows of 3, or 3 rows of 10).2
in size, always decreasing. It can't ever differ by 3, 4, etc.. This means that 7 CANNOT be [5, 2] or [4, 4, 1], as those have jumps > 2. It can only be [7] or [3, 3, 1], if gen(7, 3, 7)
.maxColumns - 1
, and so on, down to minColumns
.Here are some examples to demonstrate the goal:
31
[5, 5, 5, 5, 5, 3, 3]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1]
30
[5, 5, 5, 5, 5, 5]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
29
[5, 5, 5, 5, 5, 3, 1]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1]
28
[6, 6, 6, 6, 4]
[4, 4, 4, 4, 4, 4, 4]
27
[5, 5, 5, 5, 3, 3, 1]
[3, 3, 3, 3, 3, 3, 3, 3, 3]
26
[6, 6, 6, 4, 2, 2]
[4, 4, 4, 4, 4, 4, 4]
23
[5, 5, 5, 5, 3]
[3, 3, 3, 3, 3, 3, 3, 1, 1]
These show, if 5 or 6 is the max columns, what it would do, and what it should do if 4 or 3 is the max columns.
For example, 26 at 7 max columns should NOT be:
[6, 6, 6, 6, 2] # jumps more than 2
[4, 4, 4, 4, 4, 4, 2] # doesn't maximize maxColumns
It should ideally be:
[6, 6, 6, 4, 2, 2] # jumps max 2, maximizes large columns, minimizes small columns.
Here is my current solution:
log(29, 2, 7)
log(29, 2, 6)
log(29, 2, 5)
log(29, 2, 4)
log(44, 2, 3)
function distributeGridLayout(
length,
minColumns,
maxColumns
) {
function recur(
dp,
length,
width,
) {
if (length == 0) {
return []
}
if (length < width - 2 || width <= 0) {
return
}
if (dp[width].has(length)) {
return
}
dp[width].add(length)
for (let i = 0; i < 2; i++) {
let result = recur(dp, length - width, width)
if (result) {
return [width, ...result]
}
width -= 2
}
return
}
if (length <= maxColumns) {
return [length]
}
if (maxColumns >= 3 && length === 7) {
return [3, 3, 1]
}
if (maxColumns >= minColumns && length % maxColumns === 0) {
const result = []
while (length) {
result.push(maxColumns)
length -= maxColumns
}
return result
}
if (maxColumns > 4) {
if (maxColumns > minColumns && length % (maxColumns - 1) === 0) {
const result = []
maxColumns--
while (length) {
result.push(maxColumns)
length -= maxColumns
}
return result
}
}
const dec = 2 - (length % 2)
maxColumns -= maxColumns % dec
const dp = Array.from(
{ length: maxColumns + 1 },
() => new Set(),
)
for (let width = maxColumns; width > 0; width -= dec) {
const result = recur(dp, length - width, width)
if (result) {
if (width <= minColumns) {
return
}
return [width, ...result]
}
}
return
}
function log(n, min, max) {
const grid = distributeGridLayout(n, min, max)
console.log(`gen(${n}, ${min}, ${max})`, grid)
}
That is working for most, like this Tibetan layout (29 chars):
But it is not working for this Thai layout (44 chars, in 2-3 columns), here is the end of it (in my UI, if the algorithm returns undefined, it falls back to a basic grid layout):
What do I need to change exactly so this always fits my rules? The 44 layout of 3 max 2 min should be basically a 2 column layout...
This looks like an evolution on one of your earlier questions, with at least the difference that here we have a minColumns
requirement.
I didn't quite understand why in your code there is a specific treatment for the case length==7
.
Also the two blocks that check for length % maxColumns === 0
and length % (maxColumns - 1) === 0
seem quite arbitrary (like, why then not also length % (maxColumns - 2) === 0
, ...etc?): these don't feel as real boundary cases that need special treatment.
The exit condition inside the main loop, when width <= minColumns
, should not depend on whether you got a successful result, but could just have been taken on board as a for
loop condition (the opposite of it).
There is also a problem with maxColumns -= maxColumns % dec
: it makes maxColumns
even when it was odd and length
is even. Instead, you'll want maxColumns
to be made odd when length
is odd. I don't remember why this was so in the answer to your previous question, where the question had less specifications. Anyhow, this must be changed. It may be the easiest for code maintenance to just have a condition inside the main loop.
There seem to be at least two mistakes in your expected results:
26 [6, 6, 6, 4, 2, 2] [4, 4, 4, 4, 4, 4, 4]
I will assume this should be:
26
[6, 6, 6, 4, 4] // because of rule 7
[4, 4, 4, 4, 4, 4, 2] // because sum must be 26
Here is corrected code with some of your test cases.
function distribute(length, minColumns, maxColumns) {
const dp = Array.from({length: maxColumns + 1}, () => new Set());
function recur(length, width) {
if (length === 0) return [];
if (length < width - 2 || width <= 0) return null;
if (dp[width].has(length)) return null;
dp[width].add(length);
for (let i = 0; i < 2; i++) {
let result = recur(length - width, width);
if (result) return [width, ...result];
width -= 2;
}
return null;
}
for (let width = maxColumns; width >= minColumns; width--) {
// An odd length cannot be fulfilled with an even column count
if (length % 2 > width % 2) continue;
const result = recur(length - width, width);
if (result) return [width, ...result];
}
return null;
}
const tests = [
// [length, minColumns, maxColumns, ...expected result]
[31, 1, 6, 5, 5, 5, 5, 5, 3, 3],
[31, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1],
[30, 1, 5, 5, 5, 5, 5, 5, 5],
[30, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[29, 1, 6, 5, 5, 5, 5, 5, 3, 1],
[29, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1],
[28, 1, 6, 6, 6, 6, 6, 4],
[28, 1, 4, 4, 4, 4, 4, 4, 4, 4],
[27, 1, 6, 5, 5, 5, 5, 3, 3, 1],
[27, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[26, 1, 6, 6, 6, 6, 4, 4],
[26, 1, 4, 4, 4, 4, 4, 4, 4, 2],
[23, 1, 6, 5, 5, 5, 5, 3],
[23, 1, 4, 3, 3, 3, 3, 3, 3, 3, 1, 1],
[29, 2, 7, 7, 7, 7, 5, 3],
[29, 2, 6, 5, 5, 5, 5, 5, 3, 1],
[44, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1],
[7, 2, 6, 3, 3, 1],
];
for (const [length, minColumns, maxColumns, ...expected] of tests) {
const result = distribute(length, minColumns, maxColumns);
if (JSON.stringify(result) !== JSON.stringify(expected)) {
throw Error(`Expected distribute(${length}, ${minColumns}, ${maxColumns}) to return ${JSON.stringify(expected)}, but got ${JSON.stringify(result)}`);
}
}
console.log("All tests passed successfully.");