Feature #15975
closedAdd Array#pluck
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:
Updated by shevegen (Robert A. Heiler) over 6 years ago
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
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
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]]
Updated by inopinatus (Joshua GOODALL) over 6 years ago
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
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
I am not positive for Array#pluck. ActiveSupport may add the method.
Matz.
Updated by connorshea (Connor Shea) almost 5 years ago
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
- 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
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.