在Set上分配N个项目至少一次

时间:2016-06-11 14:02:33

标签: c# probability

给定字典中的一组N个项及其与之关联的事件。 现在我必须根据每个项目的整体概率为每个项目分配精确的X个插槽,但每个项目至少有1个插槽。

以下是我提出的建议:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public static class Program
{
    public static void Main( string[] args )
    {
        var dict = new Dictionary<char,int>();

        dict.Add( 'a' , 10 );   dict.Add( 'b' , 0 );
        dict.Add( 'c' , 4 );    dict.Add( 'd' , 1 );
        dict.Add( 'e' , 9 );    dict.Add( 'f' , 0 );

        var distributionMap = Distribute( dict , 40 );
    }

    public static Dictionary<T,int> Distribute<T>( Dictionary<T,int> occurMap , int slots )
    {
        var freeSlots = slots - occurMap.Count;
        var total = occurMap.Sum( x => x.Value );
        var distMap = new Dictionary<T,int>();

        foreach( var pair in occurMap )
        {
            var probability = (double)pair.Value / total;
            var assignedSlots = probability * freeSlots;

            distMap[ pair.Key ] = (int)( 1 + assignedSlots );
        }

        Debug.Assert( distMap.Select( x => x.Value ).Sum() == slots );

        return distMap;
    }
}

然而,断言触发,因为从doubleint的转换会在某个时刻截断概率。

如何根据计数将所有广告位至少映射一次?

2 个答案:

答案 0 :(得分:1)

之前的方法是根据totalcount分配剩余的项目,而根据它们的小数部分分配它们似乎更合理。例如,如果有一个最后一个要分配的槽,则一个0.8的项应该比最后一个槽有45.3(并且之前已经有45个槽)

我会:

  • 使用计算的积分部分初始化槽并跟踪剩余的小数部分
  • 然后按降序排列每个项目的小数部分并分配它们,直到没有剩余的插槽

示例实现如下所示:

public static Dictionary<T,int> Distribute<T>( Dictionary<T,int> occurMap , int slots )
{
    var freeSlots = slots -  occurMap.Count;
    var totalFreeSlots = freeSlots;
    var total = occurMap.Sum( x => x.Value );
    var distMap = new Dictionary<T,int>();
    var remainingSlots = new Dictionary<T,double>();

    foreach( var pair in occurMap )
    {
        var probability = (double)pair.Value / total;
        var assignedSlots = probability * totalFreeSlots;

        var integralPart = Math.Truncate(assignedSlots);
        var fractionalPart = assignedSlots - integralPart;                    

        distMap[ pair.Key ] = 1 + (int)integralPart;
        remainingSlots[pair.Key] = fractionalPart;
        freeSlots -= (int)integralPart;
    }   

    foreach (var pair in remainingSlots.ToList().OrderByDescending(x => x.Value))
    {
        if (freeSlots == 0)
            break;

        distMap[ pair.Key ]++;
        freeSlots -= 1;
    }             

    return distMap;
}

答案 1 :(得分:0)

因为插槽的数量是整数而平均频率不是 - 在初始空闲插槽分配之后,您可能有剩余空闲插槽(如果您将频率降低)或者可能分配了比实际更多的插槽(如果您向上舍入) )。合理的方法是:

  1. 首先根据向下舍入的频率(即您已经做过的事情)分配所有插槽
  2. 然后从最频繁的项目开始逐个分配左侧插槽(剩下的空闲插槽数量不能超过项目总数)。
  3. 示例实施:

    public static Dictionary<T, int> Distribute<T>(Dictionary<T, int> occurMap, int slots)
    {
        if(slots < occurMap.Count)
            throw new ArgumentException("Not enough slots");
    
        var freeSlots = slots - occurMap.Count;
        var total = occurMap.Sum(x => x.Value);
        var distMap = new Dictionary<T, int>();
        var keysByProb = new Queue<T>();
        foreach (var pair in occurMap.OrderByDescending(c => (double)c.Value / total))
        {
            var probability = (double)pair.Value / total;
            var assignedSlots = probability * freeSlots;
    
            distMap[pair.Key] = 1+ (int)Math.Floor(assignedSlots);
            keysByProb.Enqueue(pair.Key);
        }
        var left = slots - distMap.Select(x => x.Value).Sum();
        while (left > 0) {
            distMap[keysByProb.Dequeue()]++;
            left--;
        }
        Debug.Assert(distMap.Select(x => x.Value).Sum() == slots);
        return distMap;
    }