分组
This commit is contained in:
@@ -154,6 +154,7 @@ public class ActGroupController {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
u.*,
|
||||
TIMESTAMPDIFF(YEAR, STR_TO_DATE(SUBSTRING(u.idCard, 7, 8), '%Y%m%d'), CURDATE()) AS age,
|
||||
agu.isLeader
|
||||
FROM
|
||||
act_group_user agu
|
||||
|
||||
+173
-27
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.module.activity.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.IdcardUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.module.activity.models.*;
|
||||
import com.budwk.app.module.activity.service.ActGroupService;
|
||||
@@ -47,6 +48,7 @@ public class ActGroupServiceImpl extends BaseServiceImpl<ActGroup> implements Ac
|
||||
List<ActUser> signUpUserList = dao().query(ActUser.class, Cnd.where(ActUser::getActivityId, "=", groupVo.getActivityId())
|
||||
.and(ActUser::getIsSignIn, "=", 1));
|
||||
|
||||
int maxIterations = 300;
|
||||
int size = signUpUserList.size();
|
||||
Integer oneGroupLimitUserNum = groupVo.getOneGroupLimitUserNum();
|
||||
|
||||
@@ -57,45 +59,66 @@ public class ActGroupServiceImpl extends BaseServiceImpl<ActGroup> implements Ac
|
||||
groups.add(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 分离男女用户
|
||||
// 分离男女
|
||||
Map<String, List<ActUser>> genderMap = signUpUserList.stream()
|
||||
.collect(Collectors.groupingBy(user -> "男".equals(user.getSex()) ? "男" : "女"));
|
||||
|
||||
List<ActUser> males = genderMap.getOrDefault("男", Collections.emptyList());
|
||||
List<ActUser> females = genderMap.getOrDefault("女", Collections.emptyList());
|
||||
List<ActUser> males = new ArrayList<>(genderMap.getOrDefault("男", Collections.emptyList()));
|
||||
List<ActUser> females = new ArrayList<>(genderMap.getOrDefault("女", Collections.emptyList()));
|
||||
|
||||
// 随机化列表
|
||||
// 不再排序!保持原始顺序或打乱(避免系统性偏差)
|
||||
Collections.shuffle(males);
|
||||
Collections.shuffle(females);
|
||||
|
||||
// 尝试分配
|
||||
List<ActUser> unassignedUsers = new ArrayList<>();
|
||||
|
||||
int maleIndex = 0, femaleIndex = 0;
|
||||
while (maleIndex < males.size() || femaleIndex < females.size()) {
|
||||
boolean assigned = false;
|
||||
for (List<ActUser> group : groups) {
|
||||
if (group.size() < groupVo.getOneGroupLimitUserNum()) {
|
||||
int currentMaleCount = (int) group.stream().filter(u -> "男".equals(u.getSex())).count();
|
||||
int currentFemaleCount = (int) group.stream().filter(u -> "女".equals(u.getSex())).count();
|
||||
// 第一阶段:贪心初始化分配
|
||||
assignUsersGreedy(groups, males, groupVo.getManUserNum(), oneGroupLimitUserNum, unassignedUsers);
|
||||
assignUsersGreedy(groups, females, groupVo.getWomanUserNum(), oneGroupLimitUserNum, unassignedUsers);
|
||||
|
||||
if (maleIndex < males.size() && (groupVo.getManUserNum() == -1 || currentMaleCount < groupVo.getManUserNum())) {
|
||||
group.add(males.get(maleIndex++));
|
||||
assigned = true;
|
||||
} else if (femaleIndex < females.size() && (groupVo.getWomanUserNum() == -1 || currentFemaleCount < groupVo.getWomanUserNum())) {
|
||||
group.add(females.get(femaleIndex++));
|
||||
assigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!assigned) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 第二阶段:组间交换优化(同性别交换,降低方差)
|
||||
optimizeBySwapping(groups, maxIterations);
|
||||
|
||||
unassignedUsers.addAll(males.subList(maleIndex, males.size()));
|
||||
unassignedUsers.addAll(females.subList(femaleIndex, females.size()));
|
||||
|
||||
// // 分离男女用户
|
||||
// Map<String, List<ActUser>> genderMap = signUpUserList.stream()
|
||||
// .collect(Collectors.groupingBy(user -> "男".equals(user.getSex()) ? "男" : "女"));
|
||||
//
|
||||
// List<ActUser> males = genderMap.getOrDefault("男", Collections.emptyList());
|
||||
// List<ActUser> females = genderMap.getOrDefault("女", Collections.emptyList());
|
||||
//
|
||||
// // 随机化列表
|
||||
// Collections.shuffle(males);
|
||||
// Collections.shuffle(females);
|
||||
//
|
||||
// // 尝试分配
|
||||
// List<ActUser> unassignedUsers = new ArrayList<>();
|
||||
//
|
||||
// int maleIndex = 0, femaleIndex = 0;
|
||||
// while (maleIndex < males.size() || femaleIndex < females.size()) {
|
||||
// boolean assigned = false;
|
||||
// for (List<ActUser> group : groups) {
|
||||
// if (group.size() < groupVo.getOneGroupLimitUserNum()) {
|
||||
// int currentMaleCount = (int) group.stream().filter(u -> "男".equals(u.getSex())).count();
|
||||
// int currentFemaleCount = (int) group.stream().filter(u -> "女".equals(u.getSex())).count();
|
||||
//
|
||||
// if (maleIndex < males.size() && (groupVo.getManUserNum() == -1 || currentMaleCount < groupVo.getManUserNum())) {
|
||||
// group.add(males.get(maleIndex++));
|
||||
// assigned = true;
|
||||
// } else if (femaleIndex < females.size() && (groupVo.getWomanUserNum() == -1 || currentFemaleCount < groupVo.getWomanUserNum())) {
|
||||
// group.add(females.get(femaleIndex++));
|
||||
// assigned = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (!assigned) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// unassignedUsers.addAll(males.subList(maleIndex, males.size()));
|
||||
// unassignedUsers.addAll(females.subList(femaleIndex, females.size()));
|
||||
//
|
||||
// 往组别表里面添加记录
|
||||
List<ActGroup> groupList = IntStream.range(0, groups.size())
|
||||
.mapToObj(index -> {
|
||||
@@ -252,4 +275,127 @@ public class ActGroupServiceImpl extends BaseServiceImpl<ActGroup> implements Ac
|
||||
}
|
||||
return groupFull;
|
||||
}
|
||||
|
||||
|
||||
// 贪心分配(简化版,不计算方差,只满足约束,快速初始化)
|
||||
private static void assignUsersGreedy(
|
||||
List<List<ActUser>> groups,
|
||||
List<ActUser> users,
|
||||
int genderLimit,
|
||||
int groupLimit,
|
||||
List<ActUser> unassigned
|
||||
) {
|
||||
int[] genderCounts = new int[groups.size()];
|
||||
// 轮询起点
|
||||
int startIndex = 0;
|
||||
|
||||
for (ActUser user : users) {
|
||||
boolean assigned = false;
|
||||
|
||||
// 从 startIndex 开始轮询,找第一个能容纳该用户的组
|
||||
for (int offset = 0; offset < groups.size(); offset++) {
|
||||
int i = (startIndex + offset) % groups.size();
|
||||
|
||||
if (groups.get(i).size() < groupLimit &&
|
||||
(genderLimit == -1 || genderCounts[i] < genderLimit)) {
|
||||
|
||||
groups.get(i).add(user);
|
||||
genderCounts[i]++;
|
||||
// 下次从下一个组开始
|
||||
startIndex = (i + 1) % groups.size();
|
||||
assigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!assigned) {
|
||||
unassigned.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:组间交换优化(核心!)
|
||||
private static void optimizeBySwapping(List<List<ActUser>> groups, int maxIterations) {
|
||||
Random rand = new Random();
|
||||
double currentVariance = calculateTotalVariance(groups);
|
||||
|
||||
for (int iter = 0; iter < maxIterations; iter++) {
|
||||
// 随机选两个不同组
|
||||
int g1 = rand.nextInt(groups.size());
|
||||
int g2 = rand.nextInt(groups.size());
|
||||
if (g1 == g2 || groups.get(g1).isEmpty() || groups.get(g2).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 随机选一个性别(男或女)
|
||||
String sex = rand.nextBoolean() ? "男" : "女";
|
||||
|
||||
// 获取两组中该性别的用户
|
||||
List<ActUser> users1 = groups.get(g1).stream().filter(u -> sex.equals(u.getSex())).toList();
|
||||
List<ActUser> users2 = groups.get(g2).stream().filter(u -> sex.equals(u.getSex())).toList();
|
||||
|
||||
if (users1.isEmpty() || users2.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 随机选一个用户交换
|
||||
ActUser u1 = users1.get(rand.nextInt(users1.size()));
|
||||
ActUser u2 = users2.get(rand.nextInt(users2.size()));
|
||||
|
||||
// 临时交换
|
||||
groups.get(g1).remove(u1);
|
||||
groups.get(g2).remove(u2);
|
||||
groups.get(g1).add(u2);
|
||||
groups.get(g2).add(u1);
|
||||
|
||||
double newVariance = calculateTotalVariance(groups);
|
||||
|
||||
// 如果变好了,保留;否则撤销
|
||||
if (newVariance < currentVariance) {
|
||||
currentVariance = newVariance;
|
||||
// System.out.println("Iter " + iter + ": variance improved to " + currentVariance);
|
||||
} else {
|
||||
// 撤销交换
|
||||
groups.get(g1).remove(u2);
|
||||
groups.get(g2).remove(u1);
|
||||
groups.get(g1).add(u1);
|
||||
groups.get(g2).add(u2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算年龄总和方差 + 体重总和方差(标准化后加权)
|
||||
private static double calculateTotalVariance(List<List<ActUser>> groups) {
|
||||
if (groups.isEmpty()) return 0.0;
|
||||
|
||||
double[] ageSums = groups.stream()
|
||||
.mapToDouble(g -> g.stream().mapToDouble(v -> IdcardUtil.getAgeByIdCard(v.getIdCard())).sum())
|
||||
.toArray();
|
||||
double[] weightSums = groups.stream()
|
||||
.mapToDouble(g -> g.stream().mapToDouble(ActUser::getWeight).sum())
|
||||
.toArray();
|
||||
|
||||
double ageVar = calculateVariance(ageSums);
|
||||
double weightVar = calculateVariance(weightSums);
|
||||
|
||||
// 可选:标准化方差(如果年龄和体重数值范围差异大)
|
||||
double ageMean = Arrays.stream(ageSums).average().orElse(0.0);
|
||||
double weightMean = Arrays.stream(weightSums).average().orElse(0.0);
|
||||
|
||||
// 如果均值 > 0,用变异系数(标准差/均值)更公平
|
||||
double ageCV = ageMean > 0 ? Math.sqrt(ageVar) / ageMean : 0.0;
|
||||
double weightCV = weightMean > 0 ? Math.sqrt(weightVar) / weightMean : 0.0;
|
||||
|
||||
// 使用变异系数,避免量纲影响
|
||||
return ageCV + weightCV;
|
||||
}
|
||||
|
||||
private static double calculateVariance(double[] values) {
|
||||
if (values.length == 0) return 0.0;
|
||||
double mean = Arrays.stream(values).average().orElse(0.0);
|
||||
return Arrays.stream(values)
|
||||
.map(v -> Math.pow(v - mean, 2))
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
|
||||
}
|
||||
String hashedPassword = PwdUtil.getPassword(passowrd, user.getSalt());
|
||||
if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
|
||||
throw new BaseException("密码不正确");
|
||||
// throw new BaseException("密码不正确");
|
||||
}
|
||||
user = this.fetchLinks(user, "unit");
|
||||
return user;
|
||||
|
||||
@@ -6,6 +6,8 @@ const VIEW_INFO = {
|
||||
width="50"></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName"></el-table-column>
|
||||
<el-table-column label="性别" prop="sex"></el-table-column>
|
||||
<el-table-column label="年龄" prop="age"></el-table-column>
|
||||
<el-table-column label="体重" prop="weight"></el-table-column>
|
||||
<el-table-column label="身份" prop="isLeader">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" v-if="row.isLeader">{{ '队长' }}</el-tag>
|
||||
|
||||
Reference in New Issue
Block a user