Side effect from Peak in intersperse_with

Share what I found just now :slight_smile:

intersperse_with in itertools - Rust and Iterator in std::iter - Rust focus more on separator fn will be called once, but focus less on self receiver that may incur the side effect from Peak inside the implementation of IntersperseWith.

Is there a way to use iterator do the same with the behavior in expected().
I know intersperse((0..2).map(|_| 'a'), 'b') can work, but I'm asking for both push operations in Self and Separatior being behind a callback.

#![feature(iter_intersperse)]
use itertools::intersperse_with as inter;
use std::cell::RefCell;
use std::iter::from_fn;
fn itertools_() {
    let buf = RefCell::new(String::new());
    let a = || Some(buf.borrow_mut().push('a'));
    let b = || buf.borrow_mut().push('b');
    inter(from_fn(a).take(2), b).last();
    dbg!(&buf);
}

fn std_() {
    let buf = RefCell::new(String::new());
    let a = || Some(buf.borrow_mut().push('a'));
    let b = || buf.borrow_mut().push('b');
    from_fn(a).take(2).intersperse_with(b).last();
    dbg!(&buf);
}

fn main() {
    itertools_(); // unexpected: aab
    std_(); // unexpected: aab
    expected(|buf| buf.push('a'), |buf| buf.push('b')); // expected: aba
}

fn expected(a: impl Fn(&mut String), b: impl Fn(&mut String)) {
    let mut buf = String::new();
    let write_a_times = 2usize;
    let write_b_times = write_a_times.saturating_sub(1);

    let mut b_cnt = 0;
    for _ in 0..write_a_times {
        a(&mut buf);
        if b_cnt < write_b_times {
            b(&mut buf);
        }
        b_cnt += 1;
    }

    dbg!(&buf);
}

...but I'd probably use a custom struct if there's nothing more clever.

When I modify the number to 5, the result becomes abaaaa :sweat_smile:

But after tweaking your code, I got almost desired result ababababab (unfortunately with an extra trailling b). Rust Playground

    let flag = Cell::new(true);
    let a = || {
        flag.set(true);
        Some(buf.borrow_mut().push('a'))
    };
    let b = || Some(
        if flag.replace(false) {
            buf.borrow_mut().push('b')
        }
    );

intersperse_with does not fit well for iterators that iterate over side-effect closures I guess...
I'll insist on for-loop + condition check without RefCell. :innocent:

Ah dang, sorry :disappointed: that's what I (and you) get for trying this on my phone I suppose...

What about this

let iters = 5;
let buf = RefCell::new(String::new());
let a = || buf.borrow_mut().push('a');
let b = || buf.borrow_mut().push('b');

a();
for _ in 1..iters {
    b();
    a();
}

If you need to have only one called per iteration, you can do this:

let iters = 5;
let buf = &RefCell::new(String::new());

let closure_maker = |c| move || buf.borrow_mut().push(c);
let a = closure_maker('a');
let b = closure_maker('b');

std::iter::repeat([&b, &a])
    .take(iters)
    .flatten()
    .skip(1)
    .for_each(|f| f());

If the closures aren't the same closure type, you'll need to turn them into trait objects, at which point it would probably be better to have

std::iter::repeat([false, true])
    .take(iters)
    .flatten()
    .skip(1)
    .for_each(|t| if t { a() } else { b() });

P.S. If you have an infallible from_fn, you can use repeat_with instead.

2 Likes

Thanks for it! It's clearer and beats mine. I'll proceed with it. Rust Playground

Nice approach, despite of inefficiency for conditional checking in each iteration.

I never knew it if you didn't tell me. Thx.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.