流量阶梯 用量按照日、月、年、自然月、自然年,周期叠加分段计算各个阶梯金额

devtools/2025/1/16 0:04:44/

1、前言

1.1 商品有三个属性

周期时长、周期单位、叠加次数

商品周期时长周期单位叠加次数
A42
B16

1.2 商品配置阶梯

用量大于0GB, 流量总价30元
用量大于90GB, 流量单价0.19元/GB
用量大于100GB, 流量单价0.12元/GB

2、根据不同属性获取阶梯下金额

2.1 B商品结果

B商品
1月2月3月4月5月6月7月8月9月
用量10KB50KB90KB91KB101KB102KB
>030元30元30元30元30元30元
>900元0元0元0.19元1.9元1.9元
>1000元0元0元0元0.12元0.24元

2.1 A商品结果

A商品
1月2月3月4月5月6月7月8月9月
用量10KB50KB31KB10KB10KB50KB31KB10KB
>030元0元0元0元30元0元0元0元
>900元0元0.19元1.71元0元0元0.19元1.71元
>1000元0元0元0.12元0元0元0元0.12元

3、算法实现

@Slf4j
public class FlowSegmentGoodsClearingAlgorithm {private static String flowSegmentRuleStr = "{\"flowLadderList\":" + "[" +"{\"beginLadder\":0,\"calculationType\":1,\"unit\":\"KB\",\"unitPrice\":30.0000000000}," +"{\"beginLadder\":90,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1900000000}," +"{\"beginLadder\":100,\"calculationType\":0,\"unit\":\"KB\",\"unitPrice\":0.1200000000}" +"]}";public static void main(String[] args) {Map<String, BigDecimal> quantityMap = new HashMap<>();
//        quantityMap.put("2023-01", new BigDecimal("10"));
//        quantityMap.put("2023-02", new BigDecimal("50"));
//        quantityMap.put("2023-03", new BigDecimal("90"));
//        quantityMap.put("2023-04", new BigDecimal("91"));
//        quantityMap.put("2023-05", new BigDecimal("101"));
//        quantityMap.put("2023-06", new BigDecimal("102"));quantityMap.put("2023-01", new BigDecimal("10"));quantityMap.put("2023-02", new BigDecimal("50"));quantityMap.put("2023-03", new BigDecimal("31"));quantityMap.put("2023-04", new BigDecimal("10"));
//        quantityMap.put("2023-05", new BigDecimal("101"));
//        quantityMap.put("2023-06", new BigDecimal("102"));FlowSegmentRule flowSegmentRule = JSON.parseObject(flowSegmentRuleStr, FlowSegmentRule.class);List<FlowLadder> flowLadderList = flowSegmentRule.getFlowLadderList();// 将分段阶梯转换成算法阶梯List<FlowSegmentGoodsClearingAlgorithm.QuantityLadder> ladderList = new ArrayList<>();for (int i = 0; i < flowLadderList.size(); i++) {FlowLadder flowLadder = flowLadderList.get(i);if (i < flowLadderList.size() - 1) {ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), flowLadderList.get(i + 1).getBeginLadder(), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));} else {ladderList.add(new FlowSegmentGoodsClearingAlgorithm.QuantityLadder(flowLadder.getBeginLadder(), new BigDecimal(Long.MAX_VALUE), flowLadder.getUnitPrice(), flowLadder.getCalculationType(), flowLadder.getUnit()));}}List<QuantityLadderAmountResult> calc = calc(quantityMap, ladderList);calc.forEach(System.out::println);}/*** 账期内数量(流量)分段计算** @param quantityMap        key 账期 value 账期使用流量* @param quantityLadderList 分段配置* @return*/public static List<QuantityLadderAmountResult> calc(Map<String, BigDecimal> quantityMap, List<QuantityLadder> quantityLadderList) {Assert.notEmpty(quantityMap, "quantityMap cannot empty");Assert.notEmpty(quantityLadderList, "quantityLadderList cannot empty");List<String> billDateSortList = quantityMap.keySet().stream().sorted(String::compareTo).collect(Collectors.toList());BigDecimal totalFlowMB = BigDecimal.ZERO;List<QuantityLadderAmountResult> resultList = new ArrayList<>();String unit = quantityLadderList.get(0).getUnit();int index = 0;for (String billDate : billDateSortList) {if (null == quantityMap.get(billDate) || BigDecimal.ZERO.equals(quantityMap.get(billDate))) {log.debug("calc 账期 {} 内流量为O,直接返回0元账单", billDate);quantityLadderList.forEach(i -> resultList.add(assembleZeroBill(billDate, i)));continue;}totalFlowMB = totalFlowMB.add(quantityMap.get(billDate));Map<Integer, QuantityLadderAmountResult> resultMap = new HashMap<>();BigDecimal converterUsage;if (FlowUnitEnum.GB.name().equals(unit)) {converterUsage = FlowUnitConverter.toGB(FlowUnitEnum.KB.name(), totalFlowMB);} else if (FlowUnitEnum.MB.name().equals(unit)) {converterUsage = FlowUnitConverter.toMB(FlowUnitEnum.KB.name(), totalFlowMB);} else {converterUsage = totalFlowMB;}// 补已达标废弃的阶梯if (index > 0) {for (int i = 0; i < index; i++) {if (!resultMap.containsKey(i)) {resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));}}}for (int i = index; i < quantityLadderList.size(); i++) {QuantityLadder e = quantityLadderList.get(i);BigDecimal matchFlowMB = e.match(converterUsage, quantityMap.get(billDate));if (null == matchFlowMB) {index = i;break;}if (i == quantityLadderList.size() - 1) {index = i;}log.info("calc 账期 {} 总流量 {} 当期流量 {} 中有 {} 命中  {} - ({} , {}]", billDate, converterUsage, quantityMap.get(billDate), matchFlowMB, i, e.getBeginQuantity(), e.getEndQuantity());QuantityLadderAmountResult result = new QuantityLadderAmountResult();result.setBillDate(billDate);result.setQuantity(quantityMap.get(billDate));result.setMatchQuantity(matchFlowMB);result.setAmount(e.calcAmount(matchFlowMB));result.setPriceType(e.getPriceType());result.setPrice(e.getPrice());result.setTotalQuantity(converterUsage);result.setLadderDesc(e.getLadderDesc());result.setLadderIndex(i);result.setUnit(e.getUnit());resultMap.put(i, result);if (converterUsage.compareTo(e.getEndQuantity()) < 0) {index = i;break;}}// 补其他阶梯for (int i = index; i < quantityLadderList.size(); i++) {if (!resultMap.containsKey(i)) {resultMap.put(i, assembleZeroBill(billDate, quantityLadderList.get(i)));}}resultList.addAll(resultMap.values());}return resultList;}private static QuantityLadderAmountResult assembleZeroBill(String billDate, QuantityLadder ladder) {QuantityLadderAmountResult result = new QuantityLadderAmountResult();result.setBillDate(billDate);result.setQuantity(BigDecimal.ZERO);result.setMatchQuantity(BigDecimal.ZERO);result.setAmount(BigDecimal.ZERO);result.setPrice(ladder.getPrice());result.setPriceType(ladder.priceType);result.setUnit(ladder.getUnit());result.setLadderDesc(ladder.getLadderDesc());return result;}@Datapublic static class QuantityLadder {private static final Integer TOTAL_PRICE_FLAG = 1;private boolean isTotalPriceLock = false;/*** 分段开始数量*/private BigDecimal beginQuantity;/*** 分段结束数量*/private BigDecimal endQuantity;/*** 分段价格*/private BigDecimal price;/*** 单位*/private String unit;/*** 价格类型: 0-单价(默认),1-总价*/private Integer priceType;public QuantityLadder() {}public QuantityLadder(BigDecimal beginQuantity, BigDecimal endQuantity, BigDecimal price, Integer priceType, String unit) {this.beginQuantity = beginQuantity;this.endQuantity = endQuantity;this.price = price;this.priceType = priceType;this.unit = unit;}/*** 分段阶梯匹配** @param totalQuantity 账期在整改计算周期内总数量* @param quantity      账期的数量* @return 账期内匹配该阶梯的数量 null 则标识未匹配该阶梯*/public BigDecimal match(BigDecimal totalQuantity, BigDecimal quantity) {boolean isMatch = beginQuantity.compareTo(totalQuantity) < 0;if (!isMatch) {return null;}if (endQuantity.compareTo(totalQuantity) < 0) {return min(endQuantity.subtract(totalQuantity.subtract(quantity)), endQuantity.subtract(beginQuantity));} else {return min(totalQuantity.subtract(beginQuantity), quantity);}}/*** 阶梯内金额计算* 1 当阶梯配置是总价,如果该阶梯没有被计算过,直接返回总价格; 如果被计算过直接返回0* 2 当阶梯计算是单价,** @param matchQuantity* @return*/public BigDecimal calcAmount(BigDecimal matchQuantity) {if (TOTAL_PRICE_FLAG.equals(priceType)) {BigDecimal amount = isTotalPriceLock ? BigDecimal.ZERO : price;isTotalPriceLock = true;return amount;} else {return price.multiply(matchQuantity).setScale(2, RoundingMode.HALF_UP);}}/*** 阶梯描述** @return*/public String getLadderDesc() {if (String.valueOf(Long.MAX_VALUE).equals(endQuantity.toString())) {return MessageFormat.format("({0},{1})", beginQuantity, "+∞");}return MessageFormat.format("({0},{1}]", beginQuantity.toString(), endQuantity.toString());}}private static BigDecimal min(BigDecimal a, BigDecimal b) {return (a.compareTo(b) <= 0) ? a : b;}@Datapublic static class QuantityLadderAmountResult {/*** 账期 实际是业务账期*/private String billDate;/*** 账期内数量*/private BigDecimal quantity;/*** 账期内计算数量*/private BigDecimal matchQuantity;/*** 价格类型: 0-单价(默认),1-总价*/private Integer priceType;/*** 分段价格*/private BigDecimal price;/*** 计算金额*/private BigDecimal amount;/*** 账期在周期内总数量*/private BigDecimal totalQuantity;/*** 匹配分段描述*/private String ladderDesc;/*** 命中阶梯*/private Integer ladderIndex;/*** 流量单位 KB MB GB*/private String unit;/*** 扩展信息*/private String extendInfo;}}


http://www.ppmy.cn/devtools/10857.html

相关文章

算法设计与分析实验4 :利用动态规划的方法解决子集等和分割判断问题

实验4 利用动态规划的方法解决子集等和分割判断问题 一、实验目的 1. 了解动态规划的主要思想。 2. 掌握背包问题解决方法用以解决该问题。 3. 分析核心代码的时间复杂度和空间复杂度。 二、实验内容和要求 题目:给定一个只包含正整数的非空数组。是否可以将这个数组分…

设计模式学习笔记 - 开源实战二(中):从Unix开源开发学习应对大型复杂项目开发

概述 项目越复杂、代码量越多、参与开发人员越多、开发维护时间越长&#xff0c;我们就要越重视代码质量。代码质量下降会导致项目研发困难重重&#xff0c;比如&#xff1a;开发效率低&#xff0c;找了很多人&#xff0c;天天加班也出活不多&#xff1b;线上 bug 频发&#x…

蚁狮优化算法(ALO算法)学习

蚁狮优化算法&#xff08;Ant Lion Optimizer&#xff0c;简称ALO&#xff09;是一种模仿自然界中蚁狮捕食行为的群智能优化算法。这种算法由Seyedali Mirjalili于2015年提出&#xff0c;旨在解决各种优化问题。 在自然界中&#xff0c;蚁狮通过挖掘一个漏斗状的陷阱来捕捉蚂蚁…

「PHP系列」PHP Cookie/Session详解

文章目录 一、PHP Cookie1. Cookie的基本概念2. PHP中操作Cookie的常用函数3. Cookie案例代码设置Cookie读取Cookie删除Cookie 4. 注意事项 二、PHP Session1. PHP Session的基本概念2. PHP中操作Session的常用函数3. Session案例代码启动Session并设置数据读取Session数据销毁…

Swift常用的第三方库

以下是一些常用的Swift第三方库及其链接&#xff1a; Alamofire&#xff1a;用于网络请求的库。https://github.com/Alamofire/Alamofire Kingfisher&#xff1a;用于异步下载和缓存图片的库。https://github.com/onevcat/Kingfisher SwiftyJSON&#xff1a;用于处理JSON数据…

Zabbix监控系统:基础配置及部署代理服务器

目录 前言 一、自定义监控内容 1、在客户端创建自定义key 2、在服务端验证新建的监控项 3、在web界面创建自定义监控项模版 3.1 创建模版 3.2 创建应用集&#xff08;用于管理监控项&#xff09; 3.3 创建监控项 3.4 创建触发器 3.5 创建图形 3.6 将主机与模板关联…

【快速上手ESP32(基于ESP-IDFVSCode)】特别篇——一文速通FreeRTOS

前言 这边插一篇介绍FreeRTOS的文章&#xff0c;因为我在写后续快速上手ESP32系列的文章的时候发现FreeRTOS是越不过去的坎&#xff0c;因此这边补充一下。 FreeRTOS FreeRTOS是一款广泛使用的开源实时操作系统&#xff08;RTOS&#xff09;&#xff0c;为嵌入式开发提供了可…

2024深圳杯(东三省)数学建模挑战赛D题:音板的振动模态分析与参数识别思路代码成品论文分析

​ 更新完整代码和成品完整论文 《2024深圳杯&东三省数学建模思路代码成品论文》↓↓↓ https://www.yuque.com/u42168770/qv6z0d/zx70edxvbv7rheu7?singleDoc# 问题重述 深圳杯&#xff08;东三省&#xff09;数学建模挑战赛2024D题&#xff1a;音板的振动模态分析与…