2024 Clojure 状态调查 中分享您的想法!

欢迎!请参阅关于页面了解如何使用本站更多的信息。

+19
Clojure
已关闭

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

建议:添加 map-keysmap-values 功能,该功能映射 HashMap 中的键和值。它们返回作为结果的 HashMap。

解决方案:使用 reduce-kv 函数或普通的 mapinto 是一种常见的解决方案,但它们很令人困惑,并且类型会发生改变,这使得它变得棘手且繁琐。

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

备注:[Clojure 1.11.0-alpha2 添加了对 `update-keys` 和 `update-vals` 的支持 - 详细信息请参阅 CLJ-1959 和 CLJ-2651](https://groups.google.com/forum/#!topic/clojure-dev/kkPYIl5qj0o)

13 答案

0

评论者:delihiros

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

0

评论者:bronsa

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

0

评论人:alexmiller

在这个问题上的名称并不重要 - Rich有自己的看法。

在补丁中
- 删除:static元数据,这些不再被使用
- 需要文档字符串,文档字符串应该参照其他Clojure文档字符串的风格进行编写。map可能是一个很好的参考。
- 不要将这些声明放入,而是将这些定义推迟到它们需要定义的地方。没有必要为这些添加更多的声明。

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

  • 使用构造到瞬态映射的reduce-kv。这允许映射自我减少(不需要seq缓存)并避免创建只为了再次拆分的条目。
  • (into {} (map ...) m)的transducers

还应该考虑
- 是否构建k/v向量并将其转换为映射,或直接构建映射(前者可能更快,不确定)
- 如果构建映射,应如何构建映射条目(向量与直接创建mapentry对象)
- 在map-keys中,当map生成新的重叠键时是否存在任何未解决的问题?
- 在现有的核心代码中,是否有地方可以使用map-keys/map-vals(我非常确定有)

0

评论者:delihiros

感谢评论:)

我建议将这些函数命名为 update-valsupdate-keys,而不是 map-valsmap-keys
也许吧。但现在我用地名为map-**,稍后我们可以选择它:)

关于潜在的实现
我已经尝试了几个实现,并且似乎当前的实现是最快的。
您可以在以下位置查看: https://github.com/delihiros/performance

关于考虑事项
> 是否构建k/v向量并将其转换为映射,或直接构建映射(前者可能更快,不确定)
> 在现有的核心代码中,是否可以使用map-keys/map-vals(我非常确定)
> 如果构建映射,应如何构建映射条目(向量与直接创建mapentry对象)
我将尽快检查它们。我还没有做。

在map-keys中,当map生成新的重叠键时是否存在任何未解决的问题?
我认为它应该由后来应用的键和值覆盖。

0

评论人:nathanmarz

我已经在构建Specter的过程中对这个进行了相当多的调查。以下是一些map-vals的多种方法的基准测试,包括使用Specter。

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

一些评论
- 构造和分解 MapEntry 的实现表现远差于其他实现。
- 对于大型映射,应该使用瞬态(Transient),但对于小型映射则不应用此方法。
- 此基准表明,在输出中保持映射类型的属性可以通过不牺牲性能来实现(使用 Specter 或 "empty" 的测试用例拥有此属性)。

0

评论者:delihiros

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

0

发表评论者:[email protected]

调用 reduce-kv 的实现不是惰性的,因此应在提议的补丁(map-mapper-v3.patch)中明确说明文档。而且,最好使用 "map"(作为名词)而不是指明一个具体的实体类型 "hash map"。

0

评论者:bronsa

无论如何,map->map 操作也不能是惰性的。即使某个实现使用了惰性操作来遍历原始映射,但 `into {}` 会稍后将其实现。

0

评论人:nathanmarz

反对此意见。Clojure 旨在拥有简洁的核心,并将额外的功能推入库中。复合转换的问题空间,其中这一功能只是一个小部分,已经被 Specter 完美解决。Specter 的 `MAP-VALS` 和 `MAP-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

内森,你在关于复合变换方面制造了一个稻草人。这并不是请求一个具有筛选旋钮或条件行为的函数(历史上Clojure反对这样的行为)。除了Specter的方法之外,还有其他有效的方法。

关于快速实现:并不是每个函数都必须追求最高性能的实现,尤其是在分叉和复杂性增加的情况下。性能成本模型必须考虑到复杂性。

这篇工单是关于在许多代码库中重复出现的便利性请求。

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

0

评论人:nathanmarz

性能对于这类通用数据结构操作函数至关重要。操作数据结构是该语言中最基本的操作之一,它在性能敏感的代码中反复进行。在性能敏感的代码中,不得不放弃“漂亮的”函数,改用丑陋的专用代码,这是语言的失败。

map-vals 和 map-keys 是一个复杂的问题空间的一部分,我和 Specter 的用户在过去几年里学到了很多。Clojure基本触及了这个问题空间,尤其是当涉及到嵌套或递归数据结构时。给Clojure添加在语义和性能都明显劣于外部库已存在功能的函数,在我看来似乎有些荒谬。仅代表我个人观点。

0

评论者:delihiros

我同意内森·马茨的观点,性能非常重要,但我仍然强烈主张这个函数应该以某种方式导入到语言的核心部分。
人们经常使用这种变换,如果在内核中有一个快速实现,将对我们所有人都有很大帮助。

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