Extract Unique Values From An Array Of Hashes By A Key In Ruby
You have a list of records and you want every distinct value of one field — all the account ids in a batch, every category present in a result set, the set of statuses currently in use. It is a map followed by a uniq, with a couple of refinements worth knowing.
Description
map { |h| h[:key] }.uniq is the direct form. filter_map does both in one pass and drops nils, which is usually what you want when the key is optional.
For a large collection where you only need membership testing afterwards, build a Set instead — uniq is O(n) but Array#include? is O(n) per lookup while Set#include? is effectively constant.
To deduplicate case-insensitively or on a normalized form, pass a block to uniq rather than mutating the source data.
Sample input:
records = [
{ id: 1, category: "ruby", author: "Ada" },
{ id: 2, category: "rails", author: "Grace" },
{ id: 3, category: "ruby", author: nil }
]
Sample Output:
# => ["ruby", "rails"]
Answer
records.map { |r| r[:category] }.uniq
# => ["ruby", "rails"]
# One pass, and nils dropped
records.filter_map { |r| r[:author] }.uniq
# => ["Ada", "Grace"]
# Symbol-to-proc when the records respond to the method
orders.map(&:status).uniq
# Case-insensitive distinct values
records.filter_map { |r| r[:category] }.uniq(&:downcase)
# Set — much faster for repeated membership checks
require "set"
categories = records.filter_map { |r| r[:category] }.to_set
categories.include?("ruby") # => true
# Distinct values with their counts
records.filter_map { |r| r[:category] }.tally
# => { "ruby" => 2, "rails" => 1 }
# Distinct across two keys combined
records.map { |r| [r[:category], r[:author]] }.uniq
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀