)
文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载--/2是 Elixir 内核Kernel提供的一个列表差集运算符用于把右侧列表中的所有元素从左侧列表中移除是处理从一批数据中剔除某些项这类需求最直接的写法。这篇笔记围绕该运算符的匹配语义、典型用法、性能边界以及真正的集合差集方案MapSet.difference/2展开读完后你能明确判断什么时候用--什么时候该换MapSet。--/2的基本语义右列表减左列表--/2的作用是减法式的列表差集把右侧列表right中的每一个元素从左侧列表left中移除最终返回一个新的列表。原列表本身不会被修改符合 Elixir 函数式不可变数据的特性——运算结果是新构造出的列表。先看最简单的例子iex [1, 2, 3] -- [2, 4] [1, 3]这里右侧列表是[2, 4]其中2在左侧存在被移除4在左侧根本不存在没有对应元素时就静默忽略不会报错也不产生任何影响。最终左侧剩下的1和3按原有顺序返回。几个值得注意的细节结果保持左侧列表的原有顺序--不做排序也不打乱顺序运算符不满足交换律a -- b与b -- a的结果通常完全不同谁是被减数很重要移除依据是值相等语义与元素类型无关整数、原子、字符串、元组等都可以参与运算。比如用~w(...)a构造出的原子列表参与运算语义完全一致原子列表的构造方法可参考 elixir/create-a-list-of-atoms.mdiex ~w(fire water grass)a -- ~w(water)a [:fire, :grass]逐次匹配每次出现独立移除而不是一删全删--/2最容易被误解的一点在于它不是集合意义上的差集而是多重集合multiset式的逐次匹配。左侧列表里每个元素都是独立的实例右侧每出现一个对应元素就恰好消耗掉左侧的一个实例。原文档给出了一个非常能说明问题的例子iex [:a, :b, :c, :a, :d, :a] -- [:a, :a] [:b, :c, :d, :a]逐步拆解左侧有3 个:a、各 1 个:b、:c、:d右侧有 2 个:a因此依次消耗左侧的 2 个:a剩余的 1 个:a因为没有对应的扣减额度被保留下来最终结果是[:b, :c, :d, :a]顺序依旧保持。也就是说右侧出现两次就只移除两个不会像真正的集合运算那样把同值的元素全部清空。这正是--/2与去重后取差集在语义上的本质区别。这种按次数精确扣除的语义在实际业务里反而更贴合需求。例如统计库存里扣掉用户选购的每件商品或把配置里已启用的条目逐个剔除等场景都是按出现次数而非按值集合来算的。典型应用从 Keyword List 中移除条目--/2对任何元素类型都适用因此在 Elixir 中一个常见用途是从 keyword list键值列表中移除指定的键值对条目。keyword list 本质是二元元组列表例如[a: 1, b: 2]等价于[{:a, 1}, {:b, 2}]所以完全可以直接做减法iex [a: 1, b: 2, c: 3] -- [a: 1] [b: 2, c: 3]这里移除的是键值对整体元组{:a, 1}而不是只按:a这个键来移除。若想按键批量剔除需要把要移除的条目都构造成完整元组iex config [db: postgres, port: 5432, host: localhost] iex config -- [db: postgres, port: 5432] [host: localhost]值得注意的是如果移除列表里出现的元素在左侧没有对应项同样会被忽略——这与第一小节展示的4的情形一致。关于 keyword list 的更多操作追加、按键取值、去重等仓库里有系列配套笔记可以对照阅读elixir/append-to-a-keyword-list.md、elixir/all-values-for-a-key-in-a-keyword-list.md、elixir/replace-duplicates-in-a-keyword-list.md。性能陷阱大列表上的--会非常慢原文档特别提醒--/2并不是一种高效的列表运算列表越大越慢。它给出的极端示例是这样的——从一百万个整数的列表中减去第二个列表iex Enum.into(1..1000000, []) -- Enum.into(2..1000000, []) [1]这个表达式在作者的环境中运行了数分钟才返回结果。结果本身符合预期1只出现在左侧右侧从2开始因此1被保留其余元素被全部扣除但耗时暴露了--的实现本质从实现层面理解它需要对左侧的每一个元素都去右侧列表中查找是否有对应匹配本质上是两层嵌套遍历时间复杂度为O(n × m)n、m 分别为左右列表长度。两个列表各百万级时最坏情况就是万亿次级别的比较数分钟的耗时也就不足为奇了。因此请记住这条经验法则列表规模小、代码可读性优先时--/2是表达差集最简洁的方式列表规模大、性能敏感时--/2很可能是性能瓶颈应改用哈希表结构的替代方案。真正的集合差集用 MapSet.difference/2如果需求语义是值去重后的集合差即同值只算一个那么官方文档推荐使用MapSet.difference/2。MapSet基于哈希实现差集运算的代价远低于列表的嵌套遍历同时天然具备去重能力。iex left MapSet.new([1, 2, 3, 3, 4]) iex right MapSet.new([2, 5]) iex MapSet.difference(left, right) MapSet.new([1, 3, 4])对比同样数据的--写法iex [1, 2, 3, 3, 4] -- [2, 5] [1, 3, 3, 4]两者在去重行为上的差异一目了然MapSet输出[1, 3, 4]重复的3只保留一个而--保留了两个3。所以选型标准很清晰需求推荐方案语义按出现次数精确扣除list1 -- list2多重集合差保序去重后取差集、数据量大MapSet.difference/2集合差无序顺带一提MapSet.new/1接收任何可枚举Enumerable结构所以从 Range、数组等转换成集合都很方便适合先MapSet运算再转回列表的组合写法。深入查阅h Kernel.----/2定义在Kernel模块中是全局可用的运算符。在 IEx 里可以直接查看它的官方文档和完整说明iex h Kernel.--这也是原文档给出的延伸学习入口。通过h命令可以看到该运算符完整的参数说明、语义定义以及官方对MapSet.difference/2的替代建议与本文内容相互印证。如果还想巩固列表相关的基础操作可以继续阅读仓库内的 elixir/check-list-membership.mdin运算符判断成员关系、elixir/reversing-a-list.md列表反转等笔记它们与--/2一样都属于日常高频的列表处理技巧。小结--/2是 Elixir 中表达从一个列表移除另一个列表元素最直观的运算符它按出现次数逐一匹配、保持左侧顺序、对不存在的元素静默忽略。但它的逐次匹配语义决定了它不等于集合差集且 O(n × m) 的实现让它在百万级列表上会慢到数分钟级别。当遇到去重 大集合差的需求时MapSet.difference/2才是正确且高效的选择——记住小列表用--大集合用MapSet这条经验法则即可。赞分享文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载相关推荐30秒Python技巧如何高效判断一个列表是否包含于另一个列表30秒Python技巧如何高效判断一个列表是否包含于另一个列表 问题背景 在Python编程中我们经常需要判断一个列表的所有元素是否都存在于另一个列表中且教程文档30-seconds-of-python项目如何判断一个列表是否包含于另一个列表30 seconds of python项目如何判断一个列表是否包含于另一个列表 在Python编程中经常会遇到需要判断一个列表是否完全包含于另一个列表的情教程文档TIL 笔记用 Clojure merge-with 将多个 Map 聚合为一个计数求和与列表拼接TIL 笔记用 Clojure merge with 将多个 Map 聚合为一个计数求和与列表拼接 merge with 是 Clojure 核心库中用于文档教程知识库上一篇Kazumi构建你的个性化动漫宇宙——3步打造专属追番体验下一篇Fara1.5-27B企业级部署方案多GPU分片与Docker容器化最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考