知乎高问:为什么有些人不愿意在女领导手下工作?
刚看到个贴子在问:为啥有些人不愿在女领导手下干活。底下不少网友一句话概括:事多、情绪化。
我觉得这事吧,得分开看。贴子说的是“女领导”,网友骂的是“差劲的领导”。有些人确实遇到过管得特别细、爱情绪发泄的女上司,于是直接给整成了“性别问题”,省事,一杆子打翻一船人。、
从我的角度看,领导好不好相处,跟性别真没啥必然关系。事多不多,看的是边界感;情绪稳不稳,看的是职业素养。
遇到不靠谱的,上男下女一样糟心。
面试题:进店却未进行过交易的顾客
先把问题说清楚一点
我们抽象一下数据,一般会有两类记录:
进店或访问记录:谁在什么时候来过,比如 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...
}
这时候有几个容易忽略的小点:
“没交易”到底算谁?
是“从来没下过单”?(全历史无订单) 还是“本次活动期间没下单”? 这个要看你时间维度怎么切。
订单是不是只算支付成功的?
已取消的订单要不要当“有交易”? 待支付的要不要算?
所以 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