最近有个小需求,客户需要对一些货物进行组合销售,需要我们新增一个配置表,用于组合优惠的配置以及优惠查询。
例如,顾客购买了 A
,B
,C
三种套餐,我们配置了 A
,B
,A-B
,A-C
,A-D
,A-B-C
,A-B-D
这7种优惠,那么需要反馈给顾客可以使用的优惠组合为:A
,B
,A-B
,A-C
,A-B-C
这5种。
方案设计的难点在于:货物众多,组合也很多,组合顺序无特定顺序,又需要根据客户选择货物进行快速决策。
简化问题,其实就是在配置组合中找出 所选实物的无序组合。
目前能想到最快实现查询的方案是利用质数的特质来实现快速筛选。
如果我们把每个货物看成一个质数,而优惠组合则认为是组合内质数的乘积。那么很明显,判断是否符合的标准就是——所选货物的质数积 % 组合质数积 是否等于0。
首先为每一个货物分配一个质数。优惠组合的值为各货物的乘积。
例如:
货物为:A
=2,B
=3,C
=5,D
=7,E
=11
优惠组合为:A-B
= 6, A-C
= 10,A-D
= 14,A-B-C
= 30, A-B-D
= 42, A-B-C-E
= 330
如果客户购买的货物为 A
,B
,C
, 那么她可以使用的优惠组合 根据如下思路计算
A
* B
* C
= 2 * 3 * 5 = 30组合 | 组合值 | 是否符合 |
---|---|---|
A-B | 2*3=6 | 30%6=, 符合要求 |
A-C | 2*5=10 | 30%10=0, 符合要求 |
A-D | 2*7=14 | 30%14=16, 不符合要求 |
A-B-C | 235=30 | 30%30=0, 符合要求 |
A-B-D | 237=42 | 30%42=30, 不符合要求 |
A-B-C-E | 2311=330 | 30%330=30, 不符合要求 |
3.通过上述筛选,可以得到可以支持的组合优惠为A-B
,A-C
,A-B-C
这三种
4.对如上逻辑我们可以做进一步的优化,因为我们知道:
如果 0
所以可以先过滤掉组合值大于购买货物质数积的优惠组合,然后再进行取余判断。
public class OfferDiscountVerification {
/**
* 货物对应的参数
*/
private static Map<String, Long> offerPrimeNumMap;
/**
* 优惠组合
*/
private static Map<String, Long> offerDiscountGroupMap;
public static void main(String[] args) {
// 初始化
initOfferPrimeNumMap();
initOfferDiscountGroupMap();
// 输入测试
Scanner sc = new Scanner(System.in);
String selectGoods = sc.nextLine();
while (!"EOF".equals(selectGoods)) {
String[] goodsNames = selectGoods.split(",");
List<String> applicableGroupList = dealApplicableGroup(goodsNames);
printApplicableGroup(applicableGroupList);
selectGoods = sc.nextLine();
}
}
/**
* 根据selectGoods 和 offerDiscountGroupMap 计算可用折扣组合
* @param selectGoods
* @return
*/
private static List<String> dealApplicableGroup(String... selectGoods) {
// 1.计算所选货物质数积
Long selectGoodsVal = calGroupVal(selectGoods);
System.out.println("selectGoodsVal:" + selectGoodsVal);
// 2.计算组合
return offerDiscountGroupMap
.entrySet()
.stream()
.filter(obj -> selectGoodsVal >= obj.getValue())
.filter(obj -> selectGoodsVal%obj.getValue() == 0)
.map(obj -> obj.getKey())
.collect(Collectors.toList());
}
/**
* initOfferPrimeNumMap
*/
private static void initOfferPrimeNumMap() {
offerPrimeNumMap = new HashMap<>();
offerPrimeNumMap.put("A", 2L);
offerPrimeNumMap.put("B", 3L);
offerPrimeNumMap.put("C", 5L);
offerPrimeNumMap.put("D", 7L);
offerPrimeNumMap.put("E", 11L);
offerPrimeNumMap.put("F", 13L);
offerPrimeNumMap.put("G", 17L);
}
/**
* initOfferDiscountGroupMap
*/
private static void initOfferDiscountGroupMap() {
offerDiscountGroupMap = new HashMap<>();
offerDiscountGroupMap.put("A-B", calGroupVal("A", "B"));
offerDiscountGroupMap.put("A-C", calGroupVal("A", "C"));
offerDiscountGroupMap.put("A-D", calGroupVal("A", "D"));
offerDiscountGroupMap.put("A-F", calGroupVal("A", "F"));
offerDiscountGroupMap.put("A-G", calGroupVal("A", "G"));
offerDiscountGroupMap.put("A-B-C", calGroupVal("A", "B", "C"));
offerDiscountGroupMap.put("A-B-D", calGroupVal("A", "B", "D"));
offerDiscountGroupMap.put("A-B-E", calGroupVal("A", "B", "E"));
offerDiscountGroupMap.put("A-B-F", calGroupVal("A", "B", "F"));
offerDiscountGroupMap.put("A-B-C-D", calGroupVal("A", "B", "C", "D"));
offerDiscountGroupMap.put("B-C-D-F", calGroupVal("B", "C", "D", "F"));
offerDiscountGroupMap.put("D-E-F-G", calGroupVal("D", "E", "F", "G"));
}
/**
* 计算乘积
* @param goodsName
* @return
*/
private static Long calGroupVal(String... goodsName) {
if (goodsName.length == 0) {
return 0L;
}
Long val = 1L;
for (int i = 0; i < goodsName.length; i++) {
val *= offerPrimeNumMap.get(goodsName[i]);
}
return val;
}
/**
* printApplicableGroup
* @param applicableGroupList
*/
private static void printApplicableGroup(List<String> applicableGroupList) {
System.out.println("Applicable Group is:");
for (String goosGroup : applicableGroupList) {
System.out.println(goosGroup);
}
}
}
经过简单的验证,上述逻辑是ok的
1.众所周知10,000以内的质数只有1229个,面对无限的商品以及不限制的组合种类,如果为每一个实物默认分配一个质数,那么可能最终的组合值会过大。而实际上并不是所有的实物都会参与优惠组合。那么我们可以在配置组合时再去为所用的实物进行质数分配。
2.质数分配以及组合值计算正确是正确过滤优惠组合的必要保证,因此带来的对配置表组合值验证以及维护成本都需要考虑。
3.组合值只能用于判断,不能方便的反向推出所代表的组合值
上述利用素数特性的方案是目前能想到的能够较快速筛选组合的办法。不知道各位大佬有没有更好的办法,指点一二?