程序员老鬼

知乎高问:为什么有些人不愿意在女领导手下工作?

刚看到个贴子在问:为啥有些人不愿在女领导手下干活。底下不少网友一句话概括:事多、情绪化。

Image

我觉得这事吧,得分开看。贴子说的是“女领导”,网友骂的是“差劲的领导”。有些人确实遇到过管得特别细、爱情绪发泄的女上司,于是直接给整成了“性别问题”,省事,一杆子打翻一船人。、

从我的角度看,领导好不好相处,跟性别真没啥必然关系。事多不多,看的是边界感;情绪稳不稳,看的是职业素养。

遇到不靠谱的,上男下女一样糟心。

面试题:进店却未进行过交易的顾客

先把问题说清楚一点

我们抽象一下数据,一般会有两类记录:

  • 进店或访问记录:谁在什么时候来过,比如 visitLog
  • 交易记录:谁在什么时候买过东西,比如 orderLog

从算法角度看,其实就是:

已访问用户集合 - 已交易用户集合 = 只来过但没下单的那波人

用 Java 说就是两个集合做个差集。

先整一版最粗暴、但也最好理解的写法。假设我们已经从数据库查出来两个列表:

import java.util.*;

publicclassCustomerAnalyzer{

/**
     * 找出进店但从未交易过的顾客
     */

publicstatic Set<Long> findVisitedButNoOrder(
            List<Long> visitedCustomerIds,
            List<Long> orderedCustomerIds)
{

// 1. 先把所有访问过的客户丢进一个 Set,去重也顺便做了
        Set<Long> visitedSet = new HashSet<>(visitedCustomerIds);

// 2. 再把下过单的一个个从里面删掉
for (Long userId : orderedCustomerIds) {
            visitedSet.remove(userId);
        }

// 3. 剩下的就是:来过但没买
return visitedSet;
    }

publicstaticvoidmain(String[] args){
        List<Long> visits = Arrays.asList(1L, 2L, 2L, 3L, 4L);
        List<Long> orders = Arrays.asList(2L, 4L);

        Set<Long> result = findVisitedButNoOrder(visits, orders);
        System.out.println(result); // 输出可能是 [1, 3]
    }
}

这个思路特别朴素:

  • 时间复杂度:O(N + M),N 是访问记录条数,M 是交易记录条数
  • 空间复杂度:O(K),K 是访问过的不同用户数量

大部分业务场景,这个就够用了。

你看真实系统里,肯定不止一个纯粹的 userId,对吧,一般会长这样:

classVisitRecord{
    Long userId;
    Date visitTime;
    String channel; // app / H5 / 小程序之类
}

classOrderRecord{
    Long userId;
    Long orderId;
    Date payTime;
    String status; // PAID / CANCELLED / REFUND...
}

这时候有几个容易忽略的小点:

  1. “没交易”到底算谁?

  • 是“从来没下过单”?(全历史无订单)
  • 还是“本次活动期间没下单”? 这个要看你时间维度怎么切。
  • 订单是不是只算支付成功的?

    • 已取消的订单要不要当“有交易”?
    • 待支付的要不要算?

    所以 Java 算法那块,一般就会多加点过滤条件。比如我们只认支付成功、只看某一天的数据:

    publicstatic Set<Long> findVisitedButNoPaidOrder(
            List<VisitRecord> visitRecords,
            List<OrderRecord> orderRecords,
            Date start,
            Date end)
    {

    // 1. 过滤出时间范围内访问过的用户
        Set<Long> visitedSet = new HashSet<>();
    for (VisitRecord v : visitRecords) {
    if (!v.visitTime.before(start) && !v.visitTime.after(end)) {
                visitedSet.add(v.userId);
            }
        }

    // 2. 再过滤出时间范围内支付成功的用户
        Set<Long> paidSet = new HashSet<>();
    for (OrderRecord o : orderRecords) {
    if (!o.payTime.before(start) && !o.payTime.after(end)
                    && "PAID".equals(o.status)) {
                paidSet.add(o.userId);
            }
        }

    // 3. 访问集合减去支付集合
        visitedSet.removeAll(paidSet);

    return visitedSet;
    }

    这里用了个 removeAll,其实就是刚才那句循环 remove 的批量版,看着更干净一点。

    有的同学喜欢函数式一点的写法,也可以这样写:

    import java.util.stream.Collectors;

    Set<Long> visitedSet = visitRecords.stream()
            .filter(v -> !v.visitTime.before(start) && !v.visitTime.after(end))
            .map(v -> v.userId)
            .collect(Collectors.toSet());

    Set<Long> paidSet = orderRecords.stream()
            .filter(o -> !o.payTime.before(start) && !o.payTime.after(end))
            .filter(o -> "PAID".equals(o.status))
            .map(o -> o.userId)
            .collect(Collectors.toSet());

    visitedSet.removeAll(paidSet);

    本质还是集合差集,只是换个写法,看你团队的习惯。

    如果你这不是小项目,而是那种日活几百万的电商,那访问日志一天几千万行很正常,这时候在 Java 里全拉出来算,就有点顶不住了。

    一般会这么干:

    • 粗筛用数据库或数仓: 比如先在 SQL 里拉出“今天访问过的用户”和“今天有支付订单的用户”,做完去重再给 Java
    • Java 这边只负责最后一点点逻辑,比如多条件过滤、打标签等

    但不管放哪一层,核心思路还是:

    先找到 visit 用户集合,再减掉 trade 用户集合

    这一点是不会变的。

    运营经常会问你一个问题: “我想看那种,经常来、但从来不买的用户,你能帮我筛出来不?”

    这就变成两层条件了:

    • 条件一:进店次数 ≥ N(比如大于等于 3 次)
    • 条件二:交易次数 = 0

    Java 里可以这样搞,用 Map<Long, Integer> 先数次数:

    publicstatic Set<Long> findHighVisitNoOrder(
            List<VisitRecord> visitRecords,
            List<OrderRecord> orderRecords,
    int minVisitCount)
    {

    // 1. 统计每个用户的访问次数
        Map<Long, Integer> visitCountMap = new HashMap<>();
    for (VisitRecord v : visitRecords) {
            visitCountMap.merge(v.userId, 1, Integer::sum);
        }

    // 2. 标记有过支付订单的用户
        Set<Long> paidSet = new HashSet<>();
    for (OrderRecord o : orderRecords) {
    if ("PAID".equals(o.status)) {
                paidSet.add(o.userId);
            }
        }

    // 3. 过滤:访问次数达标,且从未支付
        Set<Long> result = new HashSet<>();
    for (Map.Entry<Long, Integer> entry : visitCountMap.entrySet()) {
            Long userId = entry.getKey();
            Integer count = entry.getValue();
    if (count >= minVisitCount && !paidSet.contains(userId)) {
                result.add(userId);
            }
        }

    return result;
    }

    这个就已经非常贴近真实业务了:你不仅知道“谁从没买过”,还知道“谁老来不买”,后面可以精准推优惠券、电话回访之类的。

    -END-

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

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