Skip to content

/commands/ruby/sort-array-of-hashes-by-value

ruby sort array of hashes by value (sort_by, min_by, max_by)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
Ruby
versions
Ruby 1.9+, Ruby 3.4
os
any

people.sort_by { |h| h[:age] } sorts an array of hashes by the value of one key and returns a new array. Use an array as the key to sort by several fields (sort_by { |h| [h[:team], h[:name]] }), .reverse or a negated number for descending order, and sort_by! to sort in place. min_by and max_by return a single element.

Examples

The example runs CRuby 3.4 compiled to WebAssembly (ruby.wasm, the official Ruby project build) with Node.js:

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund @ruby/3.4-wasm-wasi@2.8.1 @ruby/wasm-wasi@2.8.1 >/dev/null 2>&1
cat > ruby.mjs <<'EOF'
import fs from "node:fs/promises";
import { DefaultRubyVM } from "@ruby/wasm-wasi/dist/node";
const wasm = await WebAssembly.compile(await fs.readFile("node_modules/@ruby/3.4-wasm-wasi/dist/ruby+stdlib.wasm"));
const { vm } = await DefaultRubyVM(wasm);
vm.eval(await fs.readFile(process.argv[2], "utf8") + "\n$stdout.flush");
EOF
cat > sort.rb <<'EOF'
people = [
  { name: "zoe", age: 31, team: "ops" },
  { name: "Adam", age: 25, team: "dev" },
  { name: "bea", age: 31, team: "dev" },
  { name: "Carl", age: 19, team: "ops" },
]
names = ->(list) { list.map { |h| h[:name] }.join(", ") }

puts "age:            " + names.(people.sort_by { |h| h[:age] })
puts "age desc:       " + names.(people.sort_by { |h| -h[:age] })
puts "team, then age: " + names.(people.sort_by { |h| [h[:team], h[:age]] })
puts "name:           " + names.(people.sort_by { |h| h[:name] })
puts "name, any case: " + names.(people.sort_by { |h| h[:name].downcase })
puts "oldest:         " + people.max_by { |h| h[:age] }[:name]
puts "original:       " + names.(people)

begin
  [{ age: 3 }, { age: nil }].sort_by { |h| h[:age] }
rescue ArgumentError => e
  puts "#{e.class}: #{e.message}"
end
EOF
node --no-warnings ruby.mjs sort.rb

Output

age:            Carl, Adam, zoe, bea
age desc:       zoe, bea, Adam, Carl
team, then age: Adam, bea, Carl, zoe
name:           Adam, Carl, bea, zoe
name, any case: Adam, bea, Carl, zoe
oldest:         zoe
original:       zoe, Adam, bea, Carl
ArgumentError: comparison of Integer with nil failed

Strings sort by byte value, so capitalised names come before lowercase ones; downcase in the key gives alphabetical order. A nil among the values cannot be compared with an Integer and raises ArgumentError.

Patterns

CodeResult
arr.sort_by { |h| h[:age] }Ascending by one key
arr.sort_by { |h| -h[:age] }Descending, numeric keys only
arr.sort_by { |h| h[:name] }.reverseDescending for any key type
arr.sort_by { |h| [h[:team], h[:age]] }Several keys; arrays compare element by element
arr.sort { |a, b| [a[:team], b[:age]] <=> [b[:team], a[:age]] }Team ascending, age descending
arr.sort_by { |h| h[:age] || 0 }Treat missing values as 0
arr.min_by / arr.max_by { |h| h[:age] }Only the smallest or largest element
arr.sort_by!(&:size)In place, with a method name as key

Pitfalls

  • String vs Symbol keys. Hashes from JSON.parse have String keys; h[:age] is then nil for every element, so nothing is really sorted (and no error is raised). When only some values are nil, the sort raises comparison of Integer with nil failed, as in the example. Use h["age"] or parse with symbolize_names: true (see parse JSON).
  • Mixed types. Integers and Strings cannot be compared with each other (comparison of String with Integer failed); convert first.
  • Missing elements. Calling a method on a missing value inside the block, such as h[:name].downcase, raises undefined method for nil. Use h[:name].to_s.downcase.
  • Stability. Elements with equal keys may change order; add the index as a final key if order matters.

Confirm it worked

Map the sorted result to the key (sorted.map { |h| h[:age] }) and check it is in order; for long lists, sorted.each_cons(2).all? { |a, b| a[:age] <= b[:age] } returns true.

FAQ

How do I sort in descending order?

Negate a numeric key (sort_by { |h| -h[:age] }), call .reverse on the result, or use sort { |a, b| b[:age] <=> a[:age] }. For strings, reverse or the sort block are the options.

Is sort_by stable in Ruby?

No, the documentation states that the result is not guaranteed to be stable. Add a tie-breaker, for example sort_by.with_index { |h, i| [h[:age], i] }, to keep the original order of equal keys.

Does sort_by change the original array?

No, it returns a new array. sort_by! sorts the array in place.

Sources