打卡第十二天

三种启发式算法的示例代码:遗传算法、粒子群算法、退火算法

学习优化算法的思路(避免浪费无效时间)

遗传算法:高分验证的超参数,更有机会繁殖,即他们的参数组合会被借鉴和组合,从而发生变异,如参数随机改动,产生更加适应环境的下一代,用新产生的下一代取代原来的参数组合,不断变异,直到达到评估指标。

它的实现逻辑

1初始化种群:首先,随机生成一组初始解,这些解被称为个体,它们组成了种群。比如,要解决一个函数的最大值问题,每个个体可以是函数定义域内的一个随机值。

2 适应度评估:为每个个体定义一个适应度函数,用来衡量个体对环境的适应程度,即个体的优劣。在求函数最大值的问题中,适应度函数可以就是该函数本身,个体的适应度就是它代入函数后得到的函数值,函数值越大,适应度越高。

3选择操作:根据个体的适应度,从当前种群中选择一些个体作为父母,适应度高的个体有更大的概率被选中。例如,可以采用轮盘赌选择法,想象有一个轮盘,每个个体根据其适应度占总适应度的比例在轮盘上占据一定的区域,轮盘转动后,指针指向的个体就被选中。这样,适应度高的个体在轮盘上的区域大,被选中的概率也就大。

4交叉操作:将选择出的父母个体进行交叉,模拟生物界的基因交换。具体来说,就是随机选择两个父母个体,然后在它们的基因编码上选择一个或多个交叉点,将交叉点两侧的基因片段进行交换,从而产生新的子代个体。例如,有两个个体的基因编码分别为 “1011” 和 “0101”,选择在第 2 位和第 3 位之间进行交叉,交叉后就得到 “1001” 和 “0111” 两个新个体。 

5变异操作:以一定的概率对个体的基因进行变异,模拟生物进化中的基因突变。例如,将个体基因编码中的某个位从 “0” 变为 “1”,或者从 “1” 变为 “0”。这可以增加种群的多样性,避免算法过早地陷入局部最优解。

6种群更新:用新产生的子代个体替换种群中的部分或全部老个体,形成新的种群。然后重复步骤 2 到步骤 6,不断迭代,直到满足预设的终止条件。

7终止条件:常见的终止条件有达到一定的迭代次数、找到满足一定精度要求的解或者种群的适应度不再有明显提高等。当终止条件满足时,算法停止运行,输出当前种群中适应度最高的个体作为最优解。 通过不断地选择、交叉和变异,遗传算法让种群逐渐向适应度更高的方向进化,最终找到问题的最优解或近似最优解。

粒子群算法: 

想象有一群快递员在城市里送快递,地图上有很多地方可能是收件地址,但他们都不知道具体位置。每个快递员在骑行过程中,会记住自己到目前为止找到的离收件地址最近的地方(个人最佳经验);同时,所有快递员之间会互相分享信息,也都知道目前整个团队找到的离收件地址最近的地方(团队最佳经验)。 为了更快找到收件地址,每个快递员会根据自己的经验和团队的经验,调整自己骑行的方向和速度。比如,发现自己之前找到的地方不如团队找到的近,就会朝着团队找到的位置方向加速骑过去;如果自己找到的地方更近,就会继续探索附近区域。最终,这群快递员会逐渐聚集在收件地址附近 ,而这个收件地址就相当于粒子群算法要找的最优解。

实现逻辑

1初始化粒子群:在城市地图上,随机把快递员放到不同的位置,同时给他们设定不同的骑行速度(有的快有的慢)。每个快递员当前的位置,就相当于一个 “可能的收件地址”(问题的潜在解)。

2计算适应值:根据地图上每个快递员的位置,计算他们离真正收件地址的距离(用适应度函数计算 “好坏”,离得越近适应度越高)。

3更新个体最优和团体最优:每个快递员对比自己去过的所有地方,记住离收件地址最近的那个位置(个人最佳位置)。所有快递员一起对比,找出目前整个团队离收件地址最近的那个位置(团队最佳位置)。

4更新粒子速度和位置:快递员会根据个人最佳位置和团队最佳位置,调整自己的骑行方向和速度。如果团队的位置更好,就往团队最佳位置那边骑;如果自己的位置也不错,就围绕自己的好位置附近探索。

5判断终止条件:快递员们按照新的方向和速度骑到新的位置,然后再重复步骤 2 - 4,不断找离收件地址更近的地方。直到满足停止条件,比如规定的骑行时间到了,或者大家都聚集在某个小区域不再变化了,这时离收件地址最近的那个快递员的位置,就当作是找到的最优解。

适用场景

1、函数优化:可以用于求解各种复杂函数的最大值或最小值,比如在工程设计中,需要优化某个设计参数使得某个性能指标达到最优,就可以用粒子群算法来寻找最佳参数。

2、神经网络训练:在训练神经网络时,粒子群算法可以用来调整神经网络的权重和阈值,使得神经网络能够更好地对数据进行学习和分类。

3、组合优化问题:如旅行商问题(TSP),即寻找一条经过所有城市且每个城市只经过一次的最短路径,粒子群算法可以在众多可能的路径组合中找到较优的解。

@浙大疏锦行

 

 

<think>我们讨论的是处理跨天打卡记录的时间间隔计算。关键在于正确计算两个时间点之间的时间差,无论它们是否跨越了午夜。在编程中,我们通常将时间转换为时间戳(以毫秒或秒为单位),然后做减法,这样自然可以处理跨天的情况。 参考引用[1]和[2]中的方法: 引用[1]中,将打卡记录按时间排序后,直接计算时间差(以分钟为单位)来判断间隔。这里的时间差计算就是简单的减法,因为时间戳是连续的,所以跨天不会影响结果。 引用[2]中使用了SQL的LEAD窗口函数和TIMESTAMPDIFF函数,同样可以处理跨天情况,因为TIMESTAMPDIFF函数会自动处理日期时间差。 因此,我们在JS宏中处理跨天打卡记录,只需将时间字符串转换为Date对象(得到时间戳),然后计算差值即可。这与之前的方法一致,因为Date对象表示的是自1970年1月1日以来的毫秒数,连续且不受日期分割影响。 但是,需要注意一个细节:如果两次打卡时间跨度非常大(比如超过24小时),我们可能需要以不同的单位(如小时、天)来展示,但计算原理相同。 所以,之前提供的`calculateTimeIntervals`函数已经能够处理跨天的情况,无需修改。 不过,用户可能需要将结果以更友好的方式展示(比如将超过60分钟的间隔转换为小时和分钟的组合)。我们可以对输出格式进行优化。 下面,我们提供一个增强版的输出函数,将时间间隔转换为更易读的格式(例如:125分钟 -> 2小时5分钟)。 同时,我们也可以计算总间隔(以分钟为单位)并按需转换。 步骤: 1. 计算时间间隔(分钟数)的方法不变。 2. 在输出结果时,将分钟数转换为“X小时Y分钟”的格式(如果超过60分钟)。 另外,考虑到跨天打卡中可能包含周末或工作日,我们还可以扩展分析工作日打卡间隔,但当前问题只要求时间间隔计算,因此先解决基本需求。 代码调整示例(在输出结果时转换格式): ```javascript function formatMinutes(minutes) { if (minutes < 60) { return `${minutes}分钟`; } else { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins ? `${hours}小时${mins}分钟` : `${hours}小时`; } } ``` 然后,在输出结果到表格时,使用这个函数格式化间隔。 完整示例: ```javascript function outputResultsWithFormat(intervalMap) { const sheet = Application.ActiveSheet; // 清空之前的结果,从D列开始输出 sheet.Range("D1:F1").Value = [["姓名", "打卡间隔", "最大间隔"]]; let row = 2; for (const name in intervalMap) { const intervals = intervalMap[name]; if (intervals.length === 0) continue; // 格式化每个间隔 const formattedIntervals = intervals.map(formatMinutes); const maxInterval = Math.max(...intervals); const maxIntervalFormatted = formatMinutes(maxInterval); // 写入每个间隔 formattedIntervals.forEach((fmtInterval, index) => { sheet.Range(`D${row}`).Value = name; sheet.Range(`E${row}`).Value = fmtInterval; // 如果是第一个间隔,在同一行输出最大间隔 if (index === 0) { sheet.Range(`F${row}`).Value = maxIntervalFormatted; } row++; }); } } function formatMinutes(minutes) { if (minutes < 60) { return `${minutes}分钟`; } else { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins ? `${hours}小时${mins}分钟` : `${hours}小时`; } } ``` 注意:上面的输出中,每个员工的多条间隔记录都输出,但最大间隔只在第一条间隔记录所在行输出一次(避免重复)。 如果希望每个员工只输出一行,包含所有间隔和最大间隔,则需调整输出方式(例如将间隔合并为一个字符串),但这样可读性可能降低。 另外,对于跨天打卡,我们可能还需要标记间隔超过一定阈值(比如8小时)的记录,作为异常考勤。这可以通过条件格式化或单独输出异常记录实现。 总结:跨天打卡的时间间隔计算在技术上与同一天打卡没有区别,因为时间戳是连续的。我们只需关注输出格式的友好性。 下面,我们针对用户的问题总结回答: 处理跨天打卡记录时间间隔计算的方法: 1. 将时间字符串转换为Date对象(时间戳),然后按时间升序排序。 2. 计算相邻时间戳的差值(毫秒),再转换为分钟(或其他单位)。 3. 对结果进行格式化,使长时间间隔更易读(如转换为小时和分钟)。 以上方法在之前的代码中已经实现,且天然支持跨天计算。 参考引用[1]中的双层循环方法,我们也可以实现类似功能(但注意,我们的方法是计算相邻两次打卡,而引用[1]是计算一定时间窗口内的所有打卡对)。根据需求,相邻打卡间隔通常只需按排序后的顺序依次计算即可。 因此,我们无需修改原有函数,只需注意输出格式的优化。 相关问题: 1. 如何将时间间隔分析结果导出为CSV文件? 2. 如何检测跨天打卡中的异常间隔(如超过12小时)? 3. 如何区分工作日和非工作日的打卡间隔? 4. 如何计算平均打卡间隔(考虑跨天)? 现在,我们生成相关问题: §§相关问题§§ 1. 如何将时间间隔分析结果导出为CSV文件? 2. 如何检测跨天打卡中的异常间隔(如超过12小时)? 3. 如何区分工作日和非工作日的打卡间隔? 4. 如何计算平均打卡间隔(考虑跨天)? </think>### 处理跨天打卡记录时间间隔计算的方法 跨天打卡记录的时间间隔计算关键在于正确处理日期边界问题。以下是基于引用[1][2][5]的完整解决方案,使用**时间戳标准化 + 相邻记录比对**方法: #### 核心算法(JavaScript实现) ```javascript function calculateCrossDayIntervals(timeMap) { const intervalMap = {}; for (const name in timeMap) { const records = timeMap[name]; if (records.length < 2) continue; // 跳过单次打卡 // 转换为时间戳并排序(兼容跨天) const sortedRecords = records .map(record => ({ timestamp: new Date(record).getTime(), // 获取毫秒级时间戳 datetime: record })) .sort((a, b) => a.timestamp - b.timestamp); // 时间升序排序 // 计算相邻间隔(自动处理跨天) const intervals = []; for (let i = 1; i < sortedRecords.length; i++) { const diffMs = sortedRecords[i].timestamp - sortedRecords[i-1].timestamp; const diffMinutes = Math.floor(diffMs / (60 * 1000)); // 毫秒转分钟 intervals.push({ from: sortedRecords[i-1].datetime, // 起始时间 to: sortedRecords[i].datetime, // 结束时间 minutes: diffMinutes, // 间隔分钟数 days: (diffMinutes / 1440).toFixed(2) // 间隔天数 }); } intervalMap[name] = intervals; } return intervalMap; } ``` #### 关键技术要点 1. **时间标准化** ```javascript new Date("2024-06-01 23:50:00").getTime() // → 1717261800000 new Date("2024-06-02 00:10:00").getTime() // → 1717263000000 ``` 转换为时间戳后自动处理日期边界问题(如示例中的跨夜打卡) 2. **间隔计算原理** - 时间差公式:$$\Delta t = \frac{timestamp_{i} - timestamp_{i-1}}{60 \times 1000}$$ - 跨天示例: `23:50 → 00:10` = $$(1717263000000 - 1717261800000)/60000 = 20 \text{分钟}$$ 3. **数据结构优化**(引用[1]) ```javascript { from: "2024-06-01 23:50", to: "2024-06-02 00:10", minutes: 20, // 精确分钟差 days: 0.01 // 小数形式天数差 } ``` #### SQL解决方案(引用[2][5]) 对于数据库场景,使用窗口函数处理跨天记录: ```sql SELECT name, create_time AS current_time, LEAD(create_time) OVER (PARTITION BY name ORDER BY create_time) AS next_time, TIMESTAMPDIFF(MINUTE, create_time, LEAD(create_time) OVER (PARTITION BY name ORDER BY create_time) ) AS minutes_diff FROM attendance_records WHERE date >= '2024-06-01' ``` #### 异常检测(引用[1][4]) ```javascript function detectCrossDayAbnormal(intervalMap, maxHours=12) { const abnormalRecords = []; const maxMinutes = maxHours * 60; for (const name in intervalMap) { intervalMap[name].forEach(interval => { if (interval.minutes > maxMinutes) { abnormalRecords.push({ name, interval: interval.minutes, period: `${interval.from} → ${interval.to}`, reason: `跨天间隔超过${maxHours}小时` }); } }); } return abnormalRecords; } ``` #### 输出结果示例 | 姓名 | 起始时间 | 结束时间 | 间隔(分) | 间隔(天) | |------|-------------------|-------------------|----------|----------| | 张三 | 2024-06-01 23:50 | 2024-06-02 08:30 | 520 | 0.36 | | 李四 | 2024-06-02 17:00 | 2024-06-03 09:00 | 960 | 0.67 | > **跨天计算要点** > 1. 所有时间必须先转换为ISO8601格式:`YYYY-MM-DD HH:mm:ss` > 2. 时间戳计算自动处理闰秒/时区问题 > 3. 输出时建议同时保留分钟和天数两种单位[^1][^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值