Python技术迷

某军工研究所网友爆料:一个月加班接近 20个小时,被同事警告说加班太少。。

刚看到个贴子,说某军工所的网友一个月加班不到20小时,结果被同事“善意警告”说太少了。底下还有网友补刀:我们这边都是60、70小时起步……无语住了。

Image

有些单位把加班当忠诚度考核,甚至当成竞争筹码,不卷不行。

军工确实任务重、节奏快,可再怎么忙也得有人性边界吧。把加班当基本操作,只会恶性循环,该干的活没变,内耗情绪倒是涨了。

从我的角度看,不怕加班,就怕把“加班文化”当成正确答案。

还是希望大家都能在努力和生活之间找到平衡。工作再重要,也得给自己留口气。愿每个人都能少点被动卷,多点自愿干,心态稳,生活也能稳。【备注:文末可领最新资料】

面试题:2016年的投资

这题其实本质不难,就是名字听起来有点“金融大佬”那味儿😂,我们先把题目用人话说清楚,再慢慢用 Python 拼出算法。

有一张 Insurance 表,大概长这样

  • pid:投保人编号(主键,唯一)
  • tiv_2015:这个人 2015 年一共投了多少钱
  • tiv_2016:这个人 2016 年投了多少钱
  • lat:所在城市纬度
  • lon:所在城市经度

要干的事是:把 2016 年“成功投资”的金额加起来,保留两位小数。

一个人算“成功投资”,要同时满足两个条件:

  1. 他的 tiv_2015 至少和别的某一个人是一样的(也就是说,这个 2015 投保额在表里出现次数 ≥ 2)
  2. 他的城市是“独一份”的,也就是 (lat, lon) 这个坐标在表里只出现一次

满足这俩条件的人,把他们的 tiv_2016 全部加起来,最后保留两位小数就行。

原题一般用 SQL 写,这里我们换个角度,用“算法 + Python”来搞。

算法思路拆一拆

你可以把这题当成一个“两次统计 + 一次筛选”的小数据处理题。

直觉版本是这样:

  1. 先遍历一遍所有记录

  • 数一数每个 tiv_2015 出现了几次
  • 再数一数每个 (lat, lon) 出现了几次 这个最自然的结构就是哈希表(Python 里的 dict)
  • 再遍历一遍

    • 它的 tiv_2015 对应的计数是不是 ≥ 2
    • 它的 (lat, lon) 对应计数是不是 == 1
    • 对于每条记录,看:

    • 两个条件都成立,就把它的 tiv_2016 加进总和里

  • 最后 round(总和, 2),就完成了

  • 时间复杂度就是两遍遍历 O(n),空间就是两个哈希表,也是 O(n) 级别,很稳。

    用 Python 写一个“纯算法版”

    先假定我们把一行数据读成一个 dict,类似这样:

    record = {
    "pid": 1,
    "tiv_2015": 10.0,
    "tiv_2016": 5.0,
    "lat": 10.0,
    "lon": 10.0
    }

    整张表就是一个 list[dict]:

    records = [
        {"pid": 1, "tiv_2015": 10.0, "tiv_2016": 5.0, "lat": 10.0, "lon": 10.0},
        {"pid": 2, "tiv_2015": 20.0, "tiv_2016": 20.0, "lat": 20.0, "lon": 20.0},
        ...
    ]

    然后实现函数:

    from collections import defaultdict
    from typing import List, Dict, Any


    definvestment_2016(records: List[Dict[str, Any]]) -> float:
    """
        计算 2016 年成功投资金额总和,保留两位小数。
        records: 每一项是一个 dict,包含
            pid, tiv_2015, tiv_2016, lat, lon
        """

    # 1. 统计 2015 投保额出现次数
        cnt_2015 = defaultdict(int)
    # 2. 统计城市坐标出现次数
        cnt_city = defaultdict(int)

    for r in records:
            tiv_2015 = r["tiv_2015"]
            lat = r["lat"]
            lon = r["lon"]

            cnt_2015[tiv_2015] += 1
            cnt_city[(lat, lon)] += 1

    # 3. 第二遍筛选 & 求和
        total = 0.0
    for r in records:
            tiv_2015 = r["tiv_2015"]
            lat = r["lat"]
            lon = r["lon"]
            tiv_2016 = r["tiv_2016"]

    # 条件1:2015 年投保额,至少和别人一样一次
            cond_2015 = cnt_2015[tiv_2015] >= 2
    # 条件2:城市坐标是独一份
            cond_city = cnt_city[(lat, lon)] == 1

    if cond_2015 and cond_city:
                total += tiv_2016

    # 4. 四舍五入到两位小数
    return round(total, 2)


    if __name__ == "__main__":
        example = [
            {"pid": 1, "tiv_2015": 10, "tiv_2016": 5, "lat": 10, "lon": 10},
            {"pid": 2, "tiv_2015": 20, "tiv_2016": 20, "lat": 20, "lon": 20},
            {"pid": 3, "tiv_2015": 10, "tiv_2016": 30, "lat": 20, "lon": 20},
            {"pid": 4, "tiv_2015": 10, "tiv_2016": 40, "lat": 40, "lon": 40},
        ]
        print(investment_2016(example))  # 输出 45.0

    你可以对照一下示例数据:

    • tiv_2015 = 10 的有 pid=1,3,4,确实“至少有一个人跟我一样”,所以条件1 对他们都成立

    • 但城市坐标 (20, 20) 出现了两次(pid=2,3),所以 pid=2 和 pid=3 的城市不是独一份

      • pid=2:条件1 不成立(2015=20 只有他一个),条件2 也不成立(城市重复)
      • pid=3:条件1 成立(2015=10),条件2 不成立(城市重复)
    • 只有 pid=1 和 pid=4 同时满足两个条件,所以 tiv_2016 是 5 + 40 = 45

    上面代码跑出来 45.0,就对上题目的示例结果了([CSDN博客][1])。

    顺便和 SQL 的思路对个频

    原题在 SQL 里一般会用两次 GROUP BY:

    • 一次按 tiv_2015 分组,找出出现次数 ≥ 2 的金额
    • 一次按 (lat, lon) 分组,找出出现次数 == 1 的城市
    • 然后把这两个结果跟原表“联立”,最后 SUM(tiv_2016)

    你会发现,我们刚才用 Python 的两张哈希表,其实就是把这两个 GROUP BY 的结果“缓存”在内存里了,然后在第二次遍历里做“过滤 + 求和”。

    所以这题本质上不是什么高深算法,就是一个很标准的“两次扫描 + 计数 + 过滤”套路: 以后碰到类似“满足若干全局条件,再把某个字段加起来”的题,大概率都可以用这一套模板来写 Python 版。

    如果你后面想再扩展,比如用 pandas 来写一个更“表格化”的版本,也可以在这个思路上直接平移过去:groupby 两次、打标记、再过滤求和就行了。

    -END-

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

    🔥虎哥私藏精品🔥

    虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB