JS从含嵌套数组的数组选取x个随机元素 每个嵌套数组最多选1个
健身随机训练动作选取工具 互斥逻辑实现
核心需求
- 支持用户自定义选取n个健身训练动作
- 同组互斥动作(例如不同姿势引体向上、不同距离划船)单次生成结果中最多出现1个
- 动作库统一采用嵌套数组格式,无互斥关系的单独动作存为长度为1的子数组,方便后续维护扩展
原有逻辑缺陷
原有实现直接对扁平化的动作列表整体洗牌后截取前n项,无法识别互斥分组,会出现同组多个动作同时被选中的问题。
实现方案
- 重构动作库结构:将所有互斥动作归入同一子数组,独立动作单独存为单元素子数组
- 调整随机规则:先对外层的动作组做随机排序,遍历乱序后的动作组时,每组随机抽取1个动作加入结果集
- 边界处理:将选择滑块的最大值设置为动作组总数量,避免用户选择超过可选上限的数值(每组最多出1个动作,可选最大数量等于动作组总数)
- 结果集长度达到用户指定的n时立即停止遍历,减少不必要的计算
可直接运行的完整代码
// 获取滑块选中的动作数量 function getNumber(){ return Number(document.getElementById("userInput").value) } // 通用数组洗牌函数 function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array } // 主生成按钮触发函数 function myFunction() { const selectCount = getNumber() // 清空上一轮生成的动作列表 document.getElementById("todays_exercises").innerHTML = ""; // 重构后的动作库:同一子数组内为互斥动作 const exerciseGroups = [ [" air squats ×40"], [" skip ×200"], [" burpees ×20"], [" row 500m", " row 250m"], [" pull-ups ×10", " finger board pull-ups ×10", " ring pull-ups ×10"], [" sit-ups ×40"], [" press-ups ×25"], [" lunges ×20"], [" curls ×15"], [" ring rows ×12"], [" assisted ring dips ×10"], [" ring push ups ×10"], [" power snatch ×10"], [" power clean ×20"], [" power clean & jerk ×15"], [" hanging leg raises ×20"], [" bench press ×10"], [" calf raises ×20"], [" plank 20s"], [" dips ×20"] ]; // 设置滑块可选最大值 document.getElementById("userInput").setAttribute('max', exerciseGroups.length); // 打乱动作组顺序 const shuffledGroups = shuffleArray([...exerciseGroups]) const selected = [] // 逐组随机抽取动作,选够数量即停止 for (const group of shuffledGroups) { if (selected.length >= selectCount) break const randomIdx = Math.floor(Math.random() * group.length) selected.push(group[randomIdx]) } // 渲染结果到页面 selected.forEach(item => { document.getElementById("todays_exercises").innerHTML += `<li>${item}</li>` }); }
维护说明
后续新增动作时,如果是独立无互斥的动作,直接在exerciseGroups数组中新增一个单元素子数组即可;如果是和现有动作互斥的新动作,直接加到对应互斥组的子数组中,不需要修改选取逻辑。
内容的提问来源于stack exchange,提问作者Mark Evans
相关产品推荐
相关产品推荐

