Comment on this RCR (edit wiki page) | RCRchive home

RCR 278: Hash#collect_to_hash method

submitted by Zallus on Sat Sep 11 2004 10:02:28 PM -0700

Status: pending


Abstract

A simple method for Hashes (or Mappables), like #collect, but that returns a Hash, mapping the old keys to the returned values.

Problem

I often run into the problem of having a hash, and needing a new, modified hash. Enumerable#collect works as a "filter" method well enough when you are dealing with Arrays, but Hash#collect, suprisingly, also returns an Array. This usually leads to the following code:
newHash = Hash.new
oldHash.each { |k,v| newHash[k] = modify_in_some_way(v) }
newHash
Or, at the very least:
oldHash.keys.zip(oldHash.collect{ |k,v| modified(v) })
To get the new hash required. The first version relies on an imperative construct, while the second does not return a Hash, but merely an associative Array, which is not properly equipped to act as a hash (Ruby has no Array#to_hash !).
In Ruby, you can usually do things functionally, just chaining methods and possibly using binary comparisons. However, there (currently) is no way to do the previous in such a manner.

Proposal

Another method, specifically for Hashes, that returns a "re-mapped" Hash. Possibly Hash#collect_to_hash or Hash#remap (which sounds destructive, and might be better as a destructive compliment).

Analysis

This would have no impact on anyone who doesn't need such a method, save for people who delegate to Hashes, and don't expect such a method. It would pull quite a bit of redundance from those who do, however.

Implementation

It would look, in Ruby code, pretty much like my first example.
class Hash # or module Mappable
  def collect_into_hash( &aProc )
    new = Hash.new
    keys.each{ new[key] = aProc.call( key, self[key] ) }
    new
  end
end

Vote for this RCR

Strongly opposed [0]
Opposed [0]
Neutral [0]
In favor [1]
Strongly advocate [0]

Change the status of this RCR to:

accepted

rejected

withdrawn


Hi --

I'm still not sure what you have in mind for an implementation. Would it be something like this?

class Hash
  def map_to_hash
    h = Hash.new
    keys.each do |key|
      h[key] = yield(self[key])
    end
    h
  end
 
  def map_to_hash!
    replace(map_to_hash)
  end
end

which would then let you do this:

h = {1,2,3,4,5,6}
p h.map_to_hash {|v| v * 10}
output => {5=>60, 1=>20, 3=>40}

As for Enumerable#collect returning an Array... that's true across the board. It's not favoritism :-) An array is the lowest common denominator, so to speak, in which an enumerable can be represented, so it's the representation of choice for collect, select, etc. You couldn't have every enumerable class return an object of its own class; that might sound like it makes sense for Hash, but for enumerables in general it wouldn't. (Think of File#collect....)

-- David Black

[Edited the proposal to clear things up a bit] Yeah, that's pretty much what I mean. I might think that Enumerable#collect and the previously proposed "Mappable"'s #collect could be different, if that would be acceptable. Other than that, a different method name would be needed to represent the different paradigm. It would also be useful for things other than Hashes which are associative, but I'm not saying it should be used for things with pure-integral indeces (unless you want to sparsify something using a collect_to_hash identity transform, which would be better as Array#map_to_hash (not Array#to_hash, however, which should be the inverse of Hash#to_a.))

-- Zallus Kanite


I am very much in favour if this kind of functionality. So much so that I implemented it as Enumerable#build_hash in the 'extensions' project (http://extensions.rubyforge.org). So I'd like to see it included in Ruby, but at least I can easily use it in the meantime.

-- Gavin Sinclair


Add comments here

Back to RCRchive.


RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog