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

欢迎!请参阅关于页面以获取有关该机制的一些更多信息。

+19
Clojure
已关闭

许多人一直在编写映射HashMap值的功能

提案:添加 map-keysmap-values,它们将映射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

评论者:alexmiller

在这个问题上纠结名称没有意义 - Rich自有他的观点。

关于这个补丁
- 删除:static元数据,因为现在不再使用
- 需要文档字符串,其风格应与其它Clojure文档字符串一致。map是一个很好的借鉴地方。
- 而不是声明,可以推迟它们的定义,直到需要定义它们的内容。没有必要为此添加更多的声明。

还有其他的潜在实现 - 应该实现它们,并在不同大小的输入范围内进行比较以评估性能。除了当前方法,我还将调查

  • 使用reduce-kv并将结果构建成一个临时map。这允许map自己减少(不需要序列缓存)并避免创建并再次删除条目。
  • 使用intot{}(映射...m)

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

0

评论由:delihiros 提供

感谢评论:)

我建议将这些函数分别称为 update-valsupdate-keys,而不是 map-valsmap-keys
也许吧。但我现在给它命名为《map-**》,我们以后可以再选择名称:)

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

关于考虑
> 是否构建键值向量并将其转换为map,或者直接构建map(前者可能更快,但不确定)
> 是否在现有核心代码中有地方可以使用map-keys/map-vals(我非常确定有)
> 如果构建map,如何构建条目(向量或直接创建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 的实现表现较差。
- 对于大型映射应使用临时(transients),但对小型映射则不应使用。
- 此基准表明,在不牺牲性能的情况下,可以保持输出映射的类型属性(使用 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
...