程序员老鬼

某字节员工直言:做程序员久了,发现大家都有个共同之处,就是家庭条件真的都不太好,不绝对,但是不少人都是这种情况

刚刷到个贴子,说字节一位程序员感慨:做这行久了,发现身边同事家庭条件普遍一般,没啥富二代。

Image

挺现实的,编程这种工作,本质是靠时间和脑力去换钱,门槛靠学习堆上去,不像一些资源型行业,家里要有背景才能起步。换句话说,家境普通的人,更愿意也更有动力通过技术改变命运。

从大环境看,程序员算是“努力能见效”的典型行业,所以吸引了大批出身普通、想靠自己翻盘的人。这一点挺值得尊敬。【备注:文末可领最新资料】

算法题:2020年最后一次登录

昨天晚上十一点多,在公司楼下便利店等热关东煮,手机又跳出一道面试题,脑子一激灵:哎,就是那个…“2020年最后一次登录”。你们也碰到过对吧?看着简单,坑可不少,先把场景说人话:给一堆登录日志,每条像userId, loginTime,时间可能乱序、可能带时区、同一天多次也可能。要的是——每个用户在“2020 年”里的“最后一次”登录。超过 2020 就别算,没登过的也别输出。嗯,就这么个意思。

说下思路哈,我当时一边喝豆乳一边想:别整花活,线性走一遍就够。核心就是三个动作:先“过滤”出 2020 年的,接着“比较”同一用户最大的时间点,最后“输出”每个用户的那条。注意“2020 年”的判定要按业务时区来,不然 UTC+0 和 GMT+8 一天能差八小时,跨年边界会飘。我们组那个小李就被这个坑过一次…算了不黑他了。

还有几个细节,别忽略: 1)时间乱序无所谓,我们是在线更新最大值; 2)同一时间多条,按精确到毫秒比较即可,真撞了就保留其一; 3)日志里有各种格式?最好约定 ISO-8601(2020-12-31T23:59:59+08:00),否则就给多套 DateTimeFormatter 逐个尝试; 4)内存别怕,Map 一把梭;超大数据就分片/流式聚合。

我把代码贴下,用 Java 8+ 的 ZonedDateTime,默认按业务时区(举例 Asia/Shanghai),如果传入的字符串自带偏移量,会以字符串为准,再转到业务时区判断是不是 2020。这样跨时区的边界也稳了。

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;

publicclassLastLogin2020{
// 业务时区,可配置
privatestaticfinal ZoneId BIZ_ZONE = ZoneId.of("Asia/Shanghai");
// 常见格式:ISO-8601优先,其次无偏移的本地时间
privatestaticfinal List<DateTimeFormatter> Fmts = Arrays.asList(
            DateTimeFormatter.ISO_ZONED_DATE_TIME,      // 2020-12-31T23:59:59+08:00
            DateTimeFormatter.ISO_OFFSET_DATE_TIME,     // 2020-12-31T23:59:59+08:00
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), // 2020-12-31 23:59:59(按业务时区解释)
            DateTimeFormatter.ISO_LOCAL_DATE_TIME       // 2020-12-31T23:59:59
    );

publicstaticclassLog{
publicfinal String userId;
publicfinal String timeStr;
publicLog(String u, String t){ userId = u; timeStr = t; }
    }

publicstatic Map<String, ZonedDateTime> lastLoginIn2020(List<Log> logs){
        Map<String, ZonedDateTime> ans = new HashMap<>();
for (Log log : logs) {
            ZonedDateTime zdt = parseToBizZone(log.timeStr);
if (zdt == null) continue;
if (zdt.getYear() != 2020) continue; // 只算2020
            ZonedDateTime old = ans.get(log.userId);
if (old == null || zdt.isAfter(old)) {
                ans.put(log.userId, zdt);
            }
        }
return ans;
    }

privatestatic ZonedDateTime parseToBizZone(String s){
for (DateTimeFormatter f : Fmts) {
try {
if (f == DateTimeFormatter.ISO_ZONED_DATE_TIME ||
                    f == DateTimeFormatter.ISO_OFFSET_DATE_TIME) {
// 自带偏移,尊重它,再转业务时区用于“按年判断”
return ZonedDateTime.parse(s, f).withZoneSameInstant(BIZ_ZONE);
                } else {
// 无偏移,当作业务时区的本地时间
                    LocalDateTime ldt = LocalDateTime.parse(s, f);
return ldt.atZone(BIZ_ZONE);
                }
            } catch (DateTimeParseException ignore) { }
        }
returnnull; // 实在解析不了就丢掉
    }

// ---- demo ----
publicstaticvoidmain(String[] args){
        List<Log> logs = Arrays.asList(
new Log("A", "2019-12-31T23:59:59+08:00"),
new Log("A", "2020-12-31T23:59:59+08:00"),
new Log("A", "2020-12-31 23:59:58"),
new Log("B", "2021-01-01T00:00:00+09:00"), // 换到上海可能仍是2020-12-31 23:00
new Log("B", "2020-01-01T00:00:01+08:00"),
new Log("C", "oops") // 脏数据
        );
        Map<String, ZonedDateTime> res = lastLoginIn2020(logs);
        res.forEach((u,t) -> System.out.println(u + " -> " + t));
    }
}

复杂度这块儿别纠结,单次扫描 O(n),空间 O(u),u 是用户数。要是数据特别大…有人问我那个配置怎么写来着,对对对,落地就把 Map 换成外部存储的“按 userId 分桶”,或者用流处理(Flink/Kafka Streams)做window+maxBy,同理。好了不说了,关东煮糊了我先去换一份…

-END-

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

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