【工作中问题解决实践 一】最小单元染色法的应用

简介: 【工作中问题解决实践 一】最小单元染色法的应用

最近在处理费率和保底费的优惠及标准区间。问题本质就是:标准合作区间是一个【时间段+标准值】,优惠合作区间是多段【时间段+标准值】,并且各个时间段的开始和结束日期可以随意指定,优惠区间和标准区间重合部分按照优惠值计算,非重合部分按照标准区间值计算

问题场景

上游对时间段的限制基本没有,唯一的限制就是多段优惠区间是连续的。

优惠期结束晚于合作期

优惠期开始早于合作期

优惠期大于合作期

优惠期和合作期完全不沾边

由于各种可能行都存在,用if else条件判断起来太麻烦,可能性太多了

解决方法

当问题复杂的时候不妨把问题抽象看待:由于费率和保底费的计算都是以天为最小单位的,所以我们可以把每段时间都拆成一天一天的,放到一个集合里,然后以标准的时间值Map为基准,匹配优惠的,匹配到就修改该最小单元对应的值为优惠值,匹配不到则继续匹配,直到匹配结束。匹配完成后我们再依据值的连续性,将连续相同值的最小单元输出为时间段和值

1 最小单元拆分

第一步:分别将连续的优惠区间和标准区间进行最小单元化处理:

/**
     * 将时间段和值拆分为天和值
     *
     * @param beginDate the begin date
     * @param endDate   the end date
     * @param fee       the fee
     * @return the tree map
     */
    public TreeMap<LocalDate, BigDecimal> Build(LocalDate beginDate, LocalDate endDate, BigDecimal fee) {
        TreeMap<LocalDate, BigDecimal> feeMap = new TreeMap<>();
        for (LocalDate date = beginDate; date.isBefore(endDate) || date.isEqual(endDate); date = date.plusDays(1)) {
            date = DateUtil.stringToLocalDate(date.format(fmt), DateUtil.DATE_FORMAT);
            feeMap.put(date, fee);
        }
        return feeMap;
    }

2 最小单元染色

第二步:对最小单元区间进行Merge处理:基于标准区间,对重合部分的优惠区间重新赋值

/**
     * 以标准值时间段为基准,相同时间天的按优惠值
     *
     * @param standard the standard
     * @param discount the discount
     */
    public void Merge(TreeMap<LocalDate, BigDecimal> standard, TreeMap<LocalDate, BigDecimal> discount) {
        Set<LocalDate> days = standard.keySet();
        for (LocalDate day : days) {
            if (discount.containsKey(day)) {
                standard.put(day, discount.get(day));
            }
        }
    }

3 最小单元区间合并

第三步:将处理好的最小单元和值Map进行连续值时间区间合并

/**
     * 将最小单元再转为时间段和值
     *
     * @param full the full
     * @return the list
     */
    public List<DateAndValueInfo> Output(TreeMap<LocalDate, BigDecimal> full) {
        List<DateAndValueInfo> dateAndValueInfos = new ArrayList<>();
        Set<LocalDate> days = full.keySet();
        BigDecimal fee = new BigDecimal("0");
        LocalDate start = null;
        LocalDate end = null;
        int count = 0;
        for (LocalDate day : days) {
            count++;
            if (fee.toPlainString().equals(ZERO) && start == null) {
                fee = full.get(day);
                start = day;
                end = day;
                continue;
            }
            if (!fee.equals(full.get(day))) {
                // 相同值连续阶段结束时,将本段连续值添加到时间段,最后一阶段也添加
                Date beforeStartDate = DateUtil.localDateToDate(start);
                Date beforeEndDate = DateUtil.localDateToDate(end);
                DateAndValueInfo dateAndValueInfo = new DateAndValueInfo(beforeStartDate, beforeEndDate, fee);
                dateAndValueInfos.add(dateAndValueInfo);
                start = day;
                fee = full.get(day);
            }
            end = day;
            if (count == days.size()) {
                // 最后一阶段也添加
                Date beforeStartDate = DateUtil.localDateToDate(start);
                Date beforeEndDate = DateUtil.localDateToDate(end);
                DateAndValueInfo dateAndValueInfo = new DateAndValueInfo(beforeStartDate, beforeEndDate, fee);
                dateAndValueInfos.add(dateAndValueInfo);
            }
        }
        return dateAndValueInfos;
    }

整体的代码调用如下:

/**
     * 合并处理时间段和值
     *
     * @param disCountDateAndValueInfo the dis count date and value info
     * @param standardDateAndValueInfo the standard date and value info
     * @return the list
     */
    public List<DateAndValueInfo> handleDateAndValues(List<DateAndValueInfo> disCountDateAndValueInfo, DateAndValueInfo standardDateAndValueInfo) {
        TreeMap<LocalDate, BigDecimal> disCounts = new TreeMap<>();
        for (DateAndValueInfo dateAndValueInfo : disCountDateAndValueInfo) {
            TreeMap<LocalDate, BigDecimal> disCount = Build(DateUtil.dateToLocalDate(dateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(dateAndValueInfo.getToDate()), dateAndValueInfo.getValue());
            disCounts.putAll(disCount);
        }
        TreeMap<LocalDate, BigDecimal> standard = Build(DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate()), standardDateAndValueInfo.getValue());
        Merge(standard, disCounts);
        return Output(standard);
    }

用最小单元染色法解决甚至可以处理这样毫无规律的时间段merge

拓展处理

还可以解决这类问题:当不需要处理标准段而只需要用标准区间卡一个时间范围的话:

/**
     * 获取优惠段和合作区间merge的时间
     *
     * @param disCountDateAndValueInfo the dis count date and value info
     * @param standardDateAndValueInfo the standard date and value info
     * @return the list
     */
    private List<DateAndValueInfo> handleValidateInfo(List<DateAndValueInfo> disCountDateAndValueInfo, DateAndValueInfo standardDateAndValueInfo) {
        // 所有优惠期统一划分为细粒度值-时间
        TreeMap<LocalDate, BigDecimal> disCountInfos = new TreeMap<>();
        for (DateAndValueInfo dateAndValueInfo : disCountDateAndValueInfo) {
            TreeMap<LocalDate, BigDecimal> disCount = Build(DateUtil.dateToLocalDate(dateAndValueInfo.getFromDate()), DateUtil.dateToLocalDate(dateAndValueInfo.getToDate()), dateAndValueInfo.getValue());
            disCountInfos.putAll(disCount);
        }
        LocalDate realStartDate = disCountInfos.firstKey().isBefore(DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate())) ? DateUtil.dateToLocalDate(standardDateAndValueInfo.getFromDate()) : disCountInfos.firstKey();
        LocalDate realEndDate = disCountInfos.lastKey().isBefore(DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate())) ? DateUtil.dateToLocalDate(standardDateAndValueInfo.getToDate()) : disCountInfos.lastKey();
        // 不在卡死时间段内的值干掉
        Set<LocalDate> days = disCountInfos.keySet();
        for (LocalDate day : days) {
            if (day.isBefore(realStartDate) || day.isAfter(realEndDate)) {
                disCountInfos.remove(day);
            }
        }
        return Output(disCountInfos);
    }

总结一下

大道至简,当问题复杂时,一定是因为思维陷入到了复杂的逻辑里,要学会思考如何抽象看待问题,透过现象看本质。

相关文章
|
6月前
|
机器学习/深度学习 并行计算 算法
龚大视频学习笔记:上帝视角看GPU(2)-逻辑上的模块划分
龚大视频学习笔记:上帝视角看GPU(2)-逻辑上的模块划分
110 0
|
4月前
|
JavaScript 前端开发 测试技术
探索自动化测试的边界:从单元到端到端
在软件工程领域,自动化测试已成为确保产品质量和加速交付速度的关键实践。本文将深入探讨自动化测试的多个层面,从单元测试到端到端测试,揭示它们在现代软件开发生命周期中的作用与挑战。文章通过分析实际案例、统计数据和最新研究成果,旨在提供一个全面的视角,帮助读者理解自动化测试的最佳实践和未来趋势。
44 6
|
5月前
|
算法 调度
基于变异混合蛙跳算法的车间调度最优化matlab仿真,可以任意调整工件数和机器数,输出甘特图
**摘要:** 实现变异混合蛙跳算法的MATLAB2022a版车间调度优化程序,支持动态调整工件和机器数,输出甘特图。核心算法结合SFLA与变异策略,解决Job-Shop Scheduling Problem,最小化总完成时间。SFLA模拟蛙群行为,分组进行局部搜索和全局信息交换。变异策略增强全局探索,避免局部最优。程序初始化随机解,按规则更新,经多次迭代和信息交换后终止。
|
5月前
|
算法 调度 决策智能
基于自适应遗传算法的车间调度matlab仿真,可以任意调整工件数和机器数,输出甘特图
这是一个使用MATLAB2022a实现的自适应遗传算法解决车间调度问题的程序,能调整工件数和机器数,输出甘特图和适应度收敛曲线。程序通过编码初始化、适应度函数、遗传操作(选择、交叉、变异)及自适应机制进行优化,目标如最小化完工时间。算法在迭代过程中动态调整参数,以提升搜索效率和全局优化。
|
5月前
|
算法 调度
基于PPNSA+扰动算子的车间调度最优化matlab仿真,可以任意调整工件数和机器数,输出甘特图
`MATLAB2022a`仿真实现PPNSA+扰动算子的车间调度优化,支持工件和机器数量调整,输出甘特图与收敛曲线。算法针对JSSP,采用启发式策略应对NP难问题,最小化最大完工时间。[图:算法流程示意图]
|
存储 算法 C语言
程序组织单元及其组成编程
程序组织单元及其组成编程
|
存储 消息中间件 监控
大型系统如何划分边界?
大型系统如何划分边界?
320 0
大型系统如何划分边界?
HIMA 984862702 计算在期望位置分配极点的反馈矩阵
HIMA 984862702 计算在期望位置分配极点的反馈矩阵
HIMA 984862702 计算在期望位置分配极点的反馈矩阵
|
机器学习/深度学习 人工智能 算法
【算法 | 实验8】分配最小页数(数组划分和最大值最小化问题)
【算法 | 实验8】分配最小页数(数组划分和最大值最小化问题)
277 0
【算法 | 实验8】分配最小页数(数组划分和最大值最小化问题)