按天统计数据时,补全天数

根据业务需求查出所需数据:
$data = DB::table('course_study_record')
    ->whereBetween('created_at',[$start_time,$end_time])
    ->groupBy('date')
    ->select([DB::raw('FROM_UNIXTIME(created_at,"%Y-%m-%d") as date'),DB::raw('COUNT(id) as value')])
    ->distinct()
    ->get();
array:6 [
  0 => array:2 [
    "date" => "2019-06-14"
    "value" => 6
  ]
  1 => array:2 [
    "date" => "2019-06-15"
    "value" => 5
  ]
  2 => array:2 [
    "date" => "2019-06-16"
    "value" => 1
  ]
  3 => array:2 [
    "date" => "2019-06-17"
    "value" => 2
  ]
  4 => array:2 [
    "date" => "2019-06-18"
    "value" => 1
  ]
  5 => array:2 [
    "date" => "2019-06-19"
    "value" => 1
  ]
]
/**
 * 按天统计时补全天数并返回格式化数据结构
 * @param $day  需要统计的一共天数
 * @param $endtime 结束时间
 * @param $arr  要补全的数组
 * @return array
 */
function pad_day($day,$endtime,$arr){
    //今天结束时间
    $endToday=mktime(0,0,0,date('m'),date('d')+1,date('Y'))-1;

    $diff_days= $endToday-$endtime;

    $diff_days=ceil($diff_days/86400) - 1;

    $new = [];//用来存放格式化后的新数组
    if(!empty($arr)){
        foreach ($arr as $v){
            $new[$v['date']]['date'] = $v['date'];
            $new[$v['date']]['num'] =  $v['value'];
        }
    }

    for ($i = 0; $i < $day; $i++) {
        $diff_day=$diff_days+$i;
        $date = date('Y-m-d', strtotime('-' . $diff_day . 'day'));
        if(!array_key_exists($date,$new)){
            $new[$date] = ['date'=>$date,'num'=>0];
        }
    }

    ksort($new);
    return array_values($new);
}
<think>嗯,用户现在有一个关于Java 8的问题,需要根据学生每周的上课长按天数分类统计人数。首先,我得仔细理解他的需求。他提到的学生对象有学号、姓名、班级、每周上课长,单位是小。需要将这些长转换成天数,比如1、2、3、4、4以上,然后统计每个天数区间的人数。 首先,我得考虑如何将小转换为天数。这里可能需要一个假设,比如一多少小。用户没有说明,所以可能需要默认一个值,比如一8小。或者,可能需要询问用户,但根据上下文,用户可能希望自己处理这个转换,或者可能隐含了每固定课。比如,可能每上课8小,那么总小数除以8就是天数,余数的话可能进一法处理,比如超过部分算一。例如,10小就是1(8小)加2小,算2?或者可能直接取整数部分?这点需要明确。不过用户可能希望直接按小转换为天数,比如用Math.ceil(hours / 8.0)来计算天数,向上取整,这样即使超过部分不足一也算一。 接下来,用户需要按天数分组统计人数。天数分为1、2、3、4、4以上。这里的“4以上”可能需要特别注意,因为如果超过4,比如5,应该归类到4以上这个组里。因此,分组逻辑应该是:如果天数小于等于4,按实际天数分组,否则归为4以上。 在Java 8中,可以使用Stream API的Collectors.groupingBy来进行分组。但是,这里的分组条件不是直接的属性,而是需要根据课计算出的天数,并且需要处理大于4的情况。因此,自定义一个分类函数是关键。 可能的步骤是: 1. 将学生列表转换为流。 2. 使用map或者直接处理每个学生,计算出对应的天数。 3. 根据天数进行分类,其中天数超过4的归为“4以上”。 4. 使用groupingBy分类器,并统计每个组的数量。 但是,如何将天数转换为对应的标签呢?比如,1对应“1”,4以上对应“4以上”。这候,分类函数应该返回一个字符串作为分组的键。例如,对于每个学生,计算天数后,判断天数是否大于4,如果是,则返回“4以上”,否则返回天数 + ""。 然后,使用groupingBy按照这个字符串键进行分组,再用Collectors.counting()统计每个组的数量。 需要注意的是,可能需要处理天数为0的情况,但根据用户描述,可能学生至少有一定的上课长,所以可能不需要考虑。如果hours可能为0,则需要处理,但用户的问题中没有提到,暂可以忽略。 举个例子,比如一个学生每周上课10小,按8小,那么Math.ceil(10/8.0)=2,归为2。另一个学生有35小,35/8=4.375,向上取整为5,所以归为4以上。 接下来,代码的大致结构可能是这样的: Map<String, Long> result = students.stream() .collect(Collectors.groupingBy( student -> { int days = (int) Math.ceil(student.getWeeklyHours() / 8.0); if (days > 4) { return "4以上"; } else { return days + ""; } }, Collectors.counting() )); 但是,这样可能会有一个问题,就是分组的键可能包括所有的天数,比如1到4,以及4以上。但用户希望的结果是按照1、2、3、4、4以上这样的顺序,并且确保每个组都存在,即使数量为0。例如,可能没有学生属于3的情况,但结果中仍需要显示3:0人。 这候,上面的代码可能不会包含所有分组,因为如果某个天数区间没有学生,则该键不会出现在Map中。所以,可能需要初始化一个包含所有可能键的Map,然后合并统计结果。或者,在最后处理结果补全所有可能的键,并设置默认值0。 因此,处理步骤可能需要: 1. 创建一个包含所有可能分组的列表:1、2、3、4、4以上。 2. 使用上述的groupingBy方法得到统计结果。 3. 遍历所有可能的分组,从结果Map中获取对应的数量,如果没有则设为0。 但这样可能需要额外的处理。或者,可以在分组的候,强制将天数映射到这五个类别,确保每个学生都被正确分类,然后收集结果,即使某些类别没有数据,也会出现在Map中。不过,根据Collectors.groupingBy的行为,如果某个分类没有元素,则不会出现在Map中。因此,为了确保所有五个分类都出现在结果中,即使用户的数据中没有某些天数的情况,也需要在后续处理中补充这些键。 例如,可以先创建一个包含所有可能键的列表,然后遍历这个列表,从结果Map中获取对应的值,若不存在则为0。 这可能在代码中需要额外的步骤: List<String> categories = Arrays.asList("1", "2", "3", "4", "4以上"); Map<String, Long> finalResult = new LinkedHashMap<>(); categories.forEach(category -> finalResult.put(category, result.getOrDefault(category, 0L))); 这样,可以保证结果包含所有五个分类,并按顺序排列。 此外,用户可能需要结果按照1、2、3、4、4以上的顺序输出,因此使用LinkedHashMap来保持顺序。 现在,将以上思考整合成代码: 首先,计算每个学生的天数,分类到对应的组,统计数量,然后补全所有可能的分类。 代码示例: import java.util.*; import java.util.stream.Collectors; public class StudentStatistics { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student("001", 8), // 1 new Student("002", 16), // 2 new Student("003", 25), // Math.ceil(25/8)=4 new Student("004", 35), // Math.ceil(35/8)=5 → 4以上 new Student("005", 40) // 5 → 4以上 ); Map<String, Long> result = students.stream() .collect(Collectors.groupingBy( student -> { double hoursPerDay = 8.0; int days = (int) Math.ceil(student.getWeeklyHours() / hoursPerDay); if (days > 4) { return "4以上"; } else { return days + ""; } }, Collectors.counting() )); // 确保所有分类都存在 List<String> categories = Arrays.asList("1", "2", "3", "4", "4以上"); Map<String, Long> finalResult = new LinkedHashMap<>(); for (String category : categories) { finalResult.put(category, result.getOrDefault(category, 0L)); } System.out.println(finalResult); } static class Student { private String id; private double weeklyHours; public Student(String id, double weeklyHours) { this.id = id; this.weeklyHours = weeklyHours; } public double getWeeklyHours() { return weeklyHours; } } } 运行这段代码,输出应该是: {1=1, 2=1, 3=0, 4=1, 4以上=2} 这里假设有一个学生每周25小,即25/8=3.125,向上取整为4,所以属于4。另一个学生35小是4.375,向上取整为5,属于4以上。 不过,用户可能需要确认小转换为天数的方式是否正确。例如,是否应该使用Math.ceil,或者是否需要四舍五入,或者直接截断。比如,如果用户希望每8小,超过部分不足8小也算一,则必须使用Math.ceil。例如,9小就是2。但如果用户希望只有满8小才算一,超过部分不足的不算,则用除法后取整数部分。例如,9小则为1。这一点需要根据用户的需求来定,但用户的问题中没有明确说明,所以可能需要假设,或者给出注释说明。 在回答中,应该说明这个转换方式,并建议根据实际情况调整小天数的计算逻辑。 此外,可能还需要考虑学生对象的getter方法是否正确,比如是否有getWeeklyHours()方法。在示例代码中,我创建了一个Student类,包含该属性,并提供了getter方法。 总结,解决这个问题的步骤是: 1. 将学生列表转换为流。 2. 对每个学生计算每周课对应的天数,按8小计算,向上取整。 3. 根据天数进行分类,超过4的归为“4以上”。 4. 使用groupingBy和counting统计每个分类的数量。 5. 补全所有可能的分组,确保结果包含所有五个分类,即使数量为0。 最后,展示代码示例,并说明需要根据实际情况调整转换逻辑,比如每的课数可能不是8小,这可以修改hoursPerDay变量。</think>以下是使用 Java 8 Stream API 实现的分步解决方案: ```java // 1. 定义学生类 class Student { private String id; private String name; private String className; private double weeklyHours; // 构造函数/getters/setters } // 2. 核心统计逻辑 public Map<String, Long> classifyStudents(List<Student> students) { final double HOURS_PER_DAY = 8.0; // 根据实际需求调整每日课数 // 3. 创建所有可能的分类标签 List<String> categories = Arrays.asList("1", "2", "3", "4", "4以上"); // 4. 使用 Stream 处理数据 Map<String, Long> result = students.stream() .collect(Collectors.groupingBy( student -> { // 5. 计算等效天数(向上取整) int days = (int) Math.ceil(student.getWeeklyHours() / HOURS_PER_DAY); // 6. 分类逻辑 return (days > 4) ? "4以上" : days + ""; }, Collectors.counting() )); // 7. 补全缺失分类(保证所有标签都存在) return categories.stream() .collect(Collectors.toMap( category -> category, category -> result.getOrDefault(category, 0L), (oldVal, newVal) -> oldVal, LinkedHashMap::new )); } ``` **执行示例:** ```java List<Student> students = Arrays.asList( new Student("S001", 6), // 1 new Student("S002", 16), // 2 new Student("S003", 25), // 4(25/8=3.125 → 4) new Student("S004", 35), // 5 → 4以上 new Student("S005", 40) // 5 → 4以上 ); System.out.println(classifyStudents(students)); ``` **输出结果:** ```json { "1": 1, "2": 1, "3": 0, "4": 1, "4以上": 2 } ``` **关键优化点解释:** 1. **数学计算优化** - 使用 $days = \lceil \frac{weeklyHours}{HOURS\_PER\_DAY} \rceil$ 公式计算等效天数 - `Math.ceil()` 确保不足整的部分按一计算 2. **流式处理优势** - 单次遍历即可完成分类统计 - 并行处理能力(`.parallelStream()` 可轻松实现) 3. **防御性编程** - 显式定义 `HOURS_PER_DAY` 常量方便维护 - 最终结果使用 `LinkedHashMap` 保持分类顺序 **扩展建议:** - 若需要动态调整分类区间,可修改分类函数: ```java student -> { int days = (int) Math.ceil(...); if (days >= 5) return "5及以上"; else if (days >= 3) return "3-4"; // 其他自定义区间... } ``` - 添加空值检查:`Optional.ofNullable(students).orElseGet(ArrayList::new)` 该方案间复杂度为 $O(n)$,空间复杂度 $O(1)$(固定5个分类),适合处理大规模数据集合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值