Feature #16739
openAllow Hash#keys and Hash#values to accept a block for filtering output
Description
I often see code like the following:
hash.select { |_, v| v == :some_value }.keys
hash.keys.select { |k| k.nil? || k.even? }
hash.select { |k, v| valid_key?(k) && valid_value?(v) }.values
Each of these code snippets must allocate an intermediate data structure. I propose allowing Hash#keys and Hash#values to accept optional block parameters that take both key and value. For example, the above code could be rewritten as:
hash.keys { |_, v| v == :some_value }
hash.keys { |k, _| k.nil? || k.even? }
hash.values { |k, v| valid_key?(k) && valid_value?(v) }
This behavior:
- Does not break any existing code (since 
Hash#keysandHash#valuesdo not currently accept blocks). - Is very readable—it's obvious what it does at a glance.
 - Is more efficient than current alternatives.
 - Is more concise than current alternatives.
 - Is flexible and useful in a variety of scenarios, because the block has access to both key and value (unlike the behavior proposed in #14788).
 
        
          
          Updated by sawa (Tsuyoshi Sawada) over 5 years ago
          
          
        
        
      
      All it does is saves you from typing select. It does not look like the proposed feature makes much difference unless such situation is frequently met. Do you have any use case?
Also, the purpose of a block following the methods keys and values does not seem to be immediately clear.
        
          
          Updated by mame (Yusuke Endoh) over 5 years ago
          
          
        
        
      
      I doubt if it is obvious.  See the following code.  I believe that many people expect .map.
hash.keys {|k| k.to_s }
If you want to avoid an intermediate array, you may want to use .each_key or .filter_map.
hash.each_key.select {|k| k.nil? || k.even? }
hash.filter_map {|k, v| k if valid_key?(k) && valid_value?(v) }
Personally I like the following explicit code, though.
keys = []
hash.each do |k, v|
  keys << k if valid_key?(k) && valid_value?(v)
end
        
          
          Updated by jacobevelyn (Jacob Evelyn) over 5 years ago
          
          
        
        
      
      All it does is saves you from typing
select. It does not look like the proposed feature makes much difference unless such situation is frequently met. Do you have any use case?
I see code that could be improved with this all the time, including in projects like Ruby, JRuby, Rails, Discourse, GitLab, Metasploit, Active Admin, and Airbnb's Nerve (see links).
As I mentioned in my original post, it does not just save you from typing select—it also avoids an unnecessary Hash allocation, making it more efficient as well as more concise.
If you want to avoid an intermediate array, you may want to use
.each_keyor.filter_map.
Many of these use cases aren't supported by .each_key or .each_value because they require looking at the one that's not being returned (or both keys and values). Honestly I wasn't aware of .filter_map; you're right that it's an option but I find it a bit verbose and hard to read.
I doubt if it is obvious. See the following code. I believe that many people expect
.map.
Obviously we can disagree about this. I think about it like Enumerable#count with a param vs. a block—the method's meaning doesn't change, but it becomes more flexible and powerful when you use a block.
        
          
          Updated by Eregon (Benoit Daloze) over 5 years ago
          
          
        
        
      
      Thank you for the examples and links.
filter_map looks good enough for this to me.
In general I think we avoid adding blocks to core methods, because indeed it's not clear if people expect #map, #select or #filter_map behavior, and it's so much clearer with the explicit call.
        
          
          Updated by sawa (Tsuyoshi Sawada) over 5 years ago
          
          
        
        
      
      Includes a duplicate of #14788.
        
          
          Updated by jacobevelyn (Jacob Evelyn) over 5 years ago
          
          
        
        
      
      sawa (Tsuyoshi Sawada) wrote in #note-5:
Includes a duplicate of #14788.
I just want to note that this is proposal is for a more powerful feature than what's proposed in #14788, because both the key and value would be available to the block.
Eregon (Benoit Daloze) wrote in #note-4:
In general I think we avoid adding blocks to core methods, because indeed it's not clear if people expect #map, #select or #filter_map behavior, and it's so much clearer with the explicit call.
That's fair! Would calling these methods filter_keys/filter_values or select_keys/select_values be more explicit?