How can min_by_key be used with peekable iterators?

#1

Hello everyone,

I am new to rust and currently trying to merge bunches of sorted iterators (each iterator is reading from a huge file)

My first attempt is to use min_by_key like this:

        // v is a vector of `BufReader.lines().peekable()`
        let mut iter = v.iter().min_by_key(|x| {
            match x.peek() { 
                Some(line) => {
                    match line {
                        Ok(line) => line.len() as u64,
                        _ => std::u64::MAX,
                    }
                },
                None => std::u64::MAX,
            }
        });
       // do something with `iter.next()`

However, the compiler returns with an error telling:

> match x.peek()
cannot borrow `**x` as mutable, as it is behind a `&` reference

So I am having two questions:

  1. why peek wants to borrow x as mutable? As far as I am concerned, peek is not consuming the iterator.
  2. What should I do to avoid the error?

Thanks in advance.

#2

peek needs mutable access to the iterator, because it needs to prefetch the next element and store it, and that changes the state of the iterator.

Unfortunately min_by_key by design only lets you see the item, not modify it.

Your best bet would be to write your own for loop with a mutable iterator, and pick the min element manually.

If you really had to use min_by_key, then you’d have to wrap the peekable iterators in RefCell/Mutex that can mutate an object behind a shared reference.

1 Like
#3

For those who interested, I end up something like this:

        let (idx, _) = vec.iter_mut()
                          .enumerate()
                          .fold((None, std::u64::MAX), |(ia, a), (ib, b)| {
            let val = match b.peek() {
                Some(line) => line.len() as u64,
                None => std::u64::MAX,
            };
            if val < a { (Some(ib), val) } else { (ia, a) }
        });
1 Like