skyouc
2025-04-28 9b8ba51387d0a8db467bafaf7ecd09d93bded873
添加简略出库策略
7个文件已修改
1个文件已添加
1个文件已删除
520 ■■■■■ 已修改文件
rsf-admin/src/i18n/en.js 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/Test/CombinationFinder.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/Test/MinimalCombinationSum.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js
@@ -70,6 +70,7 @@
            loadMore: 'Load More Data',
            complete: 'Complete',
            deprecate: 'Deprecate',
            stockError: 'Insuff stock ',
            inputPlaceholder: 'Use commas to separate',
            resend: 'RESEND',
            selected: 'selected',
@@ -746,7 +747,9 @@
                anfme: "anfme",
                workQty: "workQty",
                qty: "Qty",
                stockLocs: "Stock Locs"
                stockLocs: "Stock Locs",
                stockQty: "Stock Qty",
            },
            task: {
                taskCode: "TaskCode",
rsf-admin/src/i18n/zh.js
@@ -71,6 +71,7 @@
            loadMore: '加载更多',
            complete: '完成',
            deprecate: '废弃',
            stockError: '库存不足',
            resend: '重发',
            selected: '项选中',
            batch: '批量编辑'
@@ -792,7 +793,8 @@
                anfme: "数量",
                workQty: "执行数",
                qty: "完成数量",
                stockLocs: "库存位置"
                stockLocs: "库存位置",
                stockQty: "库存数量",
            },
            task: {
                taskCode: "任务号",
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx
@@ -37,11 +37,10 @@
    ListContextProvider,
    useList,
    Toolbar,
    SingleFieldList,
} from 'react-admin';
import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
import { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog } from '@mui/material';
import { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog, Chip, ListItem, Paper } from '@mui/material';
import { styled } from '@mui/material/styles';
import DialogCloseButton from "../../components/DialogCloseButton";
import request from '@/utils/request';
@@ -131,7 +130,6 @@
                            <NumberField source="waveId" label="table.field.waveItem.waveId" />
                            <TextField source="waveCode" label="table.field.waveItem.waveCode" />
                            <TextField source="orderCode" label="table.field.waveItem.orderCode" />
                            {/* <TextField source="maktx" label="table.field.waveItem.matnrName" /> */}
                            <NumberField source="matnrId" label="table.field.waveItem.matnrId" />
                            <TextField source="matnrCode" label="table.field.waveItem.matnrCode" />
                            <TextField source="batch" label="table.field.waveItem.batch" />
@@ -143,22 +141,16 @@
                            <NumberField source="anfme" label="table.field.waveItem.anfme" />
                            <NumberField source="workQty" label="table.field.waveItem.workQty" />
                            <NumberField source="qty" label="table.field.waveItem.qty" />
                            <NumberField source="stockQty" label="table.field.waveItem.stockQty" />
                            <WrapperField cellClassName="opt" label="table.field.waveItem.stockLocs">
                                <ArrayField source="stockLocs" stockLocs="table.field.waveItem.stockLocs">
                                    <SingleFieldList linkType={false}>
                                        <ChilpField source="" size="small"></ChilpField>
                                    </SingleFieldList>
                                </ArrayField>
                                {/* <NumberField source="anfme" label="table.field.waveItem.stockLocs" /> */}
                                {/* <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                                <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> */}
                                <TagsField />
                            </WrapperField>
                        </StyledDatagrid>
                    </ListContextProvider>
                </DialogContent>
                <DialogActions>
                    <Toolbar sx={{ width: '100%', justifyContent: 'end' }} >
                        <GenerateTaskButton record={[record?.id]} />
                        <GenerateTaskButton record={[record?.id]} dataSource={data} />
                    </Toolbar>
                </DialogActions>
            </Dialog>
@@ -168,12 +160,12 @@
export default ItemToTaskModal;
const GenerateTaskButton = (record) => {
const GenerateTaskButton = (record, dataSource) => {
    const refresh = useRefresh();
    const notify = useNotify();
    const redirect = useRedirect();
    const generateTask = async () => {
        const res = await request.post(`/wave/public/task`, { ids: record?.record });
        const res = await request.post(`/wave/public/task`, { wave: dataSource });
        if (res?.data?.code === 200) {
            notify(res.data.msg);
            redirect("/task")
@@ -183,4 +175,32 @@
        refresh();
    }
    return (<Button variant="contained" label={"ra.action.save"} onClick={generateTask}></Button>)
}
const TagsField = () => {
    const record = useRecordContext();
    const translate = useTranslate();
    const locs = JSON.parse(record.stockLocs);
    if (locs == undefined || locs.length < 1) {
        return (
            <>
                <ListItem>
                    <Chip color="error" label={translate("common.action.stockError")} variant="outlined" />
                </ListItem>
            </>
        )
    } else {
        return (
            <>
                {locs.map((data) => {
                    return (
                        <ListItem key={data?.id}>
                            <Chip label={data?.locCode} />
                        </ListItem>
                    )
                })}
            </>
        )
    }
}
rsf-server/src/main/Test/CombinationFinder.java
New file
@@ -0,0 +1,123 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CombinationFinder {
    public List<Integer> findCombination(double[] nums, double target) {
        // 处理等值情况
        List<Integer> equal = findEqual(nums, target);
        if (equal != null && !equal.isEmpty()) {
            return equal;
        }
        // 处理大于的情况
        List<Integer> greater = findGreater(nums, target);
        if (greater != null && !greater.isEmpty()) {
            return greater;
        }
        // 处理小于的情况
        List<Integer> less = findLess(nums, target);
        return less != null ? less : new ArrayList<>(); // 确保不返回null
    }
    private List<Integer> findEqual(double[] nums, double target) {
        int n = nums.length;
        double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题
        Arrays.fill(dp, Double.MAX_VALUE);
        dp[0] = 0;
        int[] prev = new int[(int) (target * 100 + 1)];
        Arrays.fill(prev, -1);
        for (int j = 0; j < n; j++) {
            int num = (int) (nums[j] * 100); // 放大100倍处理精度问题
            for (int i = (int) (target * 100); i >= num; i--) {
                if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) {
                    dp[i] = dp[i - num] + 1;
                    prev[i] = j;
                }
            }
        }
        if (dp[(int) (target * 100)] == Double.MAX_VALUE) {
            return null;
        }
        List<Integer> indices = new ArrayList<>();
        int current = (int) (target * 100);
        while (current > 0) {
            int j = prev[current];
            if (j == -1) return null;
            indices.add(j);
            current -= (int) (nums[j] * 100);
        }
        return indices;
    }
    private List<Integer> findGreater(double[] nums, double target) {
        List<double[]> sorted = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            sorted.add(new double[]{nums[i], i});
        }
        sorted.sort((a, b) -> Double.compare(b[0], a[0]));
        double sum = 0;
        List<Integer> indices = new ArrayList<>();
        for (double[] pair : sorted) {
            sum += pair[0];
            indices.add((int) pair[1]);
            if (sum > target) {
                return indices;
            }
        }
        return null;
    }
    private List<Integer> findLess(double[] nums, double target) {
        int n = nums.length;
        List<Integer> bestIndices = new ArrayList<>();
        double[] maxSum = {-1.0};
        for (int k = 1; k <= n; k++) {
            List<Integer> currentIndices = new ArrayList<>();
            double[] currentMax = {-1.0};
            backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>());
            if (currentMax[0] != -1.0) {
                if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) {
                    maxSum[0] = currentMax[0];
                    bestIndices = new ArrayList<>(currentIndices);
                }
            }
        }
        return bestIndices.isEmpty() ? null : bestIndices;
    }
    private void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) {
        if (count == k) {
            if (currentSum <= target && currentSum > currentMax[0]) {
                currentMax[0] = currentSum;
                currentIndices.clear();
                currentIndices.addAll(path);
            }
            return;
        }
        for (int i = start; i < nums.length; i++) {
            if (currentSum + nums[i] > target) continue;
            path.add(i);
            backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path);
            path.remove(path.size() - 1);
        }
    }
    public static void main(String[] args) {
        CombinationFinder finder = new CombinationFinder();
        double[] nums = {3.0, 1.0, 4.0, 2.0};
        double target = 0.1;
        List<Integer> result = finder.findCombination(nums, target);
        System.out.println("最优组合的索引: " + result); // 例如,等值组合可能为索引2(4.0)和3(1.0)
    }
}
rsf-server/src/main/Test/MinimalCombinationSum.java
File was deleted
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
@@ -111,13 +111,11 @@
        ExcelUtil.build(ExcelUtil.create(waveService.list(), Wave.class), response);
    }
    @PreAuthorize("hasAuthority('manager:wave:update')")
    @ApiOperation("波次下发任务")
    @PostMapping("/wave/public/task")
    public R publicTask(@RequestBody Map<String, Object> map) {
        if (Cools.isEmpty(map)) {
        if (Cools.isEmpty(map) || Cools.isEmpty(map.get("wave"))) {
            throw new CoolException("参数不能为空!!");
        }
        return waveService.publicTask(map);
@@ -127,7 +125,7 @@
    @ApiOperation("波次出库任务预览")
    @PostMapping("/wave/locs/preview/page")
    public R mergeWavePreview(@RequestBody Map<String, Object> map) {
        if (Cools.isEmpty(map.get("waveId")) || StringUtils.isBlank(map.get("waveId").toString())) {
        if (Cools.isEmpty(map.get("wave")) || StringUtils.isBlank(map.get("wave").toString())) {
            throw new CoolException("参数不能为空!!");
        }
        Long waveId = Long.parseLong(map.get("waveId").toString());
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
@@ -170,11 +170,21 @@
    @ApiModelProperty(value= "修改人员")
    private Long updateBy;
    /**
     * 目标库位
     */
    @ApiModelProperty("目标库位")
    @TableField(exist = false)
    private String stockLocs;
    /**
     * 库存数量
     */
    @ApiModelProperty("库存数量")
    @TableField(exist = false)
    private Double stockQty;
    /**
     * 备注
     */
    @ApiModelProperty(value= "备注")
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -17,15 +17,14 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Service("waveService")
public class WaveServiceImpl extends ServiceImpl<WaveMapper, Wave> implements WaveService {
@@ -53,10 +52,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R publicTask(Map<String, Object> map) {
        List<Long> ids = (List<Long>) map.get("ids");
        if (Objects.isNull(ids) || ids.isEmpty()) {
        List<WaveItem> itemParams = (List<WaveItem>) map.get("wave");
        if (Objects.isNull(itemParams) || itemParams.isEmpty()) {
            throw new CoolException("参数不能为空!!");
        }
        List<Long> ids = itemParams.stream().map(WaveItem::getWaveId).collect(Collectors.toList());
        List<Wave> waves = this.list(new LambdaQueryWrapper<Wave>().in(Wave::getId, ids));
        if (Objects.isNull(waves) || waves.isEmpty()) {
            throw new CoolException("波次数据不存在!!");
@@ -67,10 +67,9 @@
            throw new CoolException("波次明细不存在!!");
        }
        List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList());
        /**查询每条明细匹配的库位*/
        try {
            List<WaveItem> items = getLocs(waveItems);
//            List<WaveItem> items = getLocs(waveItems);
        } catch (Exception e) {
            throw new CoolException("库位获取失败!!!");
        }
@@ -78,16 +77,16 @@
        // 2. 根据物料SKU寻找符合物料库位  {1. 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 2. 判断当前订单是全拖出库还是拣料入库}
        // 3. 修改主单、波次执行数量
        // 4. 判断全仓出库或拣料出库
        List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds));
        /**修改原出库单状态*/
        if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>()
                .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val)
                .in(AsnOrder::getId, orders))) {
            throw new CoolException("出库单据状态修改失败!!");
        }
        if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId, ids))) {
            throw new CoolException("波次状态修改失败!!");
        }
//        List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds));
//        /**修改原出库单状态*/
//        if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>()
//                .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val)
//                .in(AsnOrder::getId, orders))) {
//            throw new CoolException("出库单据状态修改失败!!");
//        }
//        if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId, ids))) {
//            throw new CoolException("波次状态修改失败!!");
//        }
        return R.ok();
    }
@@ -136,15 +135,26 @@
                    .eq(LocItem::getSplrBatch, waveItem.getSplrBatch())
                    .eq(StringUtils.isNotBlank(waveItem.getFieldsIndex()), LocItem::getFieldsIndex, waveItem.getFieldsIndex())
                    .groupBy(LocItem::getMatnrCode, LocItem::getSplrBatch, LocItem::getFieldsIndex, LocItem::getId));
            List<Double> doubles1 = locItems.stream().map(LocItem::getAnfme).collect(Collectors.toList());
            double[] doubles = doubles1.stream().mapToDouble(Double::doubleValue).toArray();
            Double[] doubles = locItems.stream().map(LocItem::getAnfme).toArray(Double[]::new);
            List<Double> result = OptimalAlgorithmUtil.findBestCombination(doubles, waveItem.getAnfme());
            String locs = JSONArray.toJSONString(new ArrayList<>());
            if (!locItems.isEmpty()) {
                List<String> codes = locItems.stream().map(LocItem::getLocCode).collect(Collectors.toList());
                locs = JSONArray.toJSONString(codes);
            /**使用回溯算法计算,获取符合出库量的最简组合*/
            List<Integer> result = OptimalAlgorithmUtil.findCombination(doubles, waveItem.getAnfme());
            String locs = "[]";
            if (Objects.isNull(result)) {
                waveItem.setStockLocs(locs).setStockQty(0.0);
            } else {
                /**过滤集合中最简短的组合*/
                List<LocItem> locsInfo = result.stream()
                        .filter(i -> i >= 0 && i < locItems.size())
                        .map(locItems::get).collect(Collectors.toList());
                locs = JSONArray.toJSONString(locsInfo);
                double sumQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum();
                double surQty = locItems.stream().mapToDouble(LocItem::getWorkQty).sum();
                double qty = locItems.stream().mapToDouble(LocItem::getQty).sum();
                double v = sumQty - surQty - qty;
                waveItem.setStockLocs(locs).setStockQty(v);
            }
            waveItem.setStockLocs(locs);
        });
        return waveItems;
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java
@@ -6,9 +6,9 @@
import java.util.stream.Collectors;
/**
 * @param
 * @author Ryan
 * @description 根据对象多个属性合并处理
 * @param
 * @return
 * @time 2025/4/25 10:55
 */
@@ -46,9 +46,9 @@
    }
    /**
     * @param
     * @author Ryan
     * @description 用于存储多个分组键的内部类
     * @param
     * @return
     * @time 2025/4/25 10:56
     */
@@ -80,52 +80,140 @@
    }
    public static List<Double> findBestCombination(Double[] candidates, double targetSum) {
        bestSolution = null;
        target = targetSum;
        minDifference = Double.MAX_VALUE;
        Arrays.sort(candidates);
        backtrack(candidates, 0, new ArrayList<>(), 0.0);
        // 如果没有精确解,返回最接近的近似解
        return bestSolution != null ? bestSolution : new ArrayList<>();
    }
    private static void backtrack(Double[] candidates, int start,
                                  List<Double> current, double currentSum) {
        // 计算当前和与目标的差值
        double difference = Math.abs(currentSum - target);
        // 如果找到更优解(差值更小或差值相同但组合更短)
        if (difference < minDifference - EPSILON ||
                (Math.abs(difference - minDifference) < EPSILON &&
                        (bestSolution == null || current.size() < bestSolution.size()))) {
            minDifference = difference;
            bestSolution = new ArrayList<>(current);
    /**
     * @param
     * @return
     * @author Ryan
     * @description 获取全拖或非全拖库位
     * @time 2025/4/28 09:41
     */
    public static List<Integer> findCombination(double[] nums, Double target) {
        // 处理等值情况
        List<Integer> equal = findEqual(nums, target);
        if (equal != null && !equal.isEmpty()) {
            return equal;
        }
        // 如果已经找到精确解,不需要继续搜索更长的组合
        if (minDifference < EPSILON) {
        // 处理大于的情况
        List<Integer> greater = findGreater(nums, target);
        if (greater != null && !greater.isEmpty()) {
            return greater;
        }
        // 处理小于的情况
        List<Integer> less = findLess(nums, target);
        return less != null ? less : new ArrayList<>(); // 确保不返回null
    }
    /**
     * @author Ryan
     * @description 使用动态规划寻找和等于目标值的最少元素组合。动态规划数组dp记录达到每个和所需的最少元素数目,prev数组记录路径以便回溯找到具体索引。
     * @param
     * @return
     * @time 2025/4/28 12:33
     */
    private static List<Integer> findEqual(double[] nums, double target) {
        int n = nums.length;
        double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题
        Arrays.fill(dp, Double.MAX_VALUE);
        dp[0] = 0;
        int[] prev = new int[(int) (target * 100 + 1)];
        Arrays.fill(prev, -1);
        for (int j = 0; j < n; j++) {
            int num = (int) (nums[j] * 100); // 放大100倍处理精度问题
            for (int i = (int) (target * 100); i >= num; i--) {
                if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) {
                    dp[i] = dp[i - num] + 1;
                    prev[i] = j;
                }
            }
        }
        if (dp[(int) (target * 100)] == Double.MAX_VALUE) {
            return null;
        }
        List<Integer> indices = new ArrayList<>();
        int current = (int) (target * 100);
        while (current > 0) {
            int j = prev[current];
            if (j == -1) return null;
            indices.add(j);
            current -= (int) (nums[j] * 100);
        }
        return indices;
    }
    /**
     * @author Ryan
     * @description 将数组降序排序后,贪心地累加元素直到总和超过目标值,返回这些元素的索引
     * @param
     * @return
     * @time 2025/4/28 12:34
     */
    private static List<Integer> findGreater(double[] nums, double target) {
        List<double[]> sorted = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            sorted.add(new double[]{nums[i], i});
        }
        sorted.sort((a, b) -> Double.compare(b[0], a[0]));
        double sum = 0;
        List<Integer> indices = new ArrayList<>();
        for (double[] pair : sorted) {
            sum += pair[0];
            indices.add((int) pair[1]);
            if (sum > target) {
                return indices;
            }
        }
        return null;
    }
    /**
     * @author Ryan
     * @description 使用回溯法生成所有可能的组合,寻找总和最大但不超过目标值的组合,并记录元素数目最少的组合。
     * @param
     * @return
     * @time 2025/4/28 12:34
     */
    private static List<Integer> findLess(double[] nums, double target) {
        int n = nums.length;
        List<Integer> bestIndices = new ArrayList<>();
        double[] maxSum = {-1.0};
        for (int k = 1; k <= n; k++) {
            List<Integer> currentIndices = new ArrayList<>();
            double[] currentMax = {-1.0};
            backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>());
            if (currentMax[0] != -1.0) {
                if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) {
                    maxSum[0] = currentMax[0];
                    bestIndices = new ArrayList<>(currentIndices);
                }
            }
        }
        return bestIndices.isEmpty() ? null : bestIndices;
    }
    private static void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) {
        if (count == k) {
            if (currentSum <= target && currentSum > currentMax[0]) {
                currentMax[0] = currentSum;
                currentIndices.clear();
                currentIndices.addAll(path);
            }
            return;
        }
        // 遍历候选数字
        for (int i = start; i < candidates.length; i++) {
            double num = candidates[i];
            // 剪枝:如果当前和已经远大于目标值,跳过
            if (currentSum + num > target + minDifference + EPSILON) {
                continue;
            }
            // 跳过重复数字
            if (i > start && Math.abs(candidates[i] - candidates[i - 1]) < EPSILON) {
                continue;
            }
            current.add(num);
            backtrack(candidates, i + 1, current, currentSum + num);
            current.remove(current.size() - 1);
        for (int i = start; i < nums.length; i++) {
            if (currentSum + nums[i] > target) continue;
            path.add(i);
            backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path);
            path.remove(path.size() - 1);
        }
    }