Project

General

Profile

Actions

Feature #15975

closed

Add Array#pluck

Feature #15975: Add Array#pluck

Added by lewispb (Lewis Buckley) over 6 years ago. Updated almost 5 years ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:93486]

Description

Inspired by https://github.com/rails/rails/issues/20339

While developing web applications I've often wanted to quickly extract an array of values from an array of hashes.

With an array of objects, this is possible:

irb(main):001:0> require 'ostruct' => true irb(main):002:0> [OpenStruct.new(name: "Lewis")].map(&:name) => ["Lewis"] 

This PR adds Array#pluck allowing this:

irb(main):001:0> [ {name: "Lewis"} ].pluck(:name) => ["Lewis"] 

without this PR:

irb(main):001:0> [ {name: "Lewis"} ].map { |item| item[:name] } => ["Lewis"] 

Implemented here:

https://github.com/ruby/ruby/pull/2263

Updated by shevegen (Robert A. Heiler) over 6 years ago Actions #1 [ruby-core:93495]

Hmm. I don't doubt that this may possibly be useful, but the method name is
a bit ... weird. My first association with this name, oddly enough, is to
associate duck typing with it, and then to "pluck the duck" (yes, strange
association but I could not help it ...).

I do not have a better alternative suggestion for a name, though. It
reminds me a little bit of a more flexible variant of .dig(), though.

Updated by inopinatus (Joshua GOODALL) over 6 years ago Actions #2 [ruby-core:93811]

I think that's pretty limited. #pluck is a fairly crude method, fine for Rails but hardly befitting the Ruby standard library. I'd much rather use a higher-order function and get somewhere much more interesting.

By instead implementing Array#to_proc (which doesn't currently exist) as something that applies to_proc to its own elements, before invoking them with passed-in arguments:

class Array def to_proc Proc.new do |head, *tail| collect(&:to_proc).collect do |ep| ep_head = ep[head] tail.empty? ? ep_head : [ep_head] + tail.collect(&ep) end end end end 

we can now do some nice things, including a pluck equivalent (and more besides) but using only existing syntax:

# data people = [{name: "Park", age: 42}, {name: "Lee", age: 31}] keys = people.flat_map(&:keys).uniq # single item extraction :name.then &people #=> ["Park", "Lee"] and equivalent to people.to_proc[:name] #=> ["Park", "Lee"] # multiple item extraction keys.then &people #=> [["Park", 42], ["Lee", 31]] and equivalent to people.to_proc[:name, :age] #=> [["Park", 42], ["Lee", 31]] # multiple method invocation :name.then(&people).map(&[:upcase, :length]) #=> [["PARK", 4], ["LEE", 3]] # use with struct-like objects, and bonus inline lambda: people.map(&OpenStruct.:new).map &[:name, :age, ->{ Digest::SHA2.hexdigest @1.name }] 

Could work as Enumerable#to_proc instead.

Updated by osyo (manga osyo) over 6 years ago Actions #3 [ruby-core:93812]

we can now do some very nice things just with existing syntax:

The sample code is invalid.
Is this?

class Array def to_proc Proc.new do |head, *tail| collect(&:to_proc).collect do |ep| ep_head = ep[head] tail.empty? ? ep_head : [ep_head] + tail.collect(&ep) end end end end # data people = [{name: "Park", age: 42}, {name: "Lee", age: 31}] # single item extraction p :name.then &people #=> ["Park", "Lee"] p people.to_proc[:name] #=> ["Park", "Lee"] # multiple item extraction p [:name, :age].then &people #=> [["Park", 42], ["Lee", 31]] p people.to_proc[:name, :age] #=> [["Park", 42], ["Lee", 31]] # multiple invocation names = ["Park", "Lee"] p names.map(&[:upcase, :length]) #=> [["PARK", 4], ["LEE", 3]] 

https://wandbox.org/permlink/4oVOzULhwKsu4gB5

Updated by inopinatus (Joshua GOODALL) over 6 years ago Actions #4 [ruby-core:93819]

My apologies, yes, there was a cut-and-paste error on show for a few minutes, and you were quick enough to see it. It's now the code I intended to paste.

Updated by knu (Akinori MUSHA) about 6 years ago Actions #5 [ruby-core:94728]

ActiveSupport has Enumerable#pluck, so I don't think we want to diverge from that by adding a method with the same name in Array.

Updated by matz (Yukihiro Matsumoto) about 6 years ago Actions #6 [ruby-core:94730]

I am not positive for Array#pluck. ActiveSupport may add the method.

Matz.

Updated by connorshea (Connor Shea) almost 5 years ago Actions #7 [ruby-core:101265]

I was going to suggest the same thing because I think it's a very useful shorthand! Here's an example I run into a lot when manipulating data in my Ruby scripts.

# Lets say I have an array of hashes representing video games (this is pretty # common because I write a decent amount of scripts manipulating data in Ruby). games = [ { title: "Half-Life 2", steam_id: 1 }, { title: "Portal", steam_id: 2 }, { title: "Portal 2", steam_id: 3 } ] # If I want to get the Steam IDs for all those, for example to match this # dataset with another dataset to find overlaps, I need to use a `map` like # this: games.map { |game| game[:steam_id] } #=> [1, 2, 3] # That code above doesn't really spark joy, it's pretty lengthy for something # that should be very simple. # What I _want_ to do is something like this, but since these are just hash # keys, I can't: games.map(&:steam_id) #=> undefined method `steam_id' # The best solution would be a `#pluck` method: games.pluck(:steam_id) #=> [1, 2, 3] # This sparks joy! 

Please consider adding a #pluck method on Enumerable 🙇‍♂️

Ideally it'd accept more than one argument to get multiple values at once, but that's not really a deal-breaker for me if we don't include it.

Maybe it could be called #pick, #each_dig, #map_keys, or something else?

Updated by marcandre (Marc-Andre Lafortune) almost 5 years ago Actions #8 [ruby-core:101268]

  • Status changed from Open to Rejected

Matz has already stated that it's a no, so I will close this.

I'll add that the issue should not be about a shorthand to map and calling [] or dig, but how to write concisely {|game| game[:steam_id]}. As @inopinatus said, maybe a variant of to_proc could make this more concise, but that is what _1 is for. This is short and concise:

games.map{ _1[:steam_id] } 

Updated by phluid61 (Matthew Kerwin) almost 5 years ago Actions #9 [ruby-core:101272]

Apologies for posting to a closed ticket, but here's a thought in case it helps someone propose something else in future: partial application in #to_proc, e.g. games.map(&(:[], :steam_id))

I hate the syntax I just invented, but the idea of partial application to the right (i.e. applying args to a proc before applying the receiver) is interesting.

Actions

Also available in: PDF Atom