说白了,set 就是一个集合。你想想数学课上学的那个“集合”概念,基本上就是那回事。它就是一个装东西的容器,但有两个特别重要的规矩。
第一个规矩:里面的东西不能重复。
这是 set 最核心的特点。你往一个 set 里放东西,如果放的东西已经存在了,那等于什么都没发生。比如说,你有一个 set,里面已经有 ‘苹果’、‘香蕉’、‘橘子’。你再往里放一个 ‘苹果’,这个 set 还是只有 ‘苹果’、‘香蕉’、‘橘子’ 这三样。它不会变成有两个‘苹果’。
这个特性让 set 在一个场景下特别好用:去重。
我刚开始学编程的时候,遇到一个任务,要统计一个长长的用户访问列表里,到底有多少个独立的用户。这个列表可能有几万行,里面一个用户 ID 可能出现几十次。我当时想的办法是,建一个新列表,然后遍历老的列表,每次拿出一个用户 ID,都去新列表里检查一下它在不在。如果不在,就加进去。
代码写出来跑得特别慢。列表越大,每次检查就要花越长的时间。后来有人告诉我,直接把整个列表扔给 set 就行了。
就一行代码。
比如你有一个列表 user_ids = [101, 102, 103, 101, 104, 102]
。
你只要执行 unique_users = set(user_ids)
。
这个 unique_users
的结果就是 {101, 102, 103, 104}
。所有重复的 ID 自动就没了,干净利落。从那以后,只要我遇到需要去除重复元素的需求,第一反应就是用 set。
第二个规矩:里面的东西是无序的。
这一点也挺重要。跟列表(list/array)不一样,set 里的元素没有“第几个”的概念。你不能说“我要 set 里的第 3 个元素”,程序会报错,因为它根本不知道哪个是第 3 个。你把一堆东西扔进 set,它怎么存放是它内部的事,你不用管,也管不了。
因为无序,所以 set 不支持索引。像 my_list[0]
这种操作,在 set 上是行不通的。你从 set 里把东西拿出来的时候,它们的顺序可能跟你放进去的顺序完全不一样。所以,如果你需要保证元素的顺序,那就不能用 set,得老老实实用列表。
那么,除了去重,set 还有什么用?
另一个巨大的用途是:快速判断一个元素是否存在。
前面我说过我那个检查用户 ID 是否存在的笨办法。用列表检查一个元素在不在,列表越大,速度越慢。因为计算机会从列表的第一个元素开始,一个一个地比对,直到找到或者检查完整个列表。如果列表有一百万个元素,最坏的情况下,它要比对一百万次。
但是 set 不一样。set 的内部结构(通常是哈希表)让它能用一种更聪明的方式存东西。你可以把它想象成一本字典。你要查一个字,你会直接通过拼音或者部首去翻,而不是从第一页一个字一个字地往后读。set 也是类似,它通过一个计算(哈希计算)能很快地定位到元素应该在的位置。
所以,判断一个元素是否在 set 里,速度几乎是恒定的。不管 set 里有 10 个元素,还是一百万个元素,花费的时间都差不多,而且都非常快。
举个实际的例子。比如你要写个程序,检查一个新注册的用户名是不是已经被别人用了。你数据库里有几百万个已注册的用户名。
如果把这几百万个用户名加载到一个列表里,然后用 if new_username in user_list:
来检查,每个新用户注册时都会卡一下。
但如果你把这几百万个用户名加载到一个 set 里,然后用 if new_username in user_set:
来检查,这个操作几乎是瞬间完成的。这个性能差异在数据量大的时候是天壤之别。
最后,set 还有一些很酷的数学运算功能,这让它在处理数据关系时很方便。
这些运算也是从数学集合论里来的。
交集 (Intersection):找出两个 set 中共有的元素。
比如,你有两个 set,一个是喜欢足球的人football_fans = {'张三', '李四', '王五'}
,另一个是喜欢篮球的人basketball_fans = {'李四', '王五', '赵六'}
。
你想找出既喜欢足球又喜欢篮球的人,做个交集运算就行了。在很多编程语言里,就是用&
这个符号。
football_fans & basketball_fans
的结果就是{'李四', '王五'}
。并集 (Union):合并两个 set,并自动去重。
还是上面那个例子,你想知道所有参与讨论的人(不管喜欢足球还是篮球)都有谁。
用|
符号做并集运算。
football_fans | basketball_fans
的结果就是{'张三', '李四', '王五', '赵六'}
。所有人都进来了,并且重复的‘李四’和‘王五’只出现一次。差集 (Difference):找出一个 set 中有,而另一个 set 中没有的元素。
比如,你想找出只喜欢足球,不喜欢篮球的人。
用-
符号做差集运算。
football_fans - basketball_fans
的结果是{'张三'}
。
反过来,basketball_fans - football_fans
的结果就是{'赵六'}
,也就是只喜欢篮球,不喜欢足球的人。
这些运算在数据分析、用户画像等领域都特别有用。比如,分析两组用户群体的重合度,或者找出只参与了 A 活动但没参与 B 活动的用户。用 set 来做这些事,代码会写得非常简洁,而且逻辑清晰。
总结一下什么时候该用 set:
– 你需要去除数据里的重复项。
– 你需要快速判断一个元素是否存在于一个大的集合中。
– 你需要处理两组或多组数据之间的交集、并集、差集关系。
什么时候不该用 set:
– 你需要保持元素的插入顺序。
– 你需要通过索引(比如第几个位置)来访问元素。
– 你需要存储重复的元素。
几乎所有主流的编程语言,比如 Python、Java、JavaScript、C++,都有 set 这个数据结构,虽然名字可能稍微有点不同(比如 Java 里的 HashSet
),但核心思想和用法都是一样的。下次当你处理一堆数据,需要保证唯一性或者想快速查找的时候,就可以想想 set 这个东西。