Yet another discussion of `while-else` clause

update: Thanks to all the reply, I finally realized that there are more approaches to deal with the question that I thought a while-else is the most beautiful solution.


Same topic is discussion one or even more years before this thread:

before this thread, the main voice is that, there is no necessary using while-else, and some technical issues prevent else from adding to the main Rust language. But several days before, I met the question where a while-else is useful:

what I wrote and some accept answers are quite ugly without while-let statement:

//what I wrote
impl Solution {
    pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
        let mut ans=0;
        g.sort_unstable();
        s.sort_unstable();
        let mut g=g.into_iter();
        let mut s=s.into_iter();
        while let Some(g)=g.next(){
            while let Some(s)=s.next(){
                if g<=s{ans+=1;break}
            }
            // if s.next() is none, there is no necessary to check the rest elements in the outer loop
        }
        ans
    }
}
// other accepted answer
impl Solution {
    pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
        g.sort();
        s.sort();
        let mut i = 0;
        let mut j = 0;
        while i < g.len() && j < s.len() {// if g is short and s is long, and most of elements in s smaller than it is in g, we have to perform much useless check for the statement `i < g.len()` 
            if g[i] <= s[j] {
                i += 1;
            }
            j += 1;
        }
        i as i32
    }
}
//using a while-else (or whatever) structure, the answer will be more beautiful:
impl Solution {
    pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
        let mut ans=0;
        g.sort_unstable();
        s.sort_unstable();
        let mut g=g.into_iter();
        let mut s=s.into_iter();
        while let Some(g)=g.next(){
            while let Some(s)=s.next(){
                if g<=s{ans+=1;break}
            }else{// if `s.next()` is `None`
                break//break the outer loop
            }
        }
        ans
    }
}

some discussion tried make the for statement return a Option<T>, it is quite easy using break Some(x) or break true /*if we don't care about the return value*/ instead.

other idea,let var = while BOOL else EXPR { BLOCK }may distinguish the confusing ofwhile COND{BLOCK}else{EXPR}`

I tried a similar syntax,

while let Some(s)=s.next() || {break}{//the `||` is treated as an operator rather than a closure
//means while (cond) or {break, or other leagal statement that generate `!` or `false`}
//no more mislead will generate since the meaning of `||` is fixed.
 ...
}

but failed to compile since current syntax does not support let-chains.

Currently, to my best knowledge, the only solution is using label to simulate while-else without extra cost:

        'outer: while let Some(g)=g.next(){
            loop {if let Some(s)=s.next(){
                if g<=s{ans+=1;break}
            }else{break 'outer}}
        }

but I do not think labels are the final solution since label is more flexible and may bring extra errors.

I deleted a previous reply, because I misunderstood the original code and posted alternative code that was incorrect. For comparison, here's a corrected version of my code. With my mistake fixed, it is not particularly simpler than the original:

pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    let mut ans = 0;
    g.sort_unstable();
    s.sort_unstable();
    let mut s = &s[..];

    for g in &g {
        if let Some(i) = s.iter().position(|s| g <= s) {
            ans += 1;
            s = &s[i + 1..];
        } else {
            break;
        }
    }
    ans
}

And here's a corrected version of my alternate solution:

pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    g.sort_unstable();
    s.sort_unstable();
    let s = &mut s.iter();
    g.into_iter()
        .take_while(|g| s.any(|s| g <= s))
        .count() as i32
}
3 Likes

Here’s another solution

pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    let mut ans = 0;
    g.sort_unstable();
    s.sort_unstable();
    let s = &mut s.into_iter();
    for g in g {
        if s.any(|s| g <= s) {
            ans += 1
        } else {
            break;
        }
    }
    ans
}
2 Likes

Ah, thanks. I updated my previous comment to use a similar approach.

However, I have to admit that it takes me a bit of extra thought to understand the versions that rely on the state of an iterator after a method like any has been called on it.

1 Like

I think I can make the first “genuine” use case of Iterator::scan that I’ve encountered out of this

pub fn find_content_children3(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    g.sort_unstable();
    s.sort_unstable();
    g.into_iter()
        .scan(s.into_iter(), |s, g| s.any(|s| g <= s).then(|| 1))
        .sum()
}
3 Likes

On a more serious note, going back closer to the original while-else version, this is another alternative:

pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    let mut ans = 0;
    g.sort_unstable();
    s.sort_unstable();
    let mut g = g.into_iter();
    let mut s = s.into_iter();
    while let Some(g) = g.next() {
        if s.try_for_each(|s| {
            if g <= s {
                ans += 1;
                None?;
            }
            Some(())
        }).is_some() {
            break
        }
    }
    ans
}
Or using the nightly ControlFlow type (click to expand)
#![feature(control_flow_enum)]
use std::ops::ControlFlow;
pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    let mut ans = 0;
    g.sort_unstable();
    s.sort_unstable();
    let mut g = g.into_iter();
    let mut s = s.into_iter();
    while let Some(g) = g.next() {
        if s.try_for_each(|s| {
            if g <= s {
                ans += 1;
                ControlFlow::Break(())
            } else {
                ControlFlow::CONTINUE
            }
        }).is_continue() {
            break
        }
    }
    ans
}

And using values (instead of labels) is also something that can be done with the loop version:

pub fn find_content_children(mut g: Vec<i32>, mut s: Vec<i32>) -> i32 {
    let mut ans = 0;
    g.sort_unstable();
    s.sort_unstable();
    let mut g = g.into_iter();
    let mut s = s.into_iter();
    while let Some(g) = g.next() {
        if loop {
            if let Some(s) = s.next() {
                if g <= s {
                    ans += 1;
                    break false;
                }
            } else {
                break true;
            }
        } {
            break;
        }
    }
    ans
}

Reminder that you can do 'a: while ... and break 'a to break out of nested loops.

2 Likes

Isn't a while/else block equivalent to

loop {
    if .. {
        ...
    } else {
        break ...
    }
}

Thank you for your reply So beautiful the code is!