I subscribe to a lovely newsletter that, among other things, presents readers with a coding challenge every week.

I had some fun with last week’s, so I thought I’d do this again, using Rust. So here’s this week’s question:

Given an array that was once sorted in ascending order is rotated at some pivot unknown to you beforehand (so [0,2,4,7,9] might become [7,9,0,2,4], for example). Find the minimum value in that array in O(n) or less.

The way my brain works, this is what jumped at me straight away:

fn find_value_of_drop(arr: &[usize]) -> usize {
    // start loop at 1 so we can always look back one space
    for i in 1..arr.len() {
        // compare this element to the one before, looking for a "drop" in value
        if arr[i] < arr[i - 1] {
            // if we found a drop, we know this element is the minimum value
            return arr[i];
        }
    }
    // If we made it here and still haven't found the drop, we know the array is 
    // such that the first element is the minimum value, so we'll return that
    arr[0]
}

(In fact, I first wrote is as a while loop that ended with a i = i + 1 because even though I haven’t written C++ for 20 years, it’s how I learned loops.)

The trick is that we can find the pivot by finding the first element where the previous element is greater. And once we find the pivot, we have also found the minimum value of the array.

I’m pretty sure that code above works – you can test it yourself against a few tests I wrote.

Using enumerate

After going through this whole thing I realize now that we can use a little Rust to make this a little nicer without disturbing the underlying logic by using enumerate, which I think is similar to Ruby’s “each with index”:

fn find_value_of_drop_more_elegant(arr: &[usize]) -> usize {
    for (i, item) in arr.iter().enumerate() {
        if let Some(n) = arr.get(i + 1) {
            if item > n {
                return *n;
            }
        }
    }
    arr[0]
}

I like this solution!

But, as I managed to do last week, I wanted to see if there were other, potentially more elegant/Rust-y way to look at this problem. First, I tried cycle, thinking it would help make the problems of (a) running out of elements in the array and (b) checking the first element a bit easier. But I don’t think cycle really helps here.

Peeking

So I looked to peekable / peek, an iterator I think I had tried to use before without success.

I think the fundamental issue for me with these higher level iterators like peek – specifically the ones that return iterators – is that the examples the documentation gives all use next() to move through the example array slice. Here’s the peekable/peek example:

let xs = [1, 2, 3];

let mut iter = xs.iter().peekable();

// peek() lets us see into the future
assert_eq!(iter.peek(), Some(&&1));
assert_eq!(iter.next(), Some(&1));

assert_eq!(iter.next(), Some(&2));

While what I almost always am in need of is a sturdy example that makes use of a loop like for or while. (An exception to this is the nice and concise examples for iterators that return a bool, like all and any, and those that return something other than an iterator, like sum.) How often do I only want to next a couple of times? My only guess is that there’s some general way to loop these specialized iterators.

My instincts (most likely from Ruby, but also from how we used enumerate above) is to want to write something like for (this_element, next_element) in arr.iter().peekable() { and then have access to this_element and an Option of next_element in the block (since if you’re at the end of the array slice you won’t have a next_element). But, of course, this isn’t Ruby.

After some haphazard Googling, I found a forum post (I’ve since lost track of) and adapted to this beast, which passed my tests:

fn find_value_of_drop_more_elegant(arr: &[usize]) -> usize {
    let mut peekable_arr = arr.iter().peekable();
    while let Some(l) = peekable_arr.next() {
        match peekable_arr.peek() {
            Some(next_element) => {
                if l > next_element {
                    return **next_element;
                }
            }
            None => return arr[0],
        };
    }
    arr[0]
}

But, phew, at first glance I did not like it, nor did I fully understand it (I still might not).

First of all it’s longer and denser than my first solution (I thought these where supposed to be higher level iterators!). And to me it doesn’t look very approachable or readable, especially at first look, particularly that second line, which uses both a while let and next, a pattern I wasn’t familiar with.

And despite the borrow checker and I reaching some sort of détente about a year ago, that **next_element causes me to raise an eyebrow – usually a sign something is off. I think it’s partially due to peek giving you a reference to the next element (hence the Some(&&1) in the documentation example). And my best guess is that the other * is needed because next() gives references as well, so by that return line it’s like, double referenced?

An attempt at generalizing?

In order to better understand this function that I basically copied from the internet, let’s start slow. Let’s learn about that second line, the one with while let and the dreaded next().

First let’s open the documentation for next() in a browser tab and let it simmer in our minds.

After thinking about next and while let for a bit, I figured out how to write a simplified example without peek or any other high level iterator.

let a = [10, 22, 33, 44, 55, 66];
let mut my_iter = a.iter();

// A call to next() returns the next value as a reference wrapped in
// an Option...
// assert_eq!(Some(&10), my_iter.next());

// And crucially, despite its name, the first time you call it you
// get the FIRST element.

// We should also note that it "returns None when iteration is finished" 

// Since we want to loop through all elements, and we're dealing 
// with Options, `while let` actually does fit our use-case pretty well!

while let Some(element) = my_iter.next() {
    println!("I'm on element {}", element);
}

For me, this shows that this “while-let-next” construction is useful at a pretty low-level, in that we can use it as a way to just iterate (whether we’re doing something fancy like peek or not). Cool!

Next, I knew that while let was a sort of syntatic sugar for avoiding a formal match statement and its None branch – just like if let, which I’ve met before.

I figured “expanding” the while let might help me understand what’s going on better. Looking at the “Rust by Example” explanation of while let, I was able to expand my simplified example out:

let a = [10, 22, 33, 44, 55, 66];
let mut my_iter = a.iter();

loop {
    match my_iter.next() {
        // if there is indeed a next element... do something
        Some(element) => println!("I'm on element {}", element),
        // if there's not a next element... break
        _ => break, // think this can be None instead of _
    }
}

OK cool, cool. Next, the big task: using what I’ve learned with the simpler example and returning to the peekable code. This is where I learned the unfortunate coincidence of the name of the next method, mixed with this coding challenge and peek (which gives you the next next() element, if you get what I mean).

Here’s how I expanded and annotated that while let:

fn find_value_of_drop_using_peek_written_out(vec: &[usize]) -> usize {
    // let's set up a super neat peekable iterator
    let mut peekable_vec = vec.iter().peekable();
    // start an ole fashion loop!
    loop {
        // this is a crucial line below. We're asking if there are any elements 
        // left in our peekable array slice. `next()` is a bit of confusing term
        // here, because later we'll use peek to look at what you might think of 
        // as the NEXT element. 
        match peekable_vec.next() {
            // if there is a "next" element...
            // call it this_element and see if we can peek ahead to the NEXT element
            Some(this_element) => match peekable_vec.peek() {
                Some(next_element) => {
                    // Yes, there is a NEXT element, so we can do our comparison
                    // because here we have access to both this_element and 
                    // next_element
                    if this_element > next_element {
                        return **next_element;
                    }
                }
                // No, no NEXT element, so just continue with the loop
                // though we could probably break or return vec[0] here...
                None => continue,
            },
            // This means we're really at the end of the vector
            // which means the first element is the minimum value
            // break so we get to the last line of the function and
            // return vec[0]
            _ => break,
        }
    }
    vec[0]
}

I think I’m starting to get a handle on it!?

A slightly tighter example using peek and if let

One of the great things about Mastodon is if you toot out some Rust code, someone will refactor it further for you. In this case, my above solution can get significantly tighter (and arguably more readable) by replacing the match statement with an if let. Behold:

fn find_value_of_drop_more_elegant(arr: &[usize]) -> usize {
    let mut peekable_arr = arr.iter().peekable();
    while let Some(l) = peekable_arr.next() {
        if let Some(next_element) = peekable_arr.peek() {
            if l > next_element {
                return **next_element;
            }
        }
    }
    // If we made it here and still haven't found the drop, we know the array is 
    // such that the first element is the minimum value, so we'll return that
    arr[0]
}

Though I think it’s fair to say it’s still quite a bit more formidable than my very first solution (shout-out to high school comp sci!).

Windows

Next to explore is the Rust method windows. Using some of what we learned above from enumerate and peek, I managed to write this out:

fn find_value_of_drop_more_elegant(arr: &[usize]) -> usize {
    let mut windows = arr.windows(2);
    while let Some(&[this_element, next_element]) = windows.next() {
        if this_element > next_element {
            return next_element;
        }
    }
    arr[0]
}

Which I think I like better than using peek, right? We get rid of the inner if let at least.

I’m guessing there’s some situation where you could use peek but couldn’t use windows? Maybe if you’re not dealing with a slice? Something to explore.

Thanks to all the Mastodon/Fediverse users who guided me on this far. I’ll leave this a bit open-ended for now: How else can I solve this and learn other Rust methods/iterators? I’m wondering if there’s a one- or two-line solution possible.