根据值选择多个集合的最佳匹配组合

时间:2018-08-17 15:25:33

标签: c# entity-framework linq

我有一个.net核心Web应用程序,可供我们的员工根据输入的余额将产品与客户匹配。产品有5种。产品1必须作为主要产品包含在每个捆绑中。根据该值,其他4种产品将尽可能多地捆绑在一起。但是产品1也有多个不同的价格点。因此,可以说客户可以负担得起产品1的最高价格,但只能负担2个附加组件。我需要它下降到产品1的下一个值,并添加每个插件等。有人可以给我的任何指导都会真正帮助您。

1 个答案:

答案 0 :(得分:0)

假设您有一个Products表,其中有TypePrice

您可以将Products放入Type 1的其余部分中,并使用其他设备:

var Prod1 = Products.Where(p => p.Type == 1);
var ProdNot1 = Products.Where(p => p.Type != 1);

使用一些扩展方法,您可以创建非类型1产品的所有可能组合(如果从数据库中提取数据,则必须在内存中执行此操作):

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k) {
    return k == 0 ? new[] { new T[0] } :
      elements.SelectMany((e, i) =>
        elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
}
public static IEnumerable<IEnumerable<T>> AllCombinations<T>(this IEnumerable<T> elements) => Enumerable.Range(1, elements.Count()).SelectMany(k => elements.Combinations(k));

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) {
    var emptyProduct = Enumerable.Empty<T>().AsSingleton();
    return sequences.Aggregate(
      emptyProduct,
      (accumulator, sequence) =>
        from accseq in accumulator
        from item in sequence
        select accseq.Append(item));
}
public static IEnumerable<IEnumerable<T>> CartesianProductWithEmpties<T>(this IEnumerable<IEnumerable<T>> sequences) =>
    sequences.AllCombinations().SelectMany(s => s.CartesianProduct());

现在您可以计算AddOnCombos

var AddOnCombos = ProdNot1.GroupBy(p => p.Type)
                          .CartesianProductWithEmpties()
                          .Select(tpc => new { Products = tpc, Price = tpc.Sum(p => p.Price), Types = tpc.Sum(p => p.Type) });

使用价格最低的Type 1产品:

var minPriceProd1 = Prod1.MinBy(p => p.Price);

您可以使用CustBal来估算附加产品的预算:

var CustBal = 375;
var nonProd1BalGuess = CustBal - minPriceProd1.Price;

然后,您可以猜测哪个附加组合将起作用,最大化类型数,优先处理较低类型并以最低价格购买

var nonProd1Guess = AddOnCombos.Where(aoc => aoc.Price <= nonProd1BalGuess)
                           .OrderByDescending(aoc => aoc.Products.Count())
                           .ThenBy(aoc => aoc.Types)
                           .ThenBy(aoc => aoc.Price)
                           .FirstOrDefault();

使用附加猜测,您可以猜测获得价格较高的Type 1产品的剩余预算。

var allProdBalGuess = nonProd1BalGuess - (nonProd1Guess?.Price ?? 0);

然后计算类型1的最终产品以及附加产品剩余的数量:

var finalProd1 = Prod1.Where(p => p.Price <= minPriceProd1.Price + allProdBalGuess).OrderByDescending(p => p.Price).First();
var nonProd1Bal = CustBal - finalProd1.Price;

然后找到最佳的附加组件组合:

var nonProd1 = AddOnCombos.Where(aoc => aoc.Price <= nonProd1Bal)
                           .OrderByDescending(aoc => aoc.Products.Count())
                           .ThenBy(aoc => aoc.Types)
                           .ThenByDescending(aoc => aoc.Price)
                           .ThenByDescending(aoc => Enumerable.Range(2, 5).Select(t => aoc.Products.FirstOrDefault(p => p.Type == t)?.Price ?? 0), new SeqCompare<double>())
                           .FirstOrDefault();

要优先考虑在较低的Type产品上花费更多的预算,您需要一些扩展来比较价格顺序:

public static class IEnumerableExt {
    public static int SeqCompareTo<T>(this IEnumerable<T> s1, IEnumerable<T> s2) => SequenceCompare(s1, s2);

    public static int SequenceCompare<T>(IEnumerable<T> source1, IEnumerable<T> source2) {
        // You could add an overload with this as a parameter
        IComparer<T> elementComparer = Comparer<T>.Default;
        using (IEnumerator<T> iterator1 = source1.GetEnumerator())
        using (IEnumerator<T> iterator2 = source2.GetEnumerator()) {
            while (true) {
                bool next1 = iterator1.MoveNext();
                bool next2 = iterator2.MoveNext();
                if (!next1 && !next2) // Both sequences finished
                    return 0;
                if (!next1) // Only the first sequence has finished
                    return -1;
                if (!next2) // Only the second sequence has finished
                    return 1;
                // Both are still going, compare current elements
                int comparison = elementComparer.Compare(iterator1.Current,
                                                         iterator2.Current);
                // If elements are non-equal, we're done
                if (comparison != 0)
                    return comparison;
            }
        }
    }
}

public class SeqCompare<T> : IComparer<IEnumerable<T>> {
    public int Compare(IEnumerable<T> x, IEnumerable<T> y) => x.SeqCompareTo(y);
}