在几小时内平均分配物品数量

时间:2013-11-13 18:55:29

标签: c# math distribution

我想要在可变的小时数内传播可变数量的项目。我遇到的问题是如何分配余数,以便在"悬垂"之间留出空间。尽可能相等。例如,如果我有5个小时的13个项目(X),我希望最终得到

Hours:    1    2    3    4    5
---------------------------------
          x    x    x    x    x
          x    x    x    x    x  
          x         x         x

我不确定我是否过度思考这个问题。我目前正在检查物品数量是否大于小时数。如果这是真的,我划分(项目数/小时数)。然后我认为我必须划分(小时数/余数)...但是对于上面的例子:5/3 = 1.6,它转向2.我认为我必须以某种方式使用Math.Floor,但是我目前还不确定如何。

对于5个小时内的4个项目,我想最终得到X. 对于Ys的2个项目 对于带有Zs

的1个项目
 1    2    3    4    5
------------------------
 x    x         x    x

      y         y

           z

项目数和小时数是可变的。

好的,我想我现在正走在正确的轨道上。我现在试图将垃圾箱分成两半,然后把其中一个放在中间垃圾箱里。这会递归重复,直到余数为0。

2 个答案:

答案 0 :(得分:4)

编辑:修复了连播时间和物品的问题。

这是一个难题,下面是解决方案。我已经为完全通用的问题编写了解决方案,因此它适用于任意时间和项目数。以下是示例输出:

Items=10, Hours=14
XX XX XX XX XX

Items=11, Hours=14
XX XXXXX XX XX

Items=12, Hours=14
XX XXXXXXXX XX

Items=16, Hours=13
XXXXXXXXXXXXX
     XXX

Items=17, Hours=13
XXXXXXXXXXXXX
X    X X    X

Items=18, Hours=13
XXXXXXXXXXXXX
X    XXX    X

Items=19, Hours=13
XXXXXXXXXXXXX
X X  X X  X X

Items=20, Hours=13
XXXXXXXXXXXXX
X X  XXX  X X

Items=21, Hours=13
XXXXXXXXXXXXX
X XX X X XX X

以下是解决方案的工作原理:

  1. 填充行的数量是微不足道的,您可以通过(项目/小时)*小时获得它。
  2. 最后一行是需要所有魔法的地方。
  3. 当剩余物品的数量是奇数时,我们想要打开中心。如果小时数也是奇数,则中心定义明确,但否则我们运气不好,在这种情况下我们会有一些“不平衡”。
  4. 对于偶数项,我们将它们配对并按平衡二叉树的顺序分配每对。这基本上意味着我们首先将每一对放在最后。然后下一对中途并递归地遵循模式。这可能是最难理解的部分,因此建议使用纸和笔:)。
  5. 这是代码:

    static void Main(string[] args)
    {
        var hours = 13;
        for (var items = 16; items < 22; items++)
            PrintDistribution(items, hours);
    }
    
    private static void PrintDistribution(int items, int hours)
    {
        Console.WriteLine(string.Format("\nItems={0}, Hours={1}", items, hours));
        for (var i = 0; i < (items / hours) * hours; i++)
        {
            Console.Write('X');
            if ((i + 1) % hours == 0)
                Console.WriteLine();
        }
    
        var line = new StringBuilder(new string(' ', hours));
        var remaining = items % hours;
        var evens = remaining / 2;
        var odd = remaining - (evens * 2);
        var seq = BinaryTreeSequence(hours / 2).GetEnumerator();
        for (var i = 0; i < evens; i++)
        {
            seq.MoveNext();
            line[seq.Current] = 'X';
            line[hours - seq.Current - 1] = 'X';
        }
    
        if (odd > 0)
            if (hours % 2 == 0)
            {
                seq.MoveNext();
                line[seq.Current] = 'X';
            }
            else
                line[hours / 2] = 'X';
    
        Console.WriteLine(line);
    }
    
    
    public static IEnumerable<int> BinaryTreeSequence(int count)
    {
        if (count > 1)
            yield return count - 1; 
        if (count > 0)
            yield return 0;
    
        var seqQueue = new Queue<Tuple<int, int, int>>();
        Enqueue(seqQueue, 0, count - 1);
    
        for (var seqIndex = count - 2; seqIndex > 0; seqIndex--)
        {
            var moreNeeded = seqQueue.Count < seqIndex;
    
            var seq = seqQueue.Dequeue();
            yield return seq.Item1;
    
            if (moreNeeded)
            {
                Enqueue(seqQueue, seq.Item1, seq.Item3);
                Enqueue(seqQueue, seq.Item2, seq.Item1);
            }
        }
    }
    
    private static void Enqueue(Queue<Tuple<int, int, int>> q, int min, int max)
    {
        var midPoint = (min + max) / 2;
        if (midPoint != min && midPoint != max)
            q.Enqueue(Tuple.Create(midPoint, min, max));
    }
    

答案 1 :(得分:1)

这是一个近似的解决方案。它返回带有从零开始的索引和项目的元组。 (我认为这些项目可能很重要,而不仅仅是虚拟值,例如你的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的解决方案只有大小为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)

实际上,这是一种restricted partition问题。要在d小时内对h项进行分割,您希望找到h-d的分区,其中h-d个分区不超过max(parts)max(parts) == 1最小的分区。例如。在5小时内划分2个项目:最佳解决方案是1 + 1 + 1,因为它不超过3个部分,0,2,4,这是你能做的最好的。作为一个没有单一解决方案的示例,5个小时中的3个项目有1 + 1,但有不同的方式来安排它,包括1,3,40,2,3和{{1}}。