Project

General

Profile

Feature #10208

Passing block to Enumerable#to_h

Added by yhara (Yutaka HARA) over 2 years ago. Updated 4 months ago.

Status:
Feedback
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:64808]

Description

Now that we can convert 'a list of [key, value] pairs' into a hash with Enumerable#to_h,
how about make it take a block to specify 'how to convert each element into a [key, value] pair'?

Example:

# Convert users into an {id => name} hash
users.map{|u| [u.id, u.name]}.to_h
    ↓
# Convert users into an {id => name} hash
users.to_h{|u| [u.id, u.name]}

This could also be a solution for these feature requests:

  • Feature #6669 A method like Hash#map but returns hash

    hsh.apply{|k, v| [k.to_s, v]}
    == hsh.to_h{|k, v| [k.to_s, v]}
    
  • Feature #7793 New methods on Hash
    Feature #9970 Add Hash#map_keys and Hash#map_values

    hsh.map_k(&:to_s)
    == hsh.to_h{|k, v| [k.to_s, v]}
    hsh.map_v(&:to_i)
    == hsh.to_h{|k, v| [k, v.to_i]}
    hsh.map_kv(&block)
    == hsh.to_h(&block)
    

Related issues

Related to Ruby trunk - Feature #12512: Import Hash#transform_values and its destructive version from ActiveSupport Closed

History

#1 [ruby-core:64812] Updated by nobu (Nobuyoshi Nakada) over 2 years ago

The name to_h doesn't feel nice for it, IMHO.

#2 [ruby-core:64813] Updated by matz (Yukihiro Matsumoto) over 2 years ago

I agree with Nobu.

Matz.

#3 [ruby-core:64929] Updated by marcandre (Marc-Andre Lafortune) over 2 years ago

I agree to_h isn't the right method to do this.

I completely agree that we need new methods to do this.

This would also be a solution for #4151, and the multiple other proposals related to it.

#4 [ruby-core:64947] Updated by sawa (Tsuyoshi Sawada) over 2 years ago

I think the creation of intermediate arrays for each pair is waste of resource. Can we use combination of two methods? For example, something like:

users.with_keys(&:id).with_values(&:name)

or

users.with_values(&:name).with_keys(&:id)

Order of application of the two methods with_keys and with_values should not matter. When only one of them has applied, the return value should be something like an enumerator. As soon as both methods have applied, a hash should be returned.

#5 [ruby-core:64967] Updated by yhara (Yutaka HARA) over 2 years ago

Marc-Andre Lafortune wrote:

I completely agree that we need new methods to do this.

Thanks. One idea is name it Enumerable#hash_by (like max_by, group_by)

User = Struct.new(:id, :name)
users = [User.new(1, "Alice"), User.new(2, "Bob")]

users.hash_by{|u| [u.id, u.name]}
# {1 => "Alice", 2 => "Bob"}

#6 [ruby-core:64968] Updated by matz (Yukihiro Matsumoto) over 2 years ago

We have #max and #max_by. When we have #hash and #hash_by, people may expect something else.
But it may not matter much. Or maybe we need to rename #hash to #hashcode (off topic here).

Matz.

#7 [ruby-core:76102] Updated by knu (Akinori MUSHA) 9 months ago

I like this proposal. I think it is reasonable for to_h to take an optional block to specify how to make a hash from a given enumerable object because most enumerable objects are not composed of two element arrays. (IIRC that was the reason why Matz didn't like the idea of adding the method when it was first proposed)

The said use case users.map{|u| [u.id, u.name]}.to_h is only as effective as Hash[users.map{|u| [u.id, u.name]}], and that spoils the usefulness of the method defined in Enumerable. users.lazy.map{|u| [u.id, u.name]}.to_h may be cool in that it does not create an intermediate array object, but it still creates a couple of chained lazy enumerable objects and looks far from being concise and straightforward.

What do you guys think? I don't think we need a new name for this.

#8 [ruby-core:76460] Updated by shyouhei (Shyouhei Urabe) 8 months ago

We looked at this issue at yesterday's developer meeting and had consensus that there is no other example of to_* method that takes a block. Introducing such new concept seems too risky.

#9 [ruby-core:77751] Updated by knu (Akinori MUSHA) 5 months ago

So, we need a different name for this.

Here's some candidates I can think of:

  • hash_by (proposed above)
  • to_h_by
  • hash_map
  • map_h
  • map_to_h

#10 Updated by shyouhei (Shyouhei Urabe) 5 months ago

  • Related to Feature #12512: Import Hash#transform_values and its destructive version from ActiveSupport added

#11 [ruby-core:77756] Updated by shyouhei (Shyouhei Urabe) 5 months ago

  • Description updated (diff)

Not a direct translation of the proposed method, but Hash#transform_values is now available. https://bugs.ruby-lang.org/issues/12512

#12 [ruby-core:78524] Updated by shyouhei (Shyouhei Urabe) 4 months ago

  • Status changed from Open to Feedback

FYI if there are people who need this functionality: so far we have not found an appropriately sounding name of this method. It should be a method of Enumerable (not only Hash), that takes a block, and returns a Hash.

Also available in: Atom PDF