程序员老鬼

我们组年终奖总共23.8w,小组就我和组长两个人,我只拿到 1.67w,组长却拿了22.13w

刚看到个贴子,说一网友吐槽:他们组年终奖一共 23.8 万,就俩人,他拿 1.67 万,组长拿了 22.13 万,直接气炸。

Image

我觉得这事吧,先承认一句:情绪正常,谁看了不难受。网友们的回复我看了看,有的说“领导背锅多拿点很正常”,也有人骂公司黑心。怎么说呢,这两种说法都对一半。

我的看法是:先冷静,把“委屈”换成三个动作——问清规则、评估成长、决定去留。规则说不明白、成长看不到,那就别死磕,把时间留给更值得的地方。

面试题:按月统计订单数与顾客数

昨天晚上快下班的时候,产品突然甩过来一句:”按月看看订单量和顾客数呗,就很普通的统计。“听起来很普通对吧,但真落到代码上,很多同学要么写一坨 SQL,要么在 Java 里绕晕自己。

我就按这个小需求,顺手整理成一篇小记,顺带把思路和代码都讲清楚,用的就是 Java。

题目叫“按月统计订单数与顾客数”,翻译一下就是:

  • 有一堆订单记录,每条起码有:订单 id、顾客 id、下单时间

  • 需要按“年-月”这一维度,把数据分组

  • 对每个月算两个东西:

    • 这个月一共多少笔订单
    • 这个月一共有多少不同的顾客(去重后的顾客数)

重点是“顾客数”要去重,同一个人下 10 单,也只算 1 个顾客。

数据可能来自数据库,也可能是接口查出来已经在内存里的 List,这里就按“内存 List 运算”来写,思路更通用。

想象一下你桌上放着一摞纸,每张纸是一笔订单,你要做一个“按月汇总”的小本子:

  1. 从头到尾把订单一张张翻过去

  2. 对于每一张:

  • 看它的下单时间,拿到对应的“年-月”,比如 2024-01
  • 在小本子里找到这一个月那一页,没有就新建一页
  • 这一页上的“订单数 +1”
  • 把这个订单的顾客 id 记进一个“顾客集合”里(Set,天然去重)
  • 等所有订单扫完,再把每一页上的“顾客集合.size()”取出来,就是这个月的顾客数

  • 所以伪代码结构就是:

    • 用一个 Map<YearMonth, Stats> 做中间结果
    • Stats 里有:orderCount 和 Set<Long> customerIds
    • 单次扫描订单列表,时间复杂度 O(n)

    这个思路基本可以直接翻译成 Java 代码。

    先随便定义一个订单类,真实项目里你按自己实体来就行:

    import java.time.LocalDateTime;

    publicclassOrder{
    private Long orderId;
    private Long customerId;
    private LocalDateTime createTime;

    publicOrder(Long orderId, Long customerId, LocalDateTime createTime){
    this.orderId = orderId;
    this.customerId = customerId;
    this.createTime = createTime;
        }

    public Long getOrderId(){
    return orderId;
        }

    public Long getCustomerId(){
    return customerId;
        }

    public LocalDateTime getCreateTime(){
    return createTime;
        }
    }

    中间统计用一个小类装一下:

    import java.util.HashSet;
    import java.util.Set;

    classMonthlyStatsTmp{
    int orderCount;
        Set<Long> customerIds = new HashSet<>();
    }

    对外返回的结构可以稍微体面一点,比如带上年月、订单数、顾客数:

    import java.time.YearMonth;

    publicclassMonthlyStats{
    private YearMonth month;
    privateint orderCount;
    privateint customerCount;

    publicMonthlyStats(YearMonth month, int orderCount, int customerCount){
    this.month = month;
    this.orderCount = orderCount;
    this.customerCount = customerCount;
        }

    public YearMonth getMonth(){
    return month;
        }

    publicintgetOrderCount(){
    return orderCount;
        }

    publicintgetCustomerCount(){
    return customerCount;
        }

    @Override
    public String toString(){
    return month + " => orders=" + orderCount + ", customers=" + customerCount;
        }
    }

    核心方法来了,一眼能看懂那种:

    import java.time.YearMonth;
    import java.util.*;

    publicclassOrderStatsService{

    /**
         * 按“年-月”统计订单数和去重顾客数
         */

    publicstatic List<MonthlyStats> statByMonth(List<Order> orders){
    // 先用 HashMap 做中间累加
            Map<YearMonth, MonthlyStatsTmp> tmpMap = new HashMap<>();

    for (Order order : orders) {
    if (order == null || order.getCreateTime() == null) {
    continue; // 简单防御
                }

                YearMonth ym = YearMonth.from(order.getCreateTime());

                MonthlyStatsTmp stats = tmpMap.computeIfAbsent(ym, k -> new MonthlyStatsTmp());
                stats.orderCount++;
                stats.customerIds.add(order.getCustomerId());
            }

    // 转成对外的结果,并按时间排序一下
            List<MonthlyStats> result = new ArrayList<>();
            tmpMap.entrySet().stream()
                    .sorted(Map.Entry.comparingByKey()) // YearMonth 自带排序
                    .forEach(e -> {
                        YearMonth ym = e.getKey();
                        MonthlyStatsTmp s = e.getValue();
                        result.add(new MonthlyStats(ym, s.orderCount, s.customerIds.size()));
                    });

    return result;
        }
    }

    如果你是用老的 java.util.Date 或 Timestamp 存时间,也没关系,转换成 LocalDateTime 或直接手工取 year 和 month 都行,比如:

    // 假设有一个 Date createTime
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(createTime);
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH) + 1; // 注意从 0 开始
    String ymKey = year + "-" + (month < 10 ? "0" + month : month);
    // 然后 Map<String, MonthlyStatsTmp> 也一样能做

    真实项目里,如果数据量特别大,或者本来就要查数据库,其实可以让数据库先按月 group by 一层,再在 Java 里做一点补充处理;上面这种写法更适合“订单已经在内存里了,我顺手统计一下”的那种场景。

    -END-

    我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

    最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404