对于网上商店,我正在寻找查询以选择最好(最便宜)的产品和包组合。非常简化,数据库看起来像这样:
Name Price
-----------
Item1 10
Item2 20
Item3 30
Item4 40
Name Price
-------------------
ItemPackage1 25
ItemPackage2 60
ItemPackage3 50
ID ItemPackage Item
------------------------------------------
ItemPackageItems1 ItemPackage1 Item1
ItemPackageItems2 ItemPackage1 Item2
ItemPackageItems3 ItemPackage2 Item3
ItemPackageItems4 ItemPackage2 Item4
ItemPackageItems5 ItemPackage3 Item1
ItemPackageItems6 ItemPackage3 Item2
ItemPackageItems7 ItemPackage3 Item2
当客户订购不同的商品时,如何获得所有组合的价格或最佳组合。
Item1 + Item2 + Item3 should return ItemPackage1 + Item3
Item1 + Item3 + Item4 should return Item1 + ItemPackage2
Item1 + Item3 should return Item1 + Item3
SQL或Linq可能会达到什么目的? 谢谢你
答案 0 :(得分:0)
我写了几个类来处理这个优化问题,但是,它还没有完成(我现在已经离开了我的电脑并且将会回来 - 你可以先看看这个,看看它是否有用) ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp.Optimizer
{
public interface IUnit
{
int Id { get; set; }
float UnitPrice { get; set; }
}
public interface IOrder
{
int Quantity { get; set; }
float GetTotalPrice();
}
public interface IOrder<T> : IOrder where T : IUnit
{
T Unit { get; }
}
public abstract class AbstractUnit : IUnit
{
public int Id { get; set; }
public float UnitPrice { get; set; }
}
public class Item : AbstractUnit
{
public string Name { get; set; }
}
public class Package : AbstractUnit
{
private List<Order<Item>> _items = new List<Order<Item>>();
public IEnumerable<Order<Item>> Items
{
get { return this._items; }
}
}
public class Order<T> : IOrder<T> where T : IUnit
{
T _unit;
public Order(T unit)
{
_unit = unit;
}
public T Unit { get { return _unit; } }
public int Quantity { get; set; }
public float GetTotalPrice()
{
return _unit.UnitPrice * Quantity;
}
}
public class Combination
{
private List<IOrder> _orders = new List<IOrder>();
private Combination()
{
// Private constructor
}
public List<IOrder> Orders { get { return _orders; } }
public float GetTotalPrice()
{
if (_orders.Any())
{
return _orders.Select(m => m.GetTotalPrice()).Sum();
}
return 0;
}
public static Combination GetBestCombination(IEnumerable<Order<Item>> intendedItems, IEnumerable<Package> allPacksSetupFromDB)
{
var potentialCombinations = new List<Combination>();
// First comb: treat each item as a standalone IOrder:
var com = new Combination();
foreach (var ele in intendedItems)
{
com.Orders.Add(ele);
}
potentialCombinations.Add(com);
// check each possible pack (loaded from db) and find all possible combinations
var possiblePacks = allPacksSetupFromDB.Where(m => m.Items.All(n => (intendedItems.Any(t => t.Unit.Id == n.Unit.Id && t.Quantity >= n.Quantity)))).ToArray();
//ToDo: in the possible packages to use, find out all possible combinations
// This is the tricky part...
// The one with lowest price is desired!
return potentialCombinations.OrderBy(m => m.GetTotalPrice()).FirstOrDefault();
}
}
}
编辑:
我以为你会继续这个,无论如何,我刚出来一个未经测试的版本:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp.Optimizer
{
public interface IUnit
{
int Id { get; set; }
float UnitPrice { get; set; }
}
public interface IOrder
{
int Quantity { get; set; }
float GetTotalPrice();
}
public interface IOrder<T> : IOrder where T : IUnit
{
T Unit { get; }
}
public abstract class AbstractUnit : IUnit
{
public int Id { get; set; }
public float UnitPrice { get; set; }
}
public class Item : AbstractUnit
{
public string Name { get; set; }
}
public class Package : AbstractUnit
{
private List<Order<Item>> _items = new List<Order<Item>>();
// Items here are required by the package - number of items is specified by the quantity.
public IEnumerable<Order<Item>> Items
{
get { return this._items; }
}
}
public class Order<T> : IOrder<T> where T : IUnit
{
T _unit;
public Order(T unit)
{
_unit = unit;
}
public T Unit { get { return _unit; } }
public int Quantity { get; set; }
public float GetTotalPrice()
{
return _unit.UnitPrice * Quantity;
}
}
public class Combination
{
private List<IOrder> _orders = new List<IOrder>();
private Combination()
{
// Private constructor
}
public List<IOrder> Orders { get { return _orders; } }
/// <summary>
/// Get the total number of items ordered (combine package as well)
/// </summary>
/// <returns></returns>
public Dictionary<int, int> GetItemIdAndQuantity()
{
var output = new Dictionary<int, int>();
foreach (var order in this._orders)
{
if (order is Order<Item>)
{
var orderedItem = (Order<Item>)order;
if (!output.Keys.Contains(orderedItem.Unit.Id))
{
output.Add(orderedItem.Unit.Id, 0);
}
output[orderedItem.Unit.Id] = output[orderedItem.Unit.Id] + ((Order<Item>)order).Quantity;
}
else
{
var orderedPackage = (Order<Package>)order;
foreach (var item in orderedPackage.Unit.Items)
{
var itemId = item.Unit.Id;
if (!output.Keys.Contains(itemId))
{
output.Add(itemId, 0);
}
output[itemId] = output[itemId] + item.Quantity * orderedPackage.Quantity;
}
}
}
return output;
}
public float GetTotalPrice()
{
if (_orders.Any())
{
return _orders.Select(m => m.GetTotalPrice()).Sum();
}
return 0;
}
public static Combination GetBestCombination(IEnumerable<Order<Item>> intendedItems, IEnumerable<Package> allPacksSetupFromDB)
{
var potentialCombinations = new List<Combination>();
// check each possible pack (loaded from db) and find all possible combinations
// Step 1: for each package, if it could be used potentially, collect it into a list
var possibleRanges = new List<PackageUseRange>();
foreach (var p in allPacksSetupFromDB)
{
// for each required item in a package, intended items have to fulfill at least one occurrence (with given quantity)
if (p.Items.All(n => (intendedItems.Any(t => t.Unit.Id == n.Unit.Id && t.Quantity >= n.Quantity))))
{
var rng = new PackageUseRange(p);
rng.Min = 0;
// Find the possible max occurrence of the package
var matchedOrders = intendedItems.Where(x => p.Items.Any(m => m.Unit.Id == x.Unit.Id));
rng.Max = matchedOrders.Select(m => m.Quantity / p.Items.First(t => t.Unit.Id == m.Unit.Id).Quantity).Min();
possibleRanges.Add(rng);
}
}
// By now we should have something like:
// package 1: min 0, max 2
// package 2: min 0, max 1
// package 3: min 0, max 3
// ...
// Step 2: find all possible combinations:
if (possibleRanges.Any())
{
// define a collection to collect combinations
// also define a method to clear unwanted combinations.
var combinations = new List<Combination>();
Action invalidOrderRemover = delegate()
{
for (int j = combinations.Count - 1; j >= 0; j++)
{
var theCom = combinations[j];
var orderedQuantities = theCom.GetItemIdAndQuantity();
foreach (var itemId in orderedQuantities.Keys)
{
var intended = intendedItems.First(m => m.Unit.Id == itemId).Quantity;
if (orderedQuantities[itemId] > intended)
{
combinations.Remove(theCom);
break;
}
}
}
};
// For first package, let's create orders with different quantities
var firstPack = possibleRanges[0];
for (int i = firstPack.Min; i <= firstPack.Max; i++)
{
var order = new Order<Package>(firstPack.Package);
order.Quantity = i;
var com = new Combination();
com.Orders.Add(order);
combinations.Add(com);
}
// From second onwards:
// 1. we expand the orders created earlier and collect current pack (with different quantity)
// 2. we also take out those impossible combinations so far (total quantity exceeds wanted)
for (int i = 1; i < possibleRanges.Count - 1; i++)
{
invalidOrderRemover.Invoke(); // to avoid the list/array unreasonably large
// expand orders based on current pack's range:
var currPack = possibleRanges[i];
var expanded = new List<Combination>();
foreach (var oldCom in combinations)
{
for (int j = currPack.Min; j <= currPack.Max; j++)
{
// Clone from previous and pick up new ones from existing package.
var newCom = new Combination();
newCom.Orders.AddRange(oldCom.Orders);
var newOrder = new Order<Package>(currPack.Package);
newOrder.Quantity = j;
newCom.Orders.Add(newOrder);
}
}
// Clear old and add back the expanded:
combinations.Clear();
combinations.AddRange(expanded);
}
// Clear unwanted again:
invalidOrderRemover.Invoke();
// Add back balanced items:
foreach (var cb in combinations)
{
var fulfilled = cb.GetItemIdAndQuantity();
foreach (var item in intendedItems)
{
if (!fulfilled.Keys.Contains(item.Unit.Id))
{
// no such item in any package
// thus just add new Item based order
var newOrder = new Order<Item>(item.Unit);
newOrder.Quantity = item.Quantity;
cb.Orders.Add(newOrder);
}
else
{
// check balance:
if (fulfilled[item.Unit.Id] < item.Quantity)
{
var newOrder = new Order<Item>(item.Unit);
newOrder.Quantity = item.Quantity - fulfilled[item.Unit.Id];
cb.Orders.Add(newOrder);
}
}
}
}
// Add to the final combination collection
potentialCombinations.AddRange(combinations);
}
// Step 3: If there is no package used at all, treat each item as a standalone IOrder:
if (!potentialCombinations.Any())
{
var com = new Combination();
foreach (var ele in intendedItems)
{
com.Orders.Add(ele);
}
potentialCombinations.Add(com);
}
// The one with lowest price is desired!
return potentialCombinations.OrderBy(m => m.GetTotalPrice()).FirstOrDefault();
}
private class PackageUseRange
{
public PackageUseRange(Package p)
{
this.Package = p;
this.Min = 0;
this.Max = 0;
}
public Package Package { get; private set; }
public int Min { get; set; }
public int Max { get; set; }
}
}
}
这个想法非常简单:找到所有可能的组合,然后得到价格最低的组合。
要实现这一点,您必须找到所有适用的包,然后找到min(字面上为0)和最大可能。
从每包的最小值和最大值,找到所有组合,然后取出错误的组合(超过购买数量)。
希望它有所帮助,虽然我不确定性能 - 对于非复杂的包应该很快,但对于复杂的情况可能会很慢。你可以为此写一些测试用例...
答案 1 :(得分:0)
public string[] getItems(string [] Items)
{
List<string> lstReturn = new List<string>();
using(DBContext context = new DBContext())
{
lstReturn = (from c in context.ItemPackageItems
where Items.Contains(c.Item)
select c.ItemPackage).Distinct().ToList();
var PackagesItems = (from c in context.ItemPackageItems
where lstReturn.Contains(c.ItemPackage)
select c.Item).Distinct();
foreach (string strItem in Items)
{
if (!PackagesItems.Contains(strItem))
lstReturn.Add(strItem);
}
}
return lstReturn.ToArray();
}
这里你的结果顺序我不同,首先是包和单个项目
答案 2 :(得分:-1)
在sql中只是因为我想练习 顺便说一句它适用于3但是如果你有超过3个项目它会更复杂
declare @ck1 varchar (20),
@ck2 varchar (20),
@ck3 varchar (20),
@out varchar (100),
@flag bit,
@Itemck int
--if 1 then only @ck1 use, if 2 it used @ck1+@ck2 if 3 @ck1+@ck3 if 4 then @ck1+@ck2+@ck3
--usage manu
--1 @ck1
--2 @ck1+@ck2
--3 @ck1+@ck3
--4 @ck1+@ck2+@ck3
--5 @ck2+@ck3
set @ck1='Item1'
set @ck2='Item2'
set @ck3='Item2'
if @ck1='Item1'
BEGIN
if @ck2 = 'Item2' and @ck3='Item2'
begin
set @out='ItemPackge 3'
set @flag=1
set @Itemck=4
end
else
begin
if @ck2 = 'Item2'
begin
set @out='ItemPackge 1'
set @flag=1
set @Itemck=2
end
else if @ck3='Item2'
begin
set @out='ItemPackge 1'
set @flag=1
set @Itemck=3
end
else
begin
set @out='Item1'
set @flag=1
set @Itemck=1
end
end
END
else
if @ck1='Item2'
BEGIN
set @out='Item2'
set @flag=1
END
if @ck1='Item3'
BEGIN
if @ck2 = 'Item4' or @ck3='Item4'
if @flag=0
set @out='ItemPackge 2'
else
set @out=@out +' + ItemPackge 2'
else
if @flag=0
set @out='Item3'
else set @out=@out +' + Item 3'
set @flag=1
END
else
if @ck1='Item4'
BEGIN
if @flag=0 set @out='Item4'
else set @out=@out +' + Item 4'
set @flag=1
END
if @Itemck=0 or @Itemck=1 or @Itemck=3
BEGIN
if @ck2 ='Item1'
BEGIN
if @ck3='Item2'
BEGIN
if @flag=0
set @out='ItemPackge 2'
else
set @out=@out +' + ItemPackge 2'
set @Itemck=5
END
Else
BEGIN
if @flag=0
set @out='Item1'
else
set @out=@out +' + Item1'
END
end
else if @ck2='Item2'
BEGIN
if @flag=0
set @out='Item2'
else
set @out=@out +' + Item2'
END
if @ck2 ='Item3'
BEGIN
if @ck3='Item4'
BEGIN
if @flag=0
set @out='ItemPackge 2'
else
set @out=@out +' + ItemPackge 2'
set @Itemck=5
END
else
BEGIN
if @flag=0
set @out='Item3'
else
set @out=@out +' + Item3'
END
END
ELSE if @ck2='Item4'
BEGIN
if @flag=0
set @out='Item4'
else
set @out=@out +' + Item4'
END
END
if @Itemck =1 or @Itemck =2
BEGIN
if @flag=0
set @out=@ck3
else
set @out=@out +' + ' + @ck3
END
print @out