带/不带求解器的 C# 简单约束加权平均算法

本文关键字:约束 加权 算法 简单 | 更新日期: 2023-09-27 18:34:51

我不知道为什么我不能使用求解基础解决这个看似简单的问题Microsoft。

我所需要的只是修改某些观测值的权重(数字(,以确保没有 1 个观测值的权重百分比超过 25%。这是为了稍后使用此算法的结果计算受约束的加权平均值。

例如,给定 { 45, 100, 33, 500, 28 } 的 5 个权重,我希望此算法的结果为 { 45, 5333, 53, 28 },其中 2 个数字必须减少,使它们在新总数的 25% 阈值内 (212 = 45+53+33+53+28(,而其他数字保持不变。 请注意,尽管最初,第 100 的第二个权重仅占总数 (706( 的 14%,但由于减少了第 4 个权重 500,但它随后推高了其他观测值的百分比,这就是唯一的挑战。

我试图使用 Solver 重新创建它,只是为了让它告诉我它是"不可行"的解决方案,它只返回所有 1。 更新:解决方案不需要使用求解器,只要在处理相当数量的权重时速度快,欢迎任何替代方案。

var solver = SolverContext.GetContext();
var model = solver.CreateModel();
var decisionList = new List<Decision>();
decisionList.Add(new Decision(Domain.IntegerRange(1, 45), "Dec1"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 100), "Dec2"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 33), "Dec3"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 500), "Dec4"));
decisionList.Add(new Decision(Domain.IntegerRange(1, 28), "Dec5"));
model.AddDecisions(decisionList.ToArray());
int weightLimit = 25;
foreach (var decision in model.Decisions)
{
    model.AddConstraint(decision.Name + "weightLimit", 100 * (decision / Model.Sum(model.Decisions.ToArray())) <= weightLimit);
}
model.AddGoal("calcGoal", GoalKind.Maximize, Model.Sum(model.Decisions.ToArray()));
var solution = solver.Solve();
foreach (var decision in model.Decisions)
{
    Debug.Print(decision.GetDouble().ToString());
}
Debug.Print("Solution Quality: " + solution.Quality.ToString());

任何这方面的帮助将不胜感激,提前感谢。

带/不带求解器的 C# 简单约束加权平均算法

我放弃了求解器 b/c 它没有辜负它的名字 imo(或者我没有达到它的标准:)(。下面是我降落的地方。由于此函数在大量输入权重列表中多次使用,因此效率和性能是关键,因此此函数尝试尽可能少地执行#次迭代(如果有人有任何建议的改进,请告诉我(。结果用于加权平均值,因此我使用"属性权重对"来存储值(属性(及其权重,下面的函数是在给定这些 AWP 列表时将权重修改为在约束范围内。该函数假设 weightLimit 以 % 的形式传入,例如 25% 作为 25 传入,而不是 0.25 ---好吧,我将停止说明代码中显而易见的内容 - 所以这里是:

    public static List<AttributeWeightPair<decimal>> WeightLimiter(List<AttributeWeightPair<decimal>> source, decimal weightLimit)
    {
        weightLimit /= 100; //convert to percentage
        var zeroWeights = source.Where(w => w.Weight == 0).ToList();
        var nonZeroWeights = source.Where(w => w.Weight > 0).ToList();
        if (nonZeroWeights.Count == 0)
            return source;
        //return equal weights if given infeasible constraint
        if ((1m / nonZeroWeights.Count()) > weightLimit)
        {
            nonZeroWeights.ForEach(w => w.Weight = 1);
            return nonZeroWeights.Concat(zeroWeights).ToList();
        }
        //return original list if weight-limiting is unnecessary
        if ((nonZeroWeights.Max(w => w.Weight) / nonZeroWeights.Sum(w => w.Weight)) <= weightLimit)
        {
            return source;
        }
        //sort (ascending) and store original weights
        nonZeroWeights = nonZeroWeights.OrderBy(w => w.Weight).ToList();
        var originalWeights = nonZeroWeights.Select(w => w.Weight).ToList();
        //set starting point and determine direction from there
        var initialSumWeights = nonZeroWeights.Sum(w => w.Weight);
        var initialLimit = weightLimit * initialSumWeights;
        var initialSuspects = nonZeroWeights.Where(w => w.Weight > initialLimit).ToList();
        var initialTarget = weightLimit * (initialSumWeights - (initialSuspects.Sum(w => w.Weight) - initialLimit * initialSuspects.Count()));
        var antepenultimateIndex = Math.Max(nonZeroWeights.FindLastIndex(w => w.Weight <= initialTarget), 1); //needs to be at least 1        
        for (int i = antepenultimateIndex; i < nonZeroWeights.Count(); i++)
        {
            nonZeroWeights[i].Weight = originalWeights[antepenultimateIndex - 1]; //set cap equal to the preceding weight
        }
        bool goingUp = (nonZeroWeights[antepenultimateIndex].Weight / nonZeroWeights.Sum(w => w.Weight)) > weightLimit ? false : true;
        //Procedure 1 - find the weight # at which a cap would result in a weight % just UNDER the weight limit
        int penultimateIndex = antepenultimateIndex;
        bool justUnderTarget = false;          
        while (!justUnderTarget)
        {
            for (int i = penultimateIndex; i < nonZeroWeights.Count(); i++)
            {
                nonZeroWeights[i].Weight = originalWeights[penultimateIndex - 1]; //set cap equal to the preceding weight
            }
            var currentMaxPcntWeight = nonZeroWeights[penultimateIndex].Weight / nonZeroWeights.Sum(w => w.Weight);
            if (currentMaxPcntWeight == weightLimit) 
            {
                return nonZeroWeights.Concat(zeroWeights).ToList();
            }
            else if (goingUp && currentMaxPcntWeight < weightLimit)
            {
                nonZeroWeights[penultimateIndex].Weight = originalWeights[penultimateIndex]; //reset
                if (penultimateIndex < nonZeroWeights.Count() - 1)
                    penultimateIndex++; //move up
                else break;
            }
            else if (!goingUp && currentMaxPcntWeight > weightLimit)
            {
                if (penultimateIndex > 1)
                    penultimateIndex--; //move down
                else break;
            }
            else
            {
                justUnderTarget = true;
            }
        }
        if (goingUp) //then need to back up a step
        {
            penultimateIndex = (penultimateIndex > 1 ? penultimateIndex - 1 : 1);
            for (int i = penultimateIndex; i < nonZeroWeights.Count(); i++)
            {
                nonZeroWeights[i].Weight = originalWeights[penultimateIndex - 1];
            }
        }
        //Procedure 2 - increment the modified weights (subject to a cap equal to their original values) until the weight limit is hit (allowing a very slight overage for the last term in some cases)
        int ultimateIndex = penultimateIndex;
        var sumWeights = nonZeroWeights.Sum(w => w.Weight); //use this counter instead of summing every time for condition check within loop
        bool justOverTarget = false;
        while (!justOverTarget)
        {
            for (int i = ultimateIndex; i < nonZeroWeights.Count(); i++)
            {
                if (nonZeroWeights[i].Weight + 1 > originalWeights[i])
                {
                    if (ultimateIndex < nonZeroWeights.Count() - 1)
                        ultimateIndex++;
                    else justOverTarget = true;
                }
                else
                {
                    nonZeroWeights[i].Weight++;
                    sumWeights++;
                }
            }
            if ((nonZeroWeights.Last().Weight / sumWeights) >= weightLimit)
            {
                justOverTarget = true;
            }
        }
        return nonZeroWeights.Concat(zeroWeights).ToList();
    }
    public class AttributeWeightPair<T>
    {
        public T Attribute { get; set; }
        public decimal? Weight { get; set; }
        public AttributeWeightPair(T attribute, decimal? count)
        {
            this.Attribute = attribute;
            this.Weight = count;
        }
    }