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

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

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

问题场景

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

优惠期结束晚于合作期

优惠期开始早于合作期

优惠期大于合作期

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

由于各种可能行都存在,用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);
    }

总结一下

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

相关文章
|
5月前
|
JavaScript 前端开发 测试技术
探索自动化测试的边界:从单元到端到端
在软件工程领域,自动化测试已成为确保产品质量和加速交付速度的关键实践。本文将深入探讨自动化测试的多个层面,从单元测试到端到端测试,揭示它们在现代软件开发生命周期中的作用与挑战。文章通过分析实际案例、统计数据和最新研究成果,旨在提供一个全面的视角,帮助读者理解自动化测试的最佳实践和未来趋势。
46 6
|
7月前
|
算法 测试技术 持续交付
软件开发深度解析:从设计到单元构建
软件开发深度解析:从设计到单元构建
179 2
|
7月前
[贴装专题] 贴装流程中涉及到的位置关系计算
[贴装专题] 贴装流程中涉及到的位置关系计算
61 0
|
存储 算法 C语言
程序组织单元及其组成编程
程序组织单元及其组成编程
动态优化解决方案空间中的最小支持(Matlab代码实现)
动态优化解决方案空间中的最小支持(Matlab代码实现)
|
算法 芯片
METSO DPU-MR 映射工具寻址的最小功能单元
METSO DPU-MR 映射工具寻址的最小功能单元
152 0
METSO  DPU-MR 映射工具寻址的最小功能单元
中国科大实现两类不同量子资源间的相互循环转化
中国科学家提出量子相干性与量子关联之间的循环转化方法,并在光子系统中实验验证了该方案,相关研究成果在线发表在《物理评论快报》上。
1349 0