Idiomatic way to get second last, third last, fourth last elements of a vector/array/slice

What is the best way to get second last, third last, and fourth last elements of a vector/array/slice of unknown length? The fewer lines of code the better.

There are cases where the second last exists but not the third last and the fourth last, so I prefer to get them separately, as Options.

I don't require a generic solution, only second, third, and fourth last.

Lastly, the code shouldn't panic.

Those specifically or from the end more generally? One of those arbitrarily, all at once, sequentially? By reference, cloned, or owning? Graceful errors or panics on short inputs?

Here's a one liner for one combination (all of those specifically at once, by reference, might panic):

let s = &v[v.len() - 4 .. v.len() - 1];

I have updated my question.

Again going for brevity:

// Is an `Option<_>`, replace 3 with 1 for last, 2 for penultimate, etc.
// Will still panic if you use 0 though
let maybe_third_last = v.len().checked_sub(3).map(|i| v[i]);
let a = vec.get(vec.len().wrapping_sub(2));
let b = vec.get(vec.len().wrapping_sub(3));
let c = vec.get(vec.len().wrapping_sub(4));

If the length is too small, this will wrap around and the index will be far-far out of range, so get will return None.

7 Likes
let mut rev = xs.iter().rev();
let (_, second, third, fourth) = (rev.next(), rev.next(), rev.next(), rev.next());

(Be careful generalizing this to other iterators, because not all iterators will continue to yield None after the first time; so with an arbitrary iterator you might get Somes in third and fourth even if second is a None. But Rev<slice::Iter> is a FusedIterator, so it is guaranteed to work the way you want.)

10 Likes

Since OP mentioned slices not iterators, pattern matching would be the shortest I guess.

let (a, b, c) = match &arr[..] {
    [.., a, b, c, _] => (a, b, c),
    _ => panic!("array shorter than 4"),
};

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8976b292814a528a1b959f98020a289e

23 Likes
let len = arr.len();
let results = arr[len.saturating_sub(4)..len.saturating_sub(1)];

Another approach that doesn't use iterators.

EDIT: Skip this post -- steffahn points out in the post below that it's not meeting the requirements.

With rev_slice — Rust library // Lib.rs,

if let [c, b, a] = v.rev()[1..4].rev() {

(But really I'd vote for the pattern in Hyeonu's solution.)

but this doesn’t match the requirement of

2 Likes

I think that means the recommendation should just be

let mut rev = xs.iter().rev().fuse();
let (_, second, third, fourth) = (rev.next(), rev.next(), rev.next(), rev.next());

even for vectors and slices, because there's no overhead for the .fuse() on them (thanks to specialization and layout optimization) and that way it will continue to work correctly if a non-fused iterator somehow shows up -- not to mention that it will emphasize to the reader that you're intending to use this behaviour.

12 Likes