Clojure 2024 调查问卷! 中分享您的想法。

欢迎!请查看关于页面以获取有关该功能更多信息。

+19
Clojure
已关闭

许多人一直都在编写用于在 HashMap 中映射值的函数

建议:添加 map-keysmap-values 函数,它们:映射 HashMap 中的键,并在 HashMap 中映射值。它们返回 HashMap 作为结果。

权宜之计:使用函数 reduce-kv 或普通的 mapinto 是一个常见的解决方案,但它们很复杂,类型会改变,这使得它变得复杂且繁琐。

讨论:https://groups.google.com/forum/#!topic/clojure-dev/kkPYIl5qj0o

以以下注释关闭:Clojure 1.11.0-alpha2 添加了对 `update-keys` 和 `update-vals` 的支持 - 请参阅 CLJ-1959 和 CLJ-2651 以获取更多信息

13 个答案

0

评论者:delihiros

map-keys 和 map-vals 的代码和测试

0

评论者:bronsa

我建议这些函数被命名为 update-valsupdate-keys,而不是 map-valsmap-keys

0
by

评论者:alexmiller

在这个问题中讨论函数名没有意义 - Rich 有自己的看法。

在补丁中
- 移除 :static 元数据,因为现在不再使用它了
- 需要文档字符串,应该采用其他 Clojure 文档字符串的风格。map 是可能参考的地方。
- 不是立即声明,而是推迟定义,直到需要什么时再定义。没有必要为此添加更多的声明。

还有其他可能的实现 - 这些应该实现,并在一系列输入大小上比较性能。除了当前方法之外,我还将调查

  • reduce-kv 和构建到临时映射中。这允许映射自己递减(无需缓存序列)并避免创建仅为了拆分开的条目。
  • transducers with (into {} (map ...) m)

还应该考虑
- 是否构建一个键/值向量并将其转换为映射,还是直接构建映射(前者可能更快,不确定)
- 如果构建映射,则如何构建映射条目(向量与直接创建 mapentry 对象)
- 在 map-keys 中,当 map 生成新的重叠键时,有任何开放问题吗?
- 在现有的核心代码中,是否有可以用于 map-keys/map-vals 的地方(我相当肯定有)

0
by

评论者:delihiros

感谢您的评论:)

我建议这些函数被命名为 update-valsupdate-keys,而不是 map-valsmap-keys
也许吧。但我现在只将其命名为 map-**,我们以后再决定:)

关于潜在实现
我已经尝试了几种实现,看起来当前实现是最快的。
您可以在这里查看: https://github.com/delihiros/performance

关于考虑事项
> 是否构建键/值向量并将其转换为映射,或者直接构建映射(前者可能更快,不确定)
> 在现有的核心代码中,是否有可以用于 map-keys/map-vals 的地方(我相当肯定有)
> 如果构建映射,则如何构建映射条目(向量与直接创建 mapentry 对象)
我将尽快检查这些。我还没做过。

在 map-keys 中,当 map 生成新的重叠键时,有任何开放问题吗?
我相信它应该由后应用的关键值覆盖。

0
by

评论者:nathanmarz

我通过构建 Specter 对此进行了相当多的研究。以下是关于多种 map-vals 方法的基准测试,包括使用 Specter 的。

代码: https://github.com/nathanmarz/specter/blob/4778500e0370fb211f47ebf4d69ca64366117b6c/scripts/benchmarks.clj#L87
结果:https://gist.github.com/nathanmarz/bf571c9ed86bfad09816e17b9b6e59e3

一些评论
- 构建和拆分 MapEntry 的实现表现不佳。
- 对于大型地图应使用瞬态对象,但对于小型地图则不应使用。
- 这个基准测试表明,可以在不牺牲性能的情况下保持输出中地图的类型(Specter 或 "空" 的测试用例具有此属性)。

0

评论者:delihiros

我已经修改了实现。它应该比以前更快。

0

评论者:[email protected]

调用 reduce-kv 的实现不是惰性的,因此应在提议的补丁(map-mapper-v3.patch)中澄清文档。也许最好使用名词 "map",而不是指定特定的具体类型 "哈希表"。

0

评论者:bronsa

map->map 操作方式也不会是惰性的。即使某个实现使用了迭代原始地图的惰性操作,最后还是需要通过 into {} 实现它。

0

评论者:nathanmarz

-1。Clojure 致力于保持核心小而简洁,将更多功能推入库中。复合转换的问题空间,其中包括此功能的一小部分,已经被 Specter 完全解决。Specter 的 MAP-VALSMAP-KEYS 导航器还支持在转换过程中移除键/值对,通过将转换到特殊的 NONE 值来实现。这大大扩展了其用途。

还值得注意的一点是,根据地图的类型,快速实现需要采用完全不同的方法。适用于哈希表的 reduce-kv 和瞬态对象是最优的,但对于数组映射,使用较低级别的功能可提供约 60% 的速度提升。请参阅 Specter 的实现,见 https://github.com/nathanmarz/specter/blob/1.0.4/src/clj/com/rpl/specter/navs.cljc#L243

0

评论由:gshayban

Nathan,你关于复合转换的做法是建立在一个假设之上的。这不是要求一个具有过滤旋钮或条件行为的函数(Clojure历史上一直反对这样做)。除了Specter之外,还有其他一些有效的做法。

关于快速实现:并非每个函数都必须追求最有效的实现,特别是在牺牲分支和通用复杂性的情况下。性能成本模型必须考虑复杂性。

这个工单是关于一个在很多代码库中重复出现的便利功能。

我们想保留元数据吗?许多映射操作都要这样做。
我们希望假设IEditableCollection吗?

0

评论者:nathanmarz

对于像这种通用的数据结构操作函数,性能极其重要。操作数据结构是你在语言中做的最基本的事情之一,而且在性能敏感的代码中不断进行。在性能敏感的代码中必须放弃“漂亮”的函数,转而使用丑陋的专用代码,这是语言的失败。

map-vals/map-keys 是一个复杂的问题域,我自己和 Specter 的用户在过去几年里学到了很多。Clojure 几乎没有触及这个问题域,尤其是当涉及到嵌套或递归数据结构时。在 Clojure 中添加比外部库中已经存在的在语义和性能上都明显劣质的函数显得非常愚蠢。仅代表我个人观点。

0

评论者:delihiros

我同意Nathan Martz的观点,认为性能非常重要,但我仍然强烈认为这个函数应该以某种方式导入到语言的核心部分。
人们经常使用这种转换,如果在核心部分有快速的实现,这将是对我们所有人都有益的。

0
参考:https://clojure.atlassian.net/browse/CLJ-1959(由 delihiros 提出)
...