这是一个近似的解决方案。它返回具有从零开始的索引和项目的元组。(我认为这些项目可能很重要,而不仅仅是像你x
的 s 这样的虚拟值)在某些情况下它不会选择最佳间距,但我认为它总是很接近(即间隙不超过必要的 1) ,并始终返回正确数量的项目。
public static IEnumerable<Tuple<int, T>> SplitItems<T>(IEnumerable<T> items, int count)
{
var itemList = items.ToList();
int lastRowCount = itemList.Count % count;
int wholeRowItemCount = itemList.Count - lastRowCount;
// return full rows: 0 <= i < wholeRowCount * count
for (int i = 0; i < wholeRowItemCount; i++)
{
yield return Tuple.Create(i % count, itemList[i]);
}
if (lastRowCount > 0)
{
//return final row: wholeRowCount * count <= i < itemList.Count
double offset = (double)count / (lastRowCount + 1);
for (double j = 0; j < lastRowCount; j++)
{
int thisIntPos = (int)Math.Round(j * count / (lastRowCount + 1) + offset, MidpointRounding.AwayFromZero);
yield return Tuple.Create(thisIntPos, itemList[wholeRowItemCount + (int)j]);
}
}
}
作为如何使用它的示例:
Console.WriteLine(string.Join("\r\n", SplitItems(Enumerable.Range(1, 12), 5)));
// prints
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(0, 6)
(1, 7)
(2, 8)
(3, 9)
(4, 10)
(2, 11)
(3, 12)
(这是次优的,因为最后一行在 2-3 处有项目,在 0-1 和 4 处有空格/间隙,而您的y
s 解决方案只有大小为 1 的间隙)
此外,虽然它与您的示例不匹配(在我的从零开始的索引中为 0、2、4),但以下示例满足您迄今为止定义的算法,因为它最小化了间隙大小。(索引 0 和 2 处的 1 大小间隙,而不是您的,在 1 和 3 处有间隙)如果0, 2, 4
确实优于1, 3, 4
,您需要确定确切原因,并将其添加到您的算法定义中。
Console.WriteLine(string.Join("\r\n", SplitItems(Enumerable.Range(1, 3), 5)));
// prints
(1, 1)
(3, 2)
(4, 3)
实际上,这是一种受限分区问题。为了按小时划分d
项目h
,您希望找到一个h-d
不超过h-d
部分的分区,max(parts)
它可以是最小的。例如,在 5 个小时中划分 2 项:最优解是 1+1+1,因为它不超过 3 个部分,并且max(parts) == 1
,这是你能做的最好的。以没有单一解的例子为例,5 小时中的 3 项有 1+1,但有不同的排列方式,包括0,2,4
、1,3,4
和0,2,3
。