Feature #7793

New methods on Hash

Added by Dominic Sisneros about 2 years ago. Updated 6 months ago.

[ruby-core:51933]
Status:Assigned
Priority:Normal
Assignee:Yukihiro Matsumoto

Description

It would be nice to have the following methods added to hash

h = { name: 'dominic', request: 'add the following methods', :why => 'convenience'}

h.map_v{|v| v.upcase}
#=> {:name=>"DOMINIC", :request=>"ADD THE FOLLOWING METHODS", :why=>"CONVENIENCE"}

h.map_k{|k| k.to_s}
#=> { "name"=> 'dominic', "request"=>"add the following methods', "why" => "convenience"}

h.map_kv{|k,v| [k.to_s, v.upcase]}
#=> { "name"=>"DOMINIC", "request"=>"ADD THE FOLLOWING METHODS", "why"=>"CONVENIENCE"}


class Hash

  def map_v
    reduce({}) do |result, array|
      k,v = array
      new_val = yield v
      result.merge( k => new_val)
    end
  end

  def map_k
    reduce({}) do |result, array|
      k,v = array
      new_k = yield k
      result.merge(new_k => v)
    end
  end

  def map_kv
    reduce({}) do |result, array|
      new_k,new_v = yield array
      result.merge(new_k => new_v)
    end
  end

end

Related issues

Related to Ruby trunk - Feature #6669: A method like Hash#map but returns hash Feedback 06/30/2012
Related to Ruby trunk - Feature #4151: Enumerable#categorize Assigned
Related to Ruby trunk - Feature #7292: Enumerable#to_h Closed 11/07/2012
Related to Ruby trunk - Feature #10552: [PATCH] Add Enumerable#frequencies and Enumerable#relative_frequencies Open 11/27/2014
Duplicated by Ruby trunk - Feature #9970: Add `Hash#map_keys` and `Hash#map_values` Open 06/21/2014

History

#1 Updated by Marc-Andre Lafortune about 2 years ago

  • Status changed from Open to Closed

I am glad to see that more people like you take the time to propose ways to create hashes.

I completely agree that hash creation from Enumerable is lacking currently.

I will close this feature request because I am convinced it can't be accepted as is (the proposed names have no chance of being accepted) and because it is largely duplicated by the following:

https://bugs.ruby-lang.org/issues/6669
https://bugs.ruby-lang.org/issues/4151
https://bugs.ruby-lang.org/issues/7292

If you have the time, read on those and see if you can contribute.

Thanks

#2 Updated by Dominic Sisneros about 2 years ago

This should be re-opened. It is not for all enumerables but only for hash.

map_v and map_k are very useful

map_kv is similar to h.mash and others and could be eliminated by those other bugs but the other functions aren't and are specifically for hashes and thus this should be re-opened

#3 Updated by Marc-Andre Lafortune about 2 years ago

  • Category set to core
  • Status changed from Closed to Assigned
  • Assignee set to Yukihiro Matsumoto

Fine, I'll reopen and assign this to Matz.

#4 Updated by Charlie Somerville about 2 years ago

At the risk of bike shedding, I think map_k and map_v should be named map_keys and map_values. That can be for matz to decide though.

#5 Updated by Nobuyoshi Nakada about 2 years ago

=begin
Considering existing methods:

$ ruby -e 'p Hash.instance_methods(false).grep(/each_/)'
[:each_value, :each_key, :each_pair]

They should be (({map_key})), (({may_value})), and (({map_pair})), respectively, I
think.

Anyway, why don't you make it a gem first?
=end

#6 Updated by Nobuyoshi Nakada about 2 years ago

  • Description updated (diff)

#7 Updated by Yutaka HARA about 2 years ago

  • Target version set to next minor

#8 Updated by Matthew Kerwin about 2 years ago

nobu (Nobuyoshi Nakada) wrote:

Anyway, why don't you make it a gem first?

That's a good idea. Let's see what the uptake is, if any: https://rubygems.org/gems/hashmap

Note: I used #map_keys, #map_values and #map_pairs as my method names.

#9 Updated by Andrew M 10 months ago

FYI, Rails has a method similar to the proposed map_k called transform_keys.

#10 Updated by Nobuyoshi Nakada 10 months ago

  • Duplicated by Feature #9970: Add `Hash#map_keys` and `Hash#map_values` added

#11 Updated by Nobuyoshi Nakada 8 months ago

  • Description updated (diff)

#12 Updated by Thomas Sawyer 8 months ago

An issue with the name is that "map" semantically means to create an Array, i.e.ahash.map{ |k,v| ... } produces an Array. So map_keys would make sense to mean ahash.map_keys{ |k| ... } and produce an Array too. Hash#map_pair would just a synonym for #map, just as #each_pair is just a synonym for #each.

Facets has long had Hash#rekey and Hash#revalue (and in-place forms Hash#rekey! and Hash#revalue!). These names are concise and do not suffer this semantic issue. Note Facets doesn't have a #remap method (though I suppose it could) because it has Enumerable#mash, and it's alias #graph, which can create a Hash from any Enumerable object.

#13 Updated by Tsuyoshi Sawada 7 months ago

Just like there are map and map!, there should be both a non-desctructive and a destructive version for this method.

h = {a: "foo"}

h.non_destructive_one{|k, v| [k.to_s, v.upcase]} #=> {"a" => "FOO"}
h #=> {a: "foo"}

h.destructive_one!{|k, v| [k.to_s, v.upcase]} #=> {"a" => "FOO"}
h #=> {"a" => "FOO"}

I also have a (not that strong) opinion that the block for these methods should take a hash rather than an array. That should make more sense since the return value is a hash.

h.non_destructive_one{|k, v| {k.to_s => v.upcase}} #=> {"a" => "FOO"}
h.destructive_one!{|k, v| {k.to_s => v.upcase}} #=> {"a" => "FOO"}

#14 Updated by Andrew Vit 7 months ago

the block for these methods should take a hash rather than an array.

Do you mean the input should be a single argument with a hash? I don't think that is very consistent for |k, v| expansion.

That should make more sense since the return value is a hash.

Everything inside the block is a tuple; what type the input/output are transformed from/to happens outside the block. IMHO the array makes more sense than the hash inside the block.

#15 Updated by Andrew M 6 months ago

Below is a summary of the different naming proposals so far in this thread, with links to the documentation for real-world implementations where available.

Option 1

The original proposal, uses the term map to express changing the keys or values on the hash, and keeps things terse by abbreviating the terms 'key' and 'value':

  • Hash#map_k
  • Hash#map_k!
  • Hash#map_vs
  • Hash#map_v!
  • Hash#map_kv
  • Hash#map_kv!

Option 2

A clearer, more verbose alternative to option 1. (Proposed by Charlie Somerville.)

Option 3

Given the existing methods Hash#each_key, Hash#each_value, and Hash#each_pair, it might be better to use a sigular alternative to option 2. (Proposed by Nobuyoshi Nakada.)

  • Hash#map_key
  • Hash#map_key!
  • Hash#map_value
  • Hash#map_value!
  • Hash#map_pair
  • Hash#map_pair!

Option 4

Given the potential for the previous options to be confused with Hash#map, which returns an array, it might be best to use an entirely different naming convention. This one is based on Facets, a popular (485,329 downloads on Rubygems) library with the purpose of extending Ruby's core classes with useful methods. (Proposed by Thomas Sawyer.)

Option 5

Similar to option 4, but based on the naming convention used by Ruby on Rails.

#16 Updated by Thomas Sawyer 6 months ago

I can't help but mention it, because it gave me a chuckle....

I like rekey and revalue from #4, because they make sense semantically, don't confuse the idea of map returning an array, and they are concise. Concision is always a big plus. However graph and mash don't really convey much in their names (mash is combination of "map" and "hash" btw), so I've always been rather ho-hum about those, but never could come up with a better, yet still concise, alternative.

Options #2 and #5 are nice for their consistency --the use of _keys, _values and _pairs-- But they lack for concision (especially #5) which sucks, and #2 has the map name issue as mentioned.

So I tried a combination of both ideas using re- as the prefix to the three suffixes and got:

  • rekey
  • revalue
  • repair

At which point the giggles kicked in :-)

#17 Updated by Andrew M 6 months ago

"repair"? Hehe, yeah that's kind of an unfortunate coincidence.

The thing I really like about Option 4's graph and mash is that they are methods on Enumerable, which means they can be used with any Enumerable object, not just hashes. As I mentioned, the creation of a method like that is being discussed in #6669. Right now, a similar effect can be achieved (for the non-destructive method anyway) by chaining map and to_h, so perhaps the full hash transform methods don't provide as big of a benefit over what we have now as rekey and revalue do.

If we do decide to base our names off of the assumption that the full hash transform methods will be on Enumerable, and not Hash, then perhaps something like this might work:

  • Enumerable#associate
  • Hash#associate!

#18 Updated by Martin Dürst 5 months ago

  • Related to Feature #10552: [PATCH] Add Enumerable#frequencies and Enumerable#relative_frequencies added

Also available in: Atom PDF