前言
最近做需求,碰到使用叠加券的情况。一笔订单,对于同一类型的优惠券可以叠加使用,但需要满足此订单匹配到的多个规则,规则有金额限制。
转换问题描述:目前有多个物品,物品需要放在多个背包中。一个物品不可分割到多个包中,背包容量大小需要大于物品重量。有的背包只允许放一个物品,即使他的容量可以容纳下多个物品。有的背包允许放多个物品,只要他的容量能容纳下。现在需要判断多个背包能否放下多个物品,并返回如何放。
算法实现
package com.ykq;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.*;
/**
* @author: ykq
* @date: 2022/9/29 10:15
* @Description:
* 需求需要对于同一coupId的可叠加面额券,能够叠加使用
* 同一coupId,对于不同的商品可能会过滤出不同的规则(多个背包),
* 需要判断同一coupId的多张面额券(不可拆分),匹配到对应的多个规则(不可合并,如果合并,意味着一张券使用时匹配了两个规则)
* 简单来说,多个背包(不可合并),放下多个物品(不可拆分)
* 注意:不允许叠加使用的规则(背包),只允许匹配一张面额券(物品)
*/
@Slf4j
public class MultiPackUtil {
/**
* @Author ykq
* @Date 2022/9/29 10:23
* @Description 校验多个背包,能否放下多个物品
*/
public static boolean checkPutDown(List<Package> parkList, List<Goods> goodsList) {
// 将背包容量从大到小排序
Collections.sort(parkList);
// 将物品重量从大到小排序
Collections.sort(goodsList);
BigDecimal parkSum = parkList.stream().map(Package::getCapacity)
.filter(Objects::nonNull).reduce(BigDecimal::add).get();
BigDecimal goodsSum = goodsList.stream().map(Goods::getWeight)
.filter(Objects::nonNull).reduce(BigDecimal::add).get();
// 先校验包的总容量,物品总重量的大小
if (parkSum.compareTo(goodsSum) < 0) {
log.error("背包总容量限制:{} < 物品总重量:{}", parkSum, goodsSum);
return false;
}
// 下面场景都是,包容量和 > 物品重量和
// 如果只有一个包
if (parkList.size() == 1) {
Package park = parkList.get(0);
// 不允许叠加使用,但有多个物品,则放不下
if (!park.isOverLayUse() && goodsList.size() > 1) {
log.error("只有一个背包:{}, 背包不允许放多个物品, 但物品有多个, 物品数量:{}", park.getParkId(), goodsList.size());
return false;
}
// 其他场景,可以放下
// 包括一个包,不允许叠加使用,有一个物品场景
// 包括一个包,允许叠加使用,有多个物品场景
// 将物品放入包中
parkList.get(0).getGoodsList().addAll(goodsList);
return true;
}
// 未使用物品栈
LinkedList<Goods> goodsDeque = new LinkedList<>(goodsList);
// 已使用物品栈
LinkedList<Goods> removeGoodsDeque = new LinkedList<>();
return checkPutDown2(parkList, goodsDeque, removeGoodsDeque);
}
/**
* @Author ykq
* @Date 2022/9/30 10:18
* @Description 回溯法,递归判断是否能放下
*/
private static boolean checkPutDown2(List<Package> parkList, LinkedList<Goods> goodsDeque,
LinkedList<Goods> removeGoodsDeque) {
// 如果有多个包,使用回溯法,一个个放
for (int parkDos = 0; parkDos < parkList.size(); parkDos++) {
Package park = parkList.get(parkDos);
while(!goodsDeque.isEmpty()) {
// 如果包总容量-已使用容量>物品重量
// 获取栈顶元素
Goods goods = goodsDeque.peekFirst();
// 判断
// 1.包不可放多个物品,没放物品时,包容量大于物品重量
// 2.包可放物品,剩余包容量大于物品重量
// 上述两种场景,可将物品放入包中
if ((!park.isOverLayUse() && park.getGoodsList().isEmpty() && park.getCapacity().compareTo(goods.getWeight()) >= 0)
|| (park.isOverLayUse() && park.getCapacity().subtract(park.getUsedWeight()).compareTo(goods.getWeight()) >= 0)){
// 将物品放入到包中
park.getGoodsList().add(goods);
// 已用容量增加
park.setUsedWeight(park.getUsedWeight().add(goods.getWeight()));
// 移除已使用的物品
goodsDeque.removeFirst();
// 将移除的物品放入被使用栈
removeGoodsDeque.addFirst(goods);
// 将后面的物品从头开始放
if (checkPutDown2(parkList, goodsDeque, removeGoodsDeque)) {
return true;
} else {
// 将被移除的元素,回滚到物品栈中
Goods removeGoods = removeGoodsDeque.removeFirst();
park.getGoodsList().remove(removeGoods);
goodsDeque.addFirst(removeGoods);
break;
}
} else {
// 放不下就去下一个包
break;
}
}
// 物品放完了,返回true
if (goodsDeque.isEmpty()) {
return true;
}
}
return false;
}
/**
* @Author ykq
* @Date 2022/10/14 9:50
* @Description 背包类
*/
@Data
public class Package implements Comparable<Package> {
// 背包id
private Long parkId;
// 背包容量
private BigDecimal capacity;
// 是否允许放多个物品
private boolean overLayUse;
// 已放物品重量
private BigDecimal usedWeight = BigDecimal.ZERO;
// 放了哪些物品
private List<Goods> goodsList = new ArrayList<>();
/**
* @Author ykq
* @Date 2022/10/14 9:59
* @Description 按背包不允许->允许排序,再按背包容量从大到小排序
*/
@Override
public int compareTo(Package other) {
int thisOverLayUseInt = this.overLayUse ? 1 : 0;
int otherOverLayUseInt = other.overLayUse ? 1 : 0;
int order = thisOverLayUseInt - otherOverLayUseInt;
if (order != 0) {
return order;
}
return other.capacity.compareTo(this.capacity);
}
}
/**
* @Author ykq
* @Date 2022/10/14 9:51
* @Description 物品类
*/
@Data
public class Goods implements Comparable<Goods> {
// 物品id
private Long goodsId;
// 物品重量
private BigDecimal weight;
/**
* @Author ykq
* @Date 2022/10/14 9:58
* @Description 按物品重量从大到小排序
*/
@Override
public int compareTo(Goods other) {
return other.weight.compareTo(this.weight);
}
}
}