In Ruby 4.0, how do Array#first, last, take and drop differ in what they return, and what does the new Array#rfind add?
answer
- no argument: element or nil
- with a count: always an array
- take requires its argument
- negative count: ArgumentError
- rfind arrived in 4.0
basics
~20 sfirst and last with no argument return one element or nil; with a count they, like take and drop, return a new array and never nil. Ruby 4.0 adds Array#rfind, which returns the last element matching a block.
solid answer
~40 s`first` and `last` without an argument return a **single element**, or `nil` on an empty array. With a count, `first(2)` and `last(2)` return a **new array** of up to that many elements, `[]` when the array is empty. `take(n)` returns the first `n` elements like `first(n)` but requires its argument; `drop(n)` returns everything **except** the first `n`. None of them modify the receiver. Oversized counts are clamped, and negative counts raise `ArgumentError` (`negative array size` from `first`, `attempt to take negative size` from `take`). Ruby 4.0 added `Array#rfind`, which scans from the end and returns the **last** element for which the block is truthy, or `nil`; it is a more efficient alternative to `reverse_each.find`.
code
ruby · 17 linesrow = %w[A1 A2 A3 A4]
row.first # => "A1"
row.first(2) # => ["A1", "A2"]
row.last(2) # => ["A3", "A4"]
row.take(10) # => ["A1", "A2", "A3", "A4"]
row.drop(3) # => ["A4"]
row # => ["A1", "A2", "A3", "A4"]
[].first # => nil
[].first(1) # => []
row.take(-1) # ArgumentError: attempt to take negative size
# Ruby 4.0
seats = %w[A1 B1 A2 B2]
seats.rfind { |s| s.start_with?("A") } # => "A2"
seats.rindex { |s| s.start_with?("A") } # => 2go deeper
Recall that a count argument turns first and last into array-returning methods, and that drop returns the remainder, not the dropped part.
Explain nil versus empty-array returns, clamping of large counts and the ArgumentError on negative ones, and that none of these methods mutate.
Pick the form whose empty-array behaviour suits the caller, and adopt rfind only where every deployment runs Ruby 4.0.
Plan when new core methods like rfind can be used across services, since code relying on them fails on any app still on Ruby 3.4.
## One element or an array The methods that read from the ends of a Ruby array split on one question: **did you pass a count?** | Call | Returns | On an empty array | |---|---|---| | `row.first` | one element | `nil` | | `row.first(2)` | new array of up to 2 elements | `[]` | | `row.last` | one element | `nil` | | `row.last(2)` | new array of up to 2 elements | `[]` | | `row.take(2)` | new array, same as `first(2)` | `[]` | | `row.drop(2)` | new array without the first 2 | `[]` | The difference in return type matters. `row.first.upcase` raises `NoMethodError` on an empty row because `first` returned `nil`, while `row.first(1).each` simply does nothing. ## take and drop - **`take(n)`** returns the first `n` elements. It behaves like `first(n)` but its argument is required. - **`drop(n)`** is the complement: all elements after the first `n`. `%w[A1 A2 A3].drop(1)` is `["A2", "A3"]`. `take` and `drop` are also defined by `Enumerable`, so the same names work on ranges and other collections. ## The edges 1. **Count larger than the array**: clamped. `row.first(50)` and `row.take(50)` return the whole array. 2. **Count of zero**: `[]`. 3. **Negative count**: `ArgumentError`. `first(-1)` and `last(-1)` report `negative array size`; `take(-1)` reports `attempt to take negative size`; `drop(-1)` reports `attempt to drop negative size`. A negative count does not mean "from the end". 4. **Receiver**: never modified. `drop(1)` is not `shift`; the original array keeps all its elements. ## Array#rfind in Ruby 4.0 Before Ruby 4.0, finding the **last** element matching a condition meant `row.reverse.find { ... }`, which copies the array, or `row.reverse_each.find { ... }`, which goes through an enumerator. Ruby 4.0 added `Array#rfind` as a more efficient alternative: - It calls the block with elements **from the end backwards** and returns the first one for which the block is truthy, which is the last match in array order. - With no match it returns `nil`, or the result of an optional `if_none_proc` argument: `row.rfind(proc { :none }) { ... }`. - Without a block it returns an `Enumerator`. It returns an **element**, not an index; `rindex` is the method that returns the index of the last match. Ruby 4.0 also gave `Array` its own `find`, a faster override of `Enumerable#find`, so `find` and `rfind` are now a matched pair on arrays. ## Finding the last match: the options compared | Expression | Returns | Extra work | |---|---|---| | `row.reverse.find { ... }` | last matching element | builds a reversed copy of the array | | `row.reverse_each.find { ... }` | last matching element | goes through an enumerator | | `row.rfind { ... }` (4.0) | last matching element | none, walks from the end | | `row.rindex { ... }` | index of the last match | none | On a codebase that must still run on Ruby 3.4, keep `reverse_each.find`; calling `rfind` there raises `NoMethodError`. ## Counts that are not integers The count is converted to an integer, so `row.take(2.1)` behaves like `row.take(2)`. Passing `nil` or a string raises `TypeError`, which usually signals a count that came from unparsed input. ## Choosing in a theatre booking app - The first seat in a row: `row.first`, and handle `nil` for an empty row. - The first three seats to show in a preview: `row.first(3)`, which never returns `nil`. - Everything after the aisle seat: `row.drop(1)`. - The last seat in a row not yet booked: `row.rfind { |seat| !booked.include?(seat) }` on Ruby 4.0.
- Why can code that works with row.first(1) fail when changed to row.first?`first(1)` always returns an array, `[]` for an empty row, so chained calls like `.each` or `.map` just do nothing. `first` with no argument returns the element itself or `nil`, so a following method call such as `.upcase` raises `NoMethodError` on an empty row.
- How is Array#rfind different from rindex and from reverse.find?`rfind` returns the last matching **element**; `rindex` returns its **index**. `reverse.find` gives the same element as `rfind` but first builds a reversed copy of the whole array. `rfind` walks from the end directly. It exists only on Ruby 4.0 and later; on 3.4 use `reverse_each.find`.
saying these in an interview costs you the question
- first(1) returns the element itself, just like first
- take removes the elements it returns from the array
- drop(2) returns the two elements it dropped
- first(-1) returns the last element of the array
- rfind returns the index of the last match, like rindex
- Array#rfind has been available since Ruby 3.x