ruby picture

RCR 278: Hash#collect_to_hash method

Submitted by Zallus (Sun Sep 12 02:09:28 UTC 2004)

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
ruby picture
Comments Current voting

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 ( So I'd like to see it included in Ruby, but at least I can easily use it in the meantime.

-- Gavin Sinclair


I don't really have a better suggestion; but I don't really like the name "collect_to_hash". Unless we simply override Hash#collect / Hash#map which ofcourse break backwards compatibility.

Strongly advocate to the idea though. Was going to suggest it myself when I found it was already suggested here.


Might this be a better solution: Array#to_hash ? It takes an associative array, and converts it to a hash. It can be loosely defined thus:

  class Array
    def to_hash
      Hash[ *(self.flatten) ]
    end
  end

The real solution would be to return associative pairs from a #map/#collect, and then turn them into a hash with an additional step. It's cleaner, and avoids the need to add this functionality to all Enumerable members.

Perhaps, though, one might want #to_hash to accept a block, being a transformation to apply to the elements of an Enumerable to get key-value pairs.

  module Enumerable
    def to_hash
      hsh = {}
      each{ |elem|
        elem = yield( elem ) if block_given?
        hsh[ elem[ 0 ] ] = elem[ 1 ]
      }
      hsh
      end
    end
  end

I'm sure this would be greatly sped up by being re-written in C. Given it's wide-ranging use, I strongly believe this belongs in the standard Ruby library.


Strongly opposed 0
Opposed 0
Neutral 0
In favor 2
Strongly advocate 3
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .