package com.hangcheng.website.util;
import cn.hutool.core.collection.CollectionUtil;
import com.hangcheng.website.config.CourseConfig;
import com.hangcheng.website.domain.response.ComboResult;
import com.hangcheng.website.domain.response.ComboVoResult;
import com.hangcheng.website.domain.response.CourseIntelligentPageResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName : CourseSelectUtils
* @Description : 课程智能组合工具类 - 针对200课程10组合优化版
* @Author : 牛欣如
* @Date: 2025-09-02 14:10
*/
@Component
public class CourseSelectUtils {
@Autowired
private CourseConfig courseConfig;
private static final int MAX_COURSES_PER_COMBO = 10;
private static final int MAX_COMBINATIONS_PER_GROUP = 10;
private static final int MAX_TOTAL_COURSES = 200;
/**
* 为单个条件生成课程组合(优化版,专门针对200课程生成10个组合)
*/
public List<List<CourseIntelligentPageResponse>> generateNearestCombinations(
int targetHours,
int minCourses,
int maxCourses,
List<CourseIntelligentPageResponse> courses,
int numCombinations) {
// 过滤有效课程并排序(按学时降序,利于剪枝)
List<CourseIntelligentPageResponse> validCourses = courses.stream()
.limit(MAX_TOTAL_COURSES).collect(Collectors.toList());
// 根据数据规模选择合适的算法
List<List<CourseIntelligentPageResponse>> combinations;
combinations = findCombinationsBacktrackOptimized(validCourses, targetHours, minCourses, maxCourses, numCombinations);
// 排序:门数升序,相同时学时降序
combinations.sort((c1, c2) -> {
int sizeCompare = Integer.compare(c1.size(), c2.size());
if (sizeCompare != 0) return sizeCompare;
int sum1 = c1.stream().mapToInt(CourseIntelligentPageResponse::getHourCount).sum();
int sum2 = c2.stream().mapToInt(CourseIntelligentPageResponse::getHourCount).sum();
return Integer.compare(sum2, sum1);
});
return getUniqueCombinations(combinations, numCombinations);
}
/**
* 优化的回溯算法
*/
private List<List<CourseIntelligentPageResponse>> findCombinationsBacktrackOptimized(
List<CourseIntelligentPageResponse> courses,
int targetHours,
int minCourses,
int maxCourses,
int maxResults) {
List<List<CourseIntelligentPageResponse>> result = new ArrayList<>();
Set<String> fingerprints = new HashSet<>();
// 按门数范围分别搜索
for (int courseCount = minCourses; courseCount <= maxCourses && result.size() < maxResults; courseCount++) {
backtrackOptimized(courses, targetHours, courseCount, 0, new ArrayList<>(),
result, fingerprints, maxResults);
}
return result;
}
/**
* 强化的回溯剪枝算法
*/
private void backtrackOptimized(List<CourseIntelligentPageResponse> courses, int target, int courseCount,
int start, List<CourseIntelligentPageResponse> current,
List<List<CourseIntelligentPageResponse>> result, Set<String> fingerprints,
int maxResults) {
if (result.size() >= maxResults) return;
// 提前终止条件:当前总和已经超过目标
int currentSum = current.stream().mapToInt(CourseIntelligentPageResponse::getHourCount).sum();
if (currentSum > target) return;
if (current.size() == courseCount) {
if (currentSum == target) {
String fingerprint = generateCombinationFingerprint(current);
if (!fingerprints.contains(fingerprint)) {
fingerprints.add(fingerprint);
result.add(new ArrayList<>(current));
}
}
return;
}
// 计算剩余课程的最大可能和
int maxRemainingSum = 0;
int remainingCourses = courseCount - current.size();
for (int i = start; i < Math.min(start + remainingCourses, courses.size()); i++) {
maxRemainingSum += courses.get(i).getHourCount();
}
// 强力剪枝
if (currentSum + maxRemainingSum < target) return;
for (int i = start; i < courses.size() && result.size() < maxResults; i++) {
CourseIntelligentPageResponse course = courses.get(i);
// 剪枝:加上当前课程后超过目标
if (currentSum + course.getHourCount() > target) continue;
// 剪枝:剩余位置不足
if (courses.size() - i < courseCount - current.size()) break;
current.add(course);
backtrackOptimized(courses, target, courseCount, i + 1, current, result, fingerprints, maxResults);
current.remove(current.size() - 1);
}
}
/**
* 跨条件组合生成(优化版)
*/
public ComboVoResult generateOrderedCombinations(
Map<String, List<List<CourseIntelligentPageResponse>>> groupCombinationsMap,
int numCombinations) {
ComboVoResult voResult = new ComboVoResult();
if (CollectionUtil.isEmpty(groupCombinationsMap)) {
voResult.setResults(new ArrayList<>());
voResult.setFailGroupCourses(new ArrayList<>());
return voResult;
}
// 验证每个条件的组合数量
List<String> failGroups = groupCombinationsMap.entrySet().stream()
.filter(entry -> CollectionUtil.isEmpty(entry.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!failGroups.isEmpty()) {
voResult.setResults(new ArrayList<>());
voResult.setFailGroupCourses(failGroups);
return voResult;
}
// 限制每个条件的组合数量,避免组合爆炸
Map<String, List<List<CourseIntelligentPageResponse>>> limitedCombinationsMap = new HashMap<>();
groupCombinationsMap.forEach((key, combinations) -> {
List<List<CourseIntelligentPageResponse>> limited = combinations.stream()
.limit(MAX_COMBINATIONS_PER_GROUP)
.collect(Collectors.toList());
limitedCombinationsMap.put(key, limited);
});
// 使用DFS生成跨条件组合
List<ComboResult> results = generateCrossGroupCombinationsDFS(limitedCombinationsMap, numCombinations);
voResult.setResults(results);
voResult.setFailGroupCourses(new ArrayList<>()); // 前面已经验证过,没有失败组
return voResult;
}
/**
* 使用DFS生成跨条件组合(避免重复课程)
*/
private List<ComboResult> generateCrossGroupCombinationsDFS(
Map<String, List<List<CourseIntelligentPageResponse>>> groupCombinationsMap,
int maxCombinations) {
List<ComboResult> results = new ArrayList<>();
List<String> groupKeys = new ArrayList<>(groupCombinationsMap.keySet());
// 按组合数量升序排列,优先处理组合少的组(利于剪枝)
groupKeys.sort((k1, k2) ->
Integer.compare(groupCombinationsMap.get(k1).size(), groupCombinationsMap.get(k2).size()));
dfsCrossGroups(groupCombinationsMap, groupKeys, 0, new LinkedHashMap<>(),
new HashSet<>(), results, maxCombinations);
return results;
}
/**
* DFS递归生成跨组组合
*/
private void dfsCrossGroups(Map<String, List<List<CourseIntelligentPageResponse>>> groupCombinationsMap,
List<String> groupKeys, int currentIndex,
Map<String, List<CourseIntelligentPageResponse>> currentSelection,
Set<Long> usedCourseIds,
List<ComboResult> results, int maxResults) {
if (results.size() >= maxResults) return;
if (currentIndex >= groupKeys.size()) {
// 找到一个完整组合
ComboResult combo = new ComboResult();
combo.setGroupCourses(new LinkedHashMap<>(currentSelection));
results.add(combo);
return;
}
String currentGroup = groupKeys.get(currentIndex);
List<List<CourseIntelligentPageResponse>> combinations = groupCombinationsMap.get(currentGroup);
for (List<CourseIntelligentPageResponse> combination : combinations) {
// 检查课程是否重复
boolean hasConflict = false;
Set<Long> newCourseIds = new HashSet<>();
for (CourseIntelligentPageResponse course : combination) {
if (usedCourseIds.contains(course.getId())) {
hasConflict = true;
break;
}
newCourseIds.add(course.getId());
}
if (hasConflict) continue;
// 选择当前组合
usedCourseIds.addAll(newCourseIds);
currentSelection.put(currentGroup, new ArrayList<>(combination));
// 递归下一个组
dfsCrossGroups(groupCombinationsMap, groupKeys, currentIndex + 1,
currentSelection, usedCourseIds, results, maxResults);
// 回溯
usedCourseIds.removeAll(newCourseIds);
currentSelection.remove(currentGroup);
if (results.size() >= maxResults) return;
}
}
/**
* 获取唯一组合
*/
private List<List<CourseIntelligentPageResponse>> getUniqueCombinations(
List<List<CourseIntelligentPageResponse>> allCombinations,
int maxSize) {
Set<String> fingerprints = new HashSet<>();
List<List<CourseIntelligentPageResponse>> result = new ArrayList<>();
for (List<CourseIntelligentPageResponse> combination : allCombinations) {
String fingerprint = generateCombinationFingerprint(combination);
if (!fingerprints.contains(fingerprint)) {
fingerprints.add(fingerprint);
result.add(combination);
if (result.size() >= maxSize) break;
}
}
return result;
}
/**
* 生成组合指纹
*/
private String generateCombinationFingerprint(List<CourseIntelligentPageResponse> combination) {
return combination.stream()
.map(c -> String.valueOf(c.getId()))
.sorted()
.collect(Collectors.joining(","));
}
/**
* 备用方法:快速生成近似组合(当精确组合不足时)
*/
public List<List<CourseIntelligentPageResponse>> generateApproximateCombinations(
int targetHours,
int minCourses,
int maxCourses,
List<CourseIntelligentPageResponse> courses,
int numCombinations,
int tolerance) {
List<CourseIntelligentPageResponse> validCourses = courses.stream()
.filter(c -> c.getHourCount() > 0)
.sorted((c1, c2) -> Integer.compare(c2.getHourCount(), c1.getHourCount()))
.limit(MAX_TOTAL_COURSES)
.collect(Collectors.toList());
List<List<CourseIntelligentPageResponse>> results = new ArrayList<>();
Set<String> fingerprints = new HashSet<>();
// 尝试目标学时附近的组合
for (int offset = 0; offset <= tolerance && results.size() < numCombinations; offset++) {
for (int hours = targetHours - offset; hours <= targetHours + offset; hours++) {
if (hours <= 0) continue;
List<List<CourseIntelligentPageResponse>> combinations =
findCombinationsBacktrackOptimized(validCourses, hours, minCourses, maxCourses,
numCombinations - results.size());
for (List<CourseIntelligentPageResponse> comb : combinations) {
String fingerprint = generateCombinationFingerprint(comb);
if (!fingerprints.contains(fingerprint)) {
fingerprints.add(fingerprint);
results.add(comb);
if (results.size() >= numCombinations) break;
}
}
if (results.size() >= numCombinations) break;
}
}
return results;
}
}优化,使用内存小,cup小
最新发布