2024/8/25周报

摘要

多目标优化(Multi-Objective Optimization, MOO)是优化领域的一个分支,它处理的是同时优化多个相互冲突的目标函数的问题。在实际应用中,很少有决策问题只涉及单一目标,通常需要在多个目标之间找到平衡点。例如,在工程设计中可能需要同时考虑成本、性能和可靠性等因素。本周进一步学习一些多目标优化算法的常见类型。

Abstract

Multi-objective optimization (MOO) is a branch of optimization field, which deals with the problem of simultaneously optimizing multiple conflicting objective functions. In practical application, few decision-making problems only involve a single goal, and it is usually necessary to find a balance between multiple goals. For example, factors such as cost, performance and reliability may need to be considered simultaneously in engineering design. Learn more about some common types of multi-objective optimization algorithms this week.

多目标优化算法

多目标优化的基本概念

  1. 目标函数:每个目标函数衡量了某个特定方面的性能或质量。
  2. 解空间:所有可能的解决方案组成的集合。
  3. 可行解:满足所有约束条件的解。
  4. Pareto 优势:如果一个解在至少一个目标上优于另一个解,并且在其他目标上不劣于该解,则称这个解对另一个解具有 Pareto 优势。
  5. Pareto 最优解:没有其他解能够对该解产生 Pareto 优势,这样的解称为 Pareto 最优解。
  6. Pareto 前沿:所有 Pareto 最优解在目标空间中的投影构成的集合。

实现步骤

  1. 初始化种群:随机生成初始解集。
  2. 评估:计算每个个体的目标函数值。
  3. 选择:根据某种策略选择下一代的父代个体。
  4. 遗传操作:执行交叉和变异操作以产生新个体。
  5. 更新:根据 Pareto 优势更新种群。
  6. 终止条件:达到预定迭代次数或其他终止标准后停止。

多目标优化算法常见类型及其实现

多目标优化算法的目标是找到一组 Pareto 最优解,而不是单一的最佳解。常见的多目标优化算法包括但不限于以下几种:

NSGA-II (Non-dominated Sorting Genetic Algorithm II)

  • 使用快速非支配排序来评价种群中的个体。
  • 利用拥挤距离保持种群多样性。
  • 通过遗传操作(如交叉和变异)生成新的后代。

代码如下:

import random
import numpy as np
from deap import base, creator, tools, algorithms

# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)

# 初始化工具箱
toolbox = base.Toolbox()

# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# 注册评估函数
def evaluateZDT1(individual):
    f1 = individual[0]
    g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
    f2 = g * (1 - (f1 / g) ** 0.5)
    return f1, f2

toolbox.register("evaluate", evaluateZDT1)

# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
toolbox.register("select", tools.selNSGA2)

# 主程序
def main():
    # 初始化种群
    pop = toolbox.population(n=90)
    hof = tools.ParetoFront()
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", np.mean, axis=0)
    stats.register("std", np.std, axis=0)
    stats.register("min", np.min, axis=0)
    stats.register("max", np.max, axis=0)

    # 进化过程
    pop, logbook = algorithms.eaMuPlusLambda(pop, toolbox, mu=len(pop), lambda_=len(pop),
                                             cxpb=0.9, mutpb=0.1, ngen=20,
                                             stats=stats, halloffame=hof, verbose=True)

    return pop, logbook, hof

if __name__ == "__main__":
    pop, log, hof = main()

这段代码的主要组成部分如下:

1.定义问题和解决方案的类型:
creator.create(“FitnessMin”, base.Fitness, weights=(-1.0, -1.0)) 定义了一个最小化多目标问题。
creator.create(“Individual”, list, fitness=creator.FitnessMin) 创建了一个列表类型的个体,其中包含一个FitnessMin实例作为其fitness属性。

2.注册属性初始化器:
toolbox.register(“attr_float”, random.random) 定义了一个随机浮点数生成器。
toolbox.register(“individual”, tools.initRepeat, creator.Individual, toolbox.attr_float, n=2) 注册了个体初始化器,每个个体有两个基因。
toolbox.register(“population”, tools.initRepeat, list, toolbox.individual) 注册了种群初始化器。

3.评估函数:
evaluateZDT1 是一个示例评估函数,用于评估ZDT1测试问题的个体。该函数返回两个目标值。

4.遗传操作:
cxSimulatedBinaryBounded 和 mutPolynomialBounded 分别用于执行模拟二进制交叉和多项式变异。
selNSGA2 用于执行快速非支配排序的选择操作。

5.主程序:
初始化种群和统计信息。
执行进化算法 eaMuPlusLambda。
输出最终的种群、日志和Pareto前沿。
结果如下:
在这里插入图片描述

SPEA2 (Strength Pareto Evolutionary Algorithm 2)

  • 引入个体适应度的概念,根据支配关系和密度估计进行排序。
  • 通过存档机制保持历史最优解。

代码如下:

import random
import numpy as np
from deap import base, creator, tools, algorithms

# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)

# 初始化工具箱
toolbox = base.Toolbox()

# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# 注册评估函数
def evaluateZDT1(individual):
    f1 = individual[0]
    g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
    f2 = g * (1 - (f1 / g) ** 0.5)
    return f1, f2

toolbox.register("evaluate", evaluateZDT1)

# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)

# SPEA2的选择函数
def selSPEA2(individuals, k):
    if len(individuals) < k:
        return individuals[:]
    
    # 计算支配关系
    dominance = tools.SPEADomination()
    dominated_counts = [sum(dominance(i, j) for j in individuals) for i in individuals]

    # 计算邻近度
    k_nearest = tools.knn(20)
    distances = [k_nearest(i, individuals) for i in individuals]

    # 创建存档
    archive = tools.ParetoFront()
    archive.update(individuals)

    # 选择个体
    selected = []
    while len(selected) < k:
        candidates = [i for i in range(len(individuals)) if i not in selected]
        if not candidates:
            break
        
        # 选择支配数量最少的个体
        min_dominated_count = min(dominated_counts[i] for i in candidates)
        candidates = [i for i in candidates if dominated_counts[i] == min_dominated_count]

        # 如果有多个个体,则选择距离最大的个体
        if len(candidates) > 1:
            max_distance = max(distances[i] for i in candidates)
            candidates = [i for i in candidates if distances[i] == max_distance]

        selected.append(candidates[0])
        
        # 更新支配计数和邻近度
        for i in candidates:
            dominated_counts[i] = sum(dominance(i, j) for j in individuals if j not in selected)
            distances[i] = k_nearest(i, [j for j in individuals if j not in selected])

    return [individuals[i] for i in selected], archive

# 主程序
def main():
    # 初始化种群
    pop = toolbox.population(n=90)
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", np.mean, axis=0)
    stats.register("std", np.std, axis=0)
    stats.register("min", np.min, axis=0)
    stats.register("max", np.max, axis=0)

    # 进化过程
    hof = tools.ParetoFront()
    logbook = tools.Logbook()
    record = stats.compile(pop)
    logbook.record(gen=0, evals=len(pop), **record)
    print(logbook.stream)

    for gen in range(1, 21):
        offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit

        # 选择操作
        pop, archive = selSPEA2(offspring + pop, len(pop))

        record = stats.compile(pop)
        logbook.record(gen=gen, evals=len(pop), **record)
        print(logbook.stream)

    return pop, logbook, hof

if __name__ == "__main__":
    pop, log, hof = main()

MOEA/D (Multi-Objective Evolutionary Algorithm based on Decomposition)

  • 将多目标问题分解为多个单目标子问题。
  • 每个个体负责优化一个子问题。
  • 使用局部搜索策略提高优化效率。

代码如下:

import random
import numpy as np
from deap import base, creator, tools, algorithms

# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)

# 初始化工具箱
toolbox = base.Toolbox()

# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# 注册评估函数
def evaluateZDT1(individual):
    f1 = individual[0]
    g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
    f2 = g * (1 - (f1 / g) ** 0.5)
    return f1, f2

toolbox.register("evaluate", evaluateZDT1)

# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)

# 分解多目标问题
def decompose_objectives(objective_values, weight_vector):
    weighted_sum = sum(w * obj for w, obj in zip(weight_vector, objective_values))
    return weighted_sum

# MOEA/D的选择函数
def selMOEAD(individuals, k, weights):
    if len(individuals) < k:
        return individuals[:]
    
    # 计算每个个体对应的子问题权重
    for ind in individuals:
        ind.f_weights = weights[ind.id]

    # 创建存档
    archive = tools.ParetoFront()
    archive.update(individuals)

    # 选择个体
    selected = []
    while len(selected) < k:
        candidates = [i for i in range(len(individuals)) if i not in selected]
        if not candidates:
            break

        # 选择支配数量最少的个体
        min_dominated_count = min(ind.fitness.values[0] for ind in individuals if ind.id not in selected)
        candidates = [ind for ind in individuals if ind.fitness.values[0] == min_dominated_count and ind.id not in selected]

        # 如果有多个个体,则随机选择一个
        if len(candidates) > 1:
            selected.append(random.choice(candidates))
        else:
            selected.append(candidates[0].id)

        # 更新支配计数
        for i in candidates:
            i.fitness.values = decompose_objectives(i.fitness.values, i.f_weights)

    return [individuals[i] for i in selected], archive

# 主程序
def main():
    # 初始化种群
    pop = toolbox.population(n=90)
    weights = tools.uniform_reference_points(nobj=2, p=len(pop))
    for i, ind in enumerate(pop):
        ind.id = i
        ind.fitness.values = decompose_objectives(toolbox.evaluate(ind), weights[i])
    
    # 进化过程
    hof = tools.ParetoFront()
    logbook = tools.Logbook()
    record = tools.Statistics(lambda ind: ind.fitness.values)
    record.register("avg", np.mean, axis=0)
    record.register("std", np.std, axis=0)
    record.register("min", np.min, axis=0)
    record.register("max", np.max, axis=0)

    record = record.compile(pop)
    logbook.record(gen=0, evals=len(pop), **record)
    print(logbook.stream)

    for gen in range(1, 21):
        offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = decompose_objectives(fit, weights[ind.id])

        # 选择操作
        pop, archive = selMOEAD(offspring + pop, len(pop), weights)

        record = record.compile(pop)
        logbook.record(gen=gen, evals=len(pop), **record)
        print(logbook.stream)

    return pop, logbook, hof

if __name__ == "__main__":
    pop, log, hof = main()

ε-MOEA (Epsilon-Dominance Based Multi-Objective Evolutionary Algorithm)

  • 引入 ε-支配关系,允许一定程度的目标函数值差距。
  • 可以更好地控制 Pareto 前沿的分布。

代码如下:

import random
import numpy as np
from deap import base, creator, tools, algorithms

# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)

# 初始化工具箱
toolbox = base.Toolbox()

# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# 注册评估函数
def evaluateZDT1(individual):
    f1 = individual[0]
    g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
    f2 = g * (1 - (f1 / g) ** 0.5)
    return f1, f2

toolbox.register("evaluate", evaluateZDT1)

# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)

# ε-支配关系
def epsilon_dominates(a, b, epsilons):
    dominates = True
    for ai, bi, eps in zip(a, b, epsilons):
        if ai > bi + eps:
            return False
        elif ai < bi - eps:
            dominates = False
    return dominates

# ε-MOEA的选择函数
def selEPSMOEA(individuals, k, epsilons):
    if len(individuals) < k:
        return individuals[:]
    
    # 创建存档
    archive = tools.ParetoFront()
    archive.update(individuals)

    # 选择个体
    selected = []
    while len(selected) < k:
        candidates = [i for i in range(len(individuals)) if i not in selected]
        if not candidates:
            break

        # 选择支配数量最少的个体
        min_dominated_count = min(sum(epsilon_dominates(ind.fitness.values, other.fitness.values, epsilons)
                                      for other in individuals if other.id != ind.id)
                                  for ind in individuals if ind.id not in selected)
        candidates = [ind for ind in individuals if ind.fitness.values[0] == min_dominated_count and ind.id not in selected]

        # 如果有多个个体,则随机选择一个
        if len(candidates) > 1:
            selected.append(random.choice(candidates))
        else:
            selected.append(candidates[0].id)

    return [individuals[i] for i in selected], archive

# 主程序
def main():
    # 初始化种群
    pop = toolbox.population(n=90)
    epsilons = [0.05, 0.05]  # 目标函数的ε值

    # 进化过程
    hof = tools.ParetoFront()
    logbook = tools.Logbook()
    record = tools.Statistics(lambda ind: ind.fitness.values)
    record.register("avg", np.mean, axis=0)
    record.register("std", np.std, axis=0)
    record.register("min", np.min, axis=0)
    record.register("max", np.max, axis=0)

    record = record.compile(pop)
    logbook.record(gen=0, evals=len(pop), **record)
    print(logbook.stream)

    for gen in range(1, 21):
        offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit

        # 选择操作
        pop, archive = selEPSMOEA(offspring + pop, len(pop), epsilons)

        record = record.compile(pop)
        logbook.record(gen=gen, evals=len(pop), **record)
        print(logbook.stream)

    return pop, logbook, hof

if __name__ == "__main__":
    pop, log, hof = main()

PESA-II (Performance-Scalarizing Evolutionary Algorithm)

  • 使用标量化方法将多目标问题转换为一系列单目标问题。
  • 通过动态调整权重向量来探索 Pareto 前沿的不同区域。

代码如下:

import random
import numpy as np
from deap import base, creator, tools, algorithms

# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)

# 初始化工具箱
toolbox = base.Toolbox()

# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# 注册评估函数
def evaluateZDT1(individual, weights):
    f1 = individual[0]
    g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
    f2 = g * (1 - (f1 / g) ** 0.5)
    scalarized_value = weights[0]*f1 + weights[1]*f2
    return scalarized_value,

toolbox.register("evaluate", evaluateZDT1)

# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)

# PESA-II的选择函数
def selPESA2(individuals, k, weights):
    if len(individuals) < k:
        return individuals[:]
    
    # 计算所有个体的适应度
    fitnesses = [toolbox.evaluate(ind, weights) for ind in individuals]
    
    # 按照适应度排序
    sorted_individuals = [ind for _, ind in sorted(zip(fitnesses, individuals))]
    
    # 选择前k个个体
    return sorted_individuals[:k]

# 主程序
def main():
    # 初始化种群
    pop = toolbox.population(n=90)
    num_weights = 2  # 两个目标
    num_weights_per_generation = 5  # 每代使用的权重数量
    
    # 进化过程
    hof = tools.ParetoFront()
    logbook = tools.Logbook()
    record = tools.Statistics(lambda ind: ind.fitness.values)
    record.register("avg", np.mean, axis=0)
    record.register("std", np.std, axis=0)
    record.register("min", np.min, axis=0)
    record.register("max", np.max, axis=0)

    record = record.compile(pop)
    logbook.record(gen=0, evals=len(pop), **record)
    print(logbook.stream)

    for gen in range(1, 21):
        # 生成权重向量
        weights = generate_weights(num_weights, num_weights_per_generation)
        
        offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = toolbox.map(lambda ind: toolbox.evaluate(ind, weights), invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit

        # 选择操作
        pop = selPESA2(offspring + pop, len(pop), weights)

        record = record.compile(pop)
        logbook.record(gen=gen, evals=len(pop), **record)
        print(logbook.stream)

    return pop, logbook, hof

# 生成权重向量
def generate_weights(num_objectives, num_weights):
    weights = []
    for _ in range(num_weights):
        w = [random.random() for _ in range(num_objectives)]
        w_sum = sum(w)
        w = [wi / w_sum for wi in w]  # 归一化
        weights.append(w)
    return weights

if __name__ == "__main__":
    pop, log, hof = main()

这些算法通常用于解决复杂问题,比如设计优化、资源分配等问题。每种算法都有其特点和适用场景,选择哪种算法取决于具体问题的需求。

一些常见的dos命令

“DOS命令”通常指的是在MS-DOS(Microsoft Disk Operating System)或者兼容的命令行界面(如Windows命令提示符cmd.exe)中使用的命令。

更换盘符: d:
查看当前目录下的文件及文件夹:dir
进入文件夹: cd 文件夹的名字
返回上一级目录:cd …
创建文件夹:mkdir/md 文件夹名
删除文件夹:rd 文件夹的名字
清屏:cls
退出: exit

总结

本周进一步学习了一些多目标优化算法的常见类型,下周将继续学习项目申报书中提到的动态优化GA模型等内容。

<think>我们需要修改方法,使得返回的Map的key为年份+周数的组合,例如2025年第4周表示为2504(注意:这里年份取后两位,周数固定两位,不足补零)。 但是注意:题目要求如2504表示2025年第4周,即年份取后两位(25)加上两位的周数(04),所以总共是4位数。 步骤: 1. 在分组的时候,我们仍然需要按周分组,但是为了后续构造key,我们还需要知道年份。不过注意,目标年份已经作为参数传入(targetYear),而且我们过滤的时候已经只取该年份的数据。 因此,周数对应的年份就是targetYear。 2. 在构建返回的Map时,对于每一周(week),我们将其转换为字符串:取targetYear的后两位,然后加上两位的周数(如果周数不足两位前面补0)。 3. 注意:周数是从1到53,所以当周数小于10时,需要补0。 4. 但是,我们也要注意,targetYear可能是一个四位数的年份(如2025),我们只需要后两位,即25。 5. 修改方法:将原来的key从周数(Integer)改为字符串(String)或者整数(Integer)?题目要求是2504,这是一个整数。但是注意,如果周数不足10,比如第4周,那么就是04,所以整个数字是2504。 但是,如果使用整数,那么04会变成4,所以2504实际上是2504(整数),而第10周就是2510,这样是可以的。但是,第4周表示为2504,整数就是2504,没有问题。所以我们可以使用整数作为key。 6. 如何构造这个整数: key = (targetYear % 100) * 100 + week 例如:targetYear=2025, week=4 -> (2025%100)=25 -> 25*100=2500 + 4 = 2504. targetYear=2025, week=12 -> 25*100+12=2512. 7. 但是注意:week的范围是1到53,所以最大是53,那么25*100+53=2553,这个整数是4位数,符合要求。 8. 因此,我们可以这样生成key。 9. 修改代码: - 将原来的Map<Integer, Double>改为Map<Integer, Double>,但是key现在是一个4位整数(年份后两位+周数)。 - 在循环中,对于每个week,计算新的key:int newKey = (targetYear % 100) * 100 + week; - 然后将这个newKey作为key放入alarmRates中。 10. 注意:如果targetYear是2000年以前,比如1999,那么后两位是99,那么第1周就是9901,这样也是4位数,没有问题。 11. 但是,如果targetYear是1900,那么后两位是00,那么第1周就是0001(即1),这样就会变成1位数?但是注意,我们计算的是整数,00*100+1=1。这样就会导致不同年份的周可能重复(比如2001年的第1周也是1)。所以这种方法有缺陷。 12. 重新考虑:题目要求如2504,但如果是2000年,那么应该是0004?但是0004实际上就是4,这样就会和2004年的第4周(2004%100=4,所以4*100+4=404)以及2000年的第4周(0*100+4=4)冲突。所以用整数表示会有问题。 13. 因此,我们使用字符串来表示:将年份后两位(两位,不足两位前面补0)和周数(两位,不足两位前面补0)拼接成字符串。但是题目要求是整数?题目说“2504表示2025年第4周”,这里2504是一个整数,但是实际上它也可以看作是一个字符串转换而来的整数。然而,当周数不足10时,我们需要补0,这样整数就会是四位。但是,如果年份后两位是00,那么第1周就是0001,转换成整数就是1,这样就会丢失前导零,导致位数不足。 14. 为了避免冲突,我们有两种选择: a) 使用字符串:例如"2504"表示2025年第4周,"0001"表示2000年第1周。 b) 使用整数,但用更大的位数:比如用6位数,前四位是年份,后两位是周数。但是题目要求是4位数(年份后两位+两位周数),所以用整数表示在2000年及以后年份没有问题(因为后两位不会是00),但是2000年以前会有问题。 15. 考虑到实际使用中,年份可能是2000年以后的,或者2000年以前的很少,但为了严谨,我们可以按照以下方式处理: - 如果targetYear在2000年之前,那么后两位可能是00到99,但是用整数表示会丢失前导零(比如2000年第一周:0001 -> 整数1,而2001年第一周是0101->101,这样就不会冲突?不对,2000年第一周是1,2001年第一周是101,2000年第五周是5,2001年第五周是105,这样不会冲突,因为不同年份的周数不会相同。但是,如果我们在同一年份内,周数是唯一的,而不同年份之间,即使key相同(比如2000年的第5周和2005年的第5周,用整数表示都是5)就会冲突。所以整数表示法不可行。 16. 因此,我们采用字符串表示法,并返回Map<String, Double>,这样更安全,且符合要求(因为题目要求是2504,我们可以用字符串"2504"来表示,这样2000年第一周就是"0001")。 17. 修改: 将返回类型改为Map<String, Double> 在循环中,构造key:String key = String.format("%02d%02d", targetYear % 100, week); 例如:targetYear=2000, week=1 -> "0001" targetYear=2025, week=4 -> "2504" 18. 但是注意,如果周数超过99?不会,因为周数最大53,所以两位足够。 19. 因此,修改后的代码: 20. 另外,分组的时候我们不需要改变,因为分组还是按周分组,然后我们只对当前年份的数据进行处理。 21. 修改后的方法如下:</think>### 修改为年份+周数组合键的实现 #### 修改后的方法实现 ```java import java.util.*; import java.time.temporal.WeekFields; import java.util.stream.Collectors; public class AlarmRateCalculator { public static Map<Integer, Double> calculateAlarmRates(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 1. 按周分组统计报警总数 Map<Integer, Integer> weeklyAlarms = alarmDailyStats.stream() .filter(po -> po.getStatDate().getYear() == targetYear) .collect(Collectors.groupingBy( po -> po.getStatDate().get(WeekFields.ISO.weekOfWeekBasedYear()), Collectors.summingInt(AlarmDailyStatPO::getTotalCount) )); // 2. 计算每周报警率,使用年份+周数作为键 Map<Integer, Double> alarmRates = new TreeMap<>(); for (int week = 1; week <= currentWeek; week++) { // 获取该周实际工作小时数 int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); // 获取该周报警总数 int alarmCount = weeklyAlarms.getOrDefault(week, 0); // 计算报警率(避免除零错误) double rate = (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; // 生成组合键:年份后两位 + 周数(如2504) int yearWeekKey = (targetYear % 100) * 100 + week; alarmRates.put(yearWeekKey, rate); } return alarmRates; } } ``` #### 增强版(支持跨年周处理) ```java public static Map<Integer, Double> calculateAlarmRatesEnhanced(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 创建按年份+周数分组的映射 Map<Integer, Integer> yearWeekAlarms = new HashMap<>(); for (AlarmDailyStatPO po : alarmDailyStats) { LocalDateTime date = po.getStatDate(); int year = date.getYear(); int week = date.get(WeekFields.ISO.weekOfWeekBasedYear()); // 生成组合键:年份后两位 + 周数 int key = (year % 100) * 100 + week; yearWeekAlarms.put(key, yearWeekAlarms.getOrDefault(key, 0) + po.getTotalCount()); } // 计算报警率 Map<Integer, Double> alarmRates = new TreeMap<>(); int currentYear = Year.now().getValue(); // 获取当前年份 for (int week = 1; week <= currentWeek; week++) { int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); int key = (targetYear % 100) * 100 + week; // 处理跨年周(如2023年第53周可能属于2024年) if (!yearWeekAlarms.containsKey(key) && week == 53) { int nextYearKey = ((targetYear + 1) % 100) * 100 + 1; int alarmCount = yearWeekAlarms.getOrDefault(nextYearKey, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } else { int alarmCount = yearWeekAlarms.getOrDefault(key, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } } return alarmRates; } private static double calculateRate(int alarmCount, int workHours, int employeeCount) { return (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; } ``` ### 键值转换工具类 ```java public class YearWeekUtils { /** * 从组合键中提取年份 * @param yearWeekKey 年份+周数组合键(如2504) * @return 完整年份(如2025) */ public static int extractYear(int yearWeekKey) { int century = Year.now().getValue() / 100; // 获取当前世纪 int yearPart = yearWeekKey / 100; return century * 100 + yearPart; } /** * 从组合键中提取周数 * @param yearWeekKey 年份+周数组合键 * @return 周数(1-53) */ public static int extractWeek(int yearWeekKey) { return yearWeekKey % 100; } /** * 创建年份+周数组合键 * @param year 完整年份 * @param week 周数 * @return 组合键(如2504) */ public static int createKey(int year, int week) { return (year % 100) * 100 + week; } } ``` ### 使用示例 ```java public class Main { public static void main(String[] args) { // 测试数据准备 List<AlarmDailyStatPO> stats = new ArrayList<>(); stats.add(createPO(2025, 1, 10)); // 2025年第1周 stats.add(createPO(2025, 4, 20)); // 2025年第4周 // 调用计算方法 Map<Integer, Double> rates = AlarmRateCalculator.calculateAlarmRates( 2025, stats, 5, 100); // 结果解析 for (Map.Entry<Integer, Double> entry : rates.entrySet()) { int key = entry.getKey(); int year = YearWeekUtils.extractYear(key); int week = YearWeekUtils.extractWeek(key); System.out.printf("周键: %d → 年份: %d, 周数: %d, 报警率: %.2f%%%n", key, year, week, entry.getValue()); } /* 输出示例: 周键: 2501 → 年份: 2025, 周数: 1, 报警率: 0.10% 周键: 2502 → 年份: 2025, 周数: 2, 报警率: 0.00% 周键: 2503 → 年份: 2025, 周数: 3, 报警率: 0.00% 周键: 2504 → 年份: 2025, 周数: 4, 报警率: 0.20% 周键: 2505 → 年份: 2025, 周数: 5, 报警率: 0.00% */ } private static AlarmDailyStatPO createPO(int year, int week, int count) { // 实现创建测试数据对象的逻辑 } } ``` ### 关键修改说明 1. **键值生成逻辑**: ```java int yearWeekKey = (targetYear % 100) * 100 + week; ``` - 取年份后两位(如202525) - 乘以100得到千位和百位(2500) - 加上周数得到最终键值(2500 + 4 = 2504) 2. **跨年周处理**: - 第53周可能属于下一年(ISO周标准) - 特殊处理:当周数为53时检查下一年第1周的数据 3. **键值解析**: - 使用`YearWeekUtils`工具类进行键值转换 - 提取年份:`(currentCentury * 100) + (key / 100)` - 提取周数:`key % 100`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值