假设你有一个包含 N 个元素的数组,你想将其分成 M 个块。例如,当尝试将 N 个工作单元分散到 M 个线程上时,这是一个常见的任务。
您希望所有块的大小相同,上下浮动一个元素(合理大小)。这可能比您想象的要难一些。
按照 Python 的约定,a//b 是整数商,因此 5//2 为 2。令 % 为“余数”:5%2 为 1。
朴素算法使用以下块范围:[0, N//M), [N/M, 2*N//M)… 直到 [N//M*M, N)。因此,所有块的大小均为 N/M,但存在一个大小为 N%M 的额外块。
如果 N 为 5 且 M 为 2,则我们有块 [0,2)、[2,4)、[4,5]。
这可不太好。我们多了一个块!至少当 N//M 至少为 1 时是这样。
相反,我们可以让最后一个块更大一些。我们使用的范围是 [0, N//M), [N/M, 2*N//M)… 直到 [N//M*(M-1), N)。如果 N 为 5,M 为 2,那么块数就是 [0,2), [2,5)。至少我们有正确数量的块。
但是如果 N 是 20,M 是 6 呢?我们会得到 [0,3), [3,6), [6,9), [9,12), [12,15), [15,20] 这很糟糕,因为最后一个块要大得多。
相反,您可以尝试以下方法:让块大小为 ceil(N/M)。也就是说,块大小是 N/M 向上舍入。因此,如果 N 为 5 且 M 为 2,则块大小为 ceil(5/3) 或 3。我们使用块范围:[0, ceil(N/M)), [N/M, 2*ceil(N/M))…[(M-1)*ceil(N/M), N)。如果 N 为 5 且 M 为 2,则块为 [0,3), [3,5)。这样要好得多。这次,我们有正确数量的块。一个大小为 3,另一个大小为 2。但如果 N 为 12 且 M 为 5,则失败。我们得到块大小为 3,因此范围为 [0,3), [3,6), [6,9), [9, 12)。所以我们缺少一个块!
那么你应该怎么做呢?更好的方法是让前 N%M 个块的大小为 ceil(N/M),让剩余的 NN%M 个块的大小为 floor(N/M)。
让我用 Python 实现它。get_chunk_range_simple 函数将总共 N 个元素分成 M 个块,并返回第 i 个块(从 0 开始索引)的起始和结束索引。它通过将余数分配到各个块来处理不均匀的划分:第一个余数块(其中余数 = N % M)的大小商为 + 1(其中商 = N // M),而其余块的大小商为。对于给定的块索引 i,该函数将起始值计算为商 * i + min(i, remainder),其中考虑了前面块中的额外元素,并将结束值计算为商 * (i + 1) + min(i + 1, remainder),以确保范围反映先前块大小的累积效应。这种方法确保覆盖所有元素,并且前面的块吸收余数以实现均衡分布。
def get_chunk_range_simple ( N , M , i ) : 商,余数= divmod ( N , M ) 返回(商* i + min ( i ,余数) , 商* ( i + 1 ) + min ( i + 1 ,余数) )
结果大小适中。我们来举例说明一下 N 为 20、M 为 6 的情况。
我们也可以用 C++ 来实现它。为了方便修改,我返回给定块的索引和长度。
// N 是元素总数 // M 是块的数量 // i 是块的索引(从 0 开始) std :: pair < size_t , size_t > get_chunk_range_simple ( size_t N , size_t M , size_t i ) { // 计算商和余数 size_t商= N / M ; size_t余数= N % M ; size_t start =商* i + ( i <余数? i :余数) ; size_t长度=商+ ( i <余数? 1 : 0 ) ; 返回{开始,长度} ; }
来源:该问题由罗伯特·克劳塞克 (Robert Clausecker) 在 X 上发布。
原文: https://lemire.me/blog/2025/05/22/dividing-an-array-into-fair-sized-chunks/