In Ruby, what do min, max and minmax return on an empty list, with a count argument, and with a one-parameter block?
answer
- empty: nil, minmax [nil, nil]
- max(n) returns an array
- largest first for max(n)
- the block compares two elements
- mixed types: comparison failed
basics
~20 sOn an empty list min and max return nil and minmax returns [nil, nil]. With a count, min(n) and max(n) return arrays, largest first for max. A block must compare two elements like <=>; a one-parameter block silently returns the wrong element.
solid answer
~40 s`min` and `max` compare elements with their own `<=>` and return one **element**, or `nil` for an empty collection; `minmax` returns `[min, max]` in one pass, or `[nil, nil]`. With a count, `min(n)` and `max(n)` return **arrays** of up to `n` elements, ordered from the extreme inward, so `[3, 1, 2].max(2)` is `[3, 2]`, and an empty receiver gives `[]`. With a block, the block is a **comparator**: it receives two elements and must return a negative, zero or positive number. Writing `sales.max { |s| s.amount }` does not raise; the block ignores its second argument, and `max` returns an arbitrary-looking sale. Use a two-argument comparator, `max { |a, b| a.amount <=> b.amount }`, or the key-based `max_by`. Elements that cannot be compared raise `ArgumentError`.
code
ruby · 15 linesSale = Data.define(:region, :amount)
sales = [
Sale.new(region: "north", amount: 300),
Sale.new(region: "south", amount: 120),
Sale.new(region: "north", amount: 50)
]
sales.max { |a, b| a.amount <=> b.amount }.amount # => 300
sales.max { |s| s.amount }.amount # => 50, silently wrong
[3, 1, 2].max(2) # => [3, 2]
[3, 1, 2].min(2) # => [1, 2]
[].max # => nil
[].minmax # => [nil, nil]
[1, "a"].max # ArgumentError: comparison of String with 1 failedgo deeper
Recall that min and max return one element, nil when empty, and that minmax returns both as a pair.
Explain the count form's arrays and order, that the block is a two-element comparator, and the ArgumentError for incomparable values.
Find one-parameter max and min blocks in review, since they return wrong results silently, and guard nil results before formatting.
Prefer key-based comparisons in shared code so intent is explicit, and treat silent-wrong-result APIs as a code-review checklist item.
## What they compare `min`, `max` and `minmax` come from `Enumerable`, with faster native versions on `Array`. Without a block they compare elements using the elements' own **`<=>`** method, the spaceship operator that returns a negative number, zero or a positive number. For a franchise report, the regional totals are integers: ```ruby totals = [350, 120, 900] totals.min # => 120 totals.max # => 900 totals.minmax # => [120, 900] ``` ## Return values at the edges | Call | Normal result | Empty receiver | |---|---|---| | `min`, `max` | one element | `nil` | | `minmax` | `[min, max]` | `[nil, nil]` | | `min(n)`, `max(n)` | array of up to `n` elements | `[]` | Two details about the count form: - `max(2)` returns the **largest first**: `[3, 1, 2].max(2)` is `[3, 2]`. `min(2)` returns the smallest first, `[1, 2]`. - The argument is a **count**, converted to an Integer. Passing a Symbol, as in `sales.max(:amount)`, raises `TypeError` (no implicit conversion of Symbol into Integer); it is not a way to name a field. The rdoc notes that the ordering of equal elements is indeterminate, so ties should not be relied on. ## The block is a comparator, not a key With a block, the block replaces `<=>`. It is called with **two** elements, `a` and `b`, and must return a number that says which is smaller: ```ruby sales.max { |a, b| a.amount <=> b.amount } ``` The common mistake is to write a one-parameter block, as if the block picked the value to compare: ```ruby sales.max { |s| s.amount } ``` This does **not** raise. A block ignores extra arguments, so it receives `a` and returns `a.amount`, a positive number that `max` reads as "`a` is greater than `b`" on every comparison. The result is simply the wrong sale. In a report, that bug survives until someone checks a number by hand. The fix is either a real comparator, as above, or the key-based methods `max_by`, `min_by` and `minmax_by`, which take a one-parameter block. ## Comparing values that do not compare If two elements have no meaningful `<=>`, for example an Integer and a String, the comparison returns `nil` and Ruby raises **`ArgumentError`** with a "comparison of ... failed" message. `nil` values in the data trigger the same error. Cleaning the input first, or comparing a derived value that is always present, avoids it. ## `minmax` in one pass `minmax` returns both extremes at once. Besides being shorter than calling `min` and then `max`, it walks the collection **once**, which matters for large collections and is the only option for a source that can be read only once. For a range of report values, such as the smallest and largest regional total, it is the natural call. ## Other receivers The same rules apply to any `Enumerable`, and the element type decides what "smallest" means: - On a **Hash**, the elements are `[key, value]` pairs, and pairs compare key first. `{north: 350, south: 120}.min` returns `[:north, 350]` because `:north` sorts before `:south`, not because 350 is small. - **Strings** compare character by character, so `["9", "10"].max` is `"9"`. Numeric text read from a file needs converting before it is compared. - **Arrays** compare element by element, which is what makes multi-part comparisons possible when the parts are comparable. In each case the fix for a surprising answer is the same: compare the value you actually mean, through a comparator block or a key-based method. ## Summary - `nil` (or `[nil, nil]`, or `[]`) on an empty receiver: guard before formatting. - `max(n)` returns an array, largest first. - The block compares two elements; a one-argument block returns a wrong answer without an error. - Mixed or `nil` elements raise `ArgumentError`. These are exactly the edges interviewers probe, because each one appears in real reporting code.
- Why does sales.max { |s| s.amount } not raise an error?A block ignores surplus arguments, so the comparator call passes `a` and `b` and the block uses only `a`. It returns `a.amount`, a positive Integer, which `max` interprets as "a is greater than b" every time. The comparisons are consistent enough to finish, so a wrong element comes back without any error.
- When would you use minmax instead of calling min and max separately?Whenever you need both: `minmax` walks the collection once and returns `[min, max]`, while two calls walk it twice. For a source that can only be read once, such as an enumerator over a file, two separate calls would not even see the same data.
saying these in an interview costs you the question
- max with a one-parameter block compares by the block's value
- [].max raises an error on an empty array
- max(2) returns the two largest in ascending order
- max(:amount) finds the element with the largest amount
- minmax on an empty array returns nil