简体   繁体   English

在ruby中映射哈希的更清洁方法

[英]Cleaner way of mapping a hash in ruby

Let's assume I need to do a trivial task on every element of a Hash , eg increment its value by 1 , or change value into an array containing that value. 让我们假设我需要对Hash每个元素执行一个简单的任务,例如将其值增加1 ,或将值更改为包含该值的数组。 I've been doing it like this 我一直这样做

hash.map{ |k, v| [k, v+1] }.to_h

v+1 is just an example, it can be anything. v+1只是一个例子,它可以是任何东西。

Is there any cleaner way to do this? 有没有更清洁的方法来做到这一点? I don't really like mapping a hash to an array of 2-sized arrays, then remembering to convert it to hash again. 我真的不喜欢将哈希映射到2个大小的数组的数组,然后记住再次将其转换为哈希。

Example of what might be nicer: 可能更好的例子:

hash.hash_map{ |v| v+1 }

This way some thing like string conversion ( to_s ) might be simplified to 这样,字符串转换( to_s )之类的东西可能会被简化为

hash.hash_map(&:to_s)

Duplication clarification: I'm not looking for Hash[...] or .to_h , I'm asking if anyone knows a more compact and cleaner solution. 复制澄清:我不是在寻找Hash[...].to_h ,我在问是否有人知道更紧凑,更清洁的解决方案。

That's just the way Ruby's collection framework works. 这就是Ruby的集合框架的工作方式。 There is one map method in Enumerable which doesn't know anything about hashes or arrays or lists or sets or trees or streams or whatever else you may come up with. Enumerable一个 map方法,它不知道任何关于哈希或数组或列表或集合,树或流或其他任何你可能想到的东西。 All it knows is that there is a method named each which will yield one single element per iteration. 它只知道有一个名为each的方法,每次迭代将yield 一个单独的元素。 That's it. 而已。

Note that this is the same way the collections frameworks of Java and .NET work, too. 请注意,这与Java和.NET的集合框架的工作方式相同。 All collections operations always return the same type: in .NET, that's IEnumerable , in Ruby, that's Array . 所有集合操作总是返回相同的类型:在.NET中,这是IEnumerable ,在Ruby中,就是Array

Another design approach is that collections operations are type-preserving, ie mapping a set will produce a set, etc. That's the way it is done in Smalltalk, for example. 另一种设计方法是集合操作是类型保留的,即映射集合将产生集合等等。例如,这就是在Smalltalk中完成的方式。 However, in Smalltalk, but there it is achieved by copy&pasting almost identical methods into each and every different collection. 但是,在Smalltalk中,可以通过将几乎相同的方法复制并粘贴到每个不同的集合中来实现。 Ie if you want to implement your own collection, in Ruby, you only have to implement each , and you get everything else for free, whereas in Smalltalk, you have to implement every single collection method separately. 也就是说,如果你想在Ruby中实现你自己的集合,你只需要实现each ,你就可以免费获得其他所有东西,而在Smalltalk中,你必须分别实现每一个集合方法。 (In Ruby, that would be over 40 methods.) (在Ruby中,这将超过40种方法。)

Scala is the first language that managed to provide a collections framework with type-preserving operations without code duplication, but it took until Scala 2.8 (released in 2010) to figure that out. Scala是第一种能够在没有代码重复的情况下为类型保留操作提供集合框架的语言,但直到Scala 2.8(2010年发布)才能解决这个问题。 (The key is the idea of collection builders.) Ruby's collections library was designed in 1993, 17 years before we had figured out how to do type-preserving collections operations without code duplication. (关键是集合构建器的想法。)Ruby的集合库是在1993年设计的,17年前我们已经想出如何在没有代码重复的情况下进行类型保留集合操作。 Plus, Scala depends heavily on its sophisticated static type system and type-level metaprogramming to find the correct collection builder at compile time. 此外,Scala在很大程度上依赖于其复杂的静态类型系统和类型级元编程,以便在编译时找到正确的集合构建器。 This is not necessary for the scheme to work, but having to look up the builder for every operation at runtime may incur a hefty runtime cost. 这不是该方案工作所必需的,但是必须在运行时为每个操作查找构建器可能会产生大量的运行时成本。

What you could do is add new methods that are not part of the standard Enumerable protocol, for example similar to Scala's mapValues and mapKeys . 可以做的是添加不属于标准Enumerable协议的新方法,例如类似于Scala的mapValuesmapKeys

AFAIK, this does not exist in the Hash out of Ruby box, but here is a simple monkeypatch to achieve what you want: AFAIK,这不存在于Ruby框中的Hash中,但是这里有一个简单的monkeypatch来实现你想要的:

▶ class Hash
▷   def hash_map &cb
▷     keys.zip(values.map(&cb)).to_h
▷   end  
▷ end

There are more readable ways to achieve the requested functionality, but this one uses the built-in map for values once , pretending to be the fastest implementation that comes into my mind. 还有更易读的方式来实现所要求的功能,但是这一次采用了内置map进行一次数值,假装是进入我的脑海里最快的实现。

▶ h = {a: 1, b: 2}
#⇒ { :a => 1, :b => 2 }
▶ h.hash_map do |v| v + 5 end
#⇒ { :a => 6, :b => 7 }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM