我已经连续几天使用 Claude 3.7 Sonnet 和 ChatGPT 4o 进行迭代,试图让它完全符合我的要求,但它在算法上不断犯错误,并且基本上循环往复地犯同样的错误。
如何制作一个“布局”(整数数组),它代表一个网格,具有以下约束:
- 该函数是
generateGridLayout(n, minColumns, maxColumns)
,实际上它通常被称为gen(n, 3, 7)
,但理论上可以是任何可以形成漂亮网格的东西,比如gen(n, 2, 256)
最大范围。 - 它应该处理任意数量的元素(比如最多
Number.MAX_SAFE_INTEGER
,也就是9007199254740991
,但实际上我的“网格布局”主要只有最多 1000 个项目)。 - 如果项目数量
n
为奇数,则每行只能包含奇数个值。如果项目n
数量为偶数,则每行可以包含偶数或奇数个值(例如,30,可以是 10 行,每行 3 个值,或者 3 行,每行 10 个值)。 - 行数之间的差异只能是
2
,并且始终是递减的。差异不能是 3、4 等等。这意味着 7 不能是 [5, 2] 或 [4, 4, 1],因为它们的跳跃次数大于 2。如果gen(7, 3, 7)
,它只能是 [7] 或 [3, 3, 1]。 - (元注释,这是一个 UI 布局,因此它基于视口/容器大小,所以如果我们说“最大值是 7”,但只有 5 个空间,它会将最大值设置为 5,但这个事实与解决方案并不相关)
- 如果我们说“最大值为 7”,但项目数为偶数,并且偶数不能满足“所有偶数行或所有奇数行”的条件,则尝试
maxColumns - 1
,依此类推,直到minColumns
。 - 重要提示:它应该最小化小行的数量。因此,对于 29,当最大列数为 6 时,它应该是 [5, 5, 5, 5, 5, 3, 1],而不是 [5, 5, 5, 5, 3, 3, 3]。也就是说,它最大化了大行的数量,并最小化了小行的数量。同样,对于 29,它绝对不应该是 [5, 5, 5, 5, 3, 3, 1, 1, 1]。
以下是一些示例来说明该目标:
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]
这些显示,如果 5 或 6 是最大列,它会做什么,以及如果 4 或 3 是最大列,它应该做什么。
例如,26 个最大 7 列不应该是:
[6, 6, 6, 6, 2] # jumps more than 2
[4, 4, 4, 4, 4, 4, 2] # doesn't maximize maxColumns
理想情况下应该是:
[6, 6, 6, 4, 2, 2] # jumps max 2, maximizes large columns, minimizes small columns.
这是我当前的解决方案:
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)
}
这对大多数人来说都是有效的,比如这个藏文布局(29 个字符):

但它不适用于这种泰语布局(44 个字符,2-3 列),这是它的结尾(在我的 UI 中,如果算法返回未定义,它会回退到基本的网格布局):

我到底需要修改什么才能让它始终符合我的规则?44 布局(3 最大 2 最小)基本上应该是一个 2 列布局……
这看起来像是你之前的一个问题的演变,至少这里有一个区别,那就是我们有一个
minColumns
要求。我不太明白为什么你的代码中对这种情况有特殊的处理
length==7
。另外,检查
length % maxColumns === 0
和的两个块length % (maxColumns - 1) === 0
似乎相当随意(比如,为什么不呢length % (maxColumns - 2) === 0
,...等等?):这些感觉不像是需要特殊处理的真正边界情况。主循环内的退出条件(何时)不应取决于您是否获得了成功的结果,而应将其作为循环条件(与之相反)
width <= minColumns
来考虑。for
还有一个问题
maxColumns -= maxColumns % dec
:maxColumns
当它是奇数并且length
是偶数时,它会变成偶数。相反,你会希望maxColumns
当它是奇数时变成奇数length
。我不记得你之前问题的答案中为什么会这样,因为这个问题没有太多细节。无论如何,这必须改变。在主循环中只添加一个条件可能最方便代码维护。您的预期结果似乎至少存在两个错误:
我认为这应该是:
这是包含一些测试用例的更正代码。