Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Itertools group_by iterator method without a for loop?

Tags:

rust

Itertools::group_by is an iterator method that generates a new group every time the key function changes. The provided example shows how to use it with a for loop, but using the output GroupBy struct in an iterator chain seems to be very cumbersome, unless I misunderstood something:

let data = vec![1, 3, -2, -2, 1, 0, 1, 2];

// example from docs
for (key, group) in &data.into_iter().group_by(|elt| *elt >= 0) {
    assert_eq!(4, group.sum::<i32>().abs());
}

// usage in an iterator method chain
data.iter()
    .group_by(|elt| **elt >= 0)
    .into_iter()
    .map(|bool, group| (bool, group.collect::<Vec<i32>>()))
    .collect::<Vec<(bool, Vec<i32>)>>();

The second example fails to compile:

error[E0619]: the type of this value must be known in this context
  --> src/main.rs:16:35
   |
16 |         .map(|bool, group| (bool, group.collect::<Vec<i32>>()))
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0599]: no method named `collect` found for type `std::iter::Map<itertools::Groups<'_, bool, std::slice::Iter<'_, i32>, [closure@src/main.rs:14:19: 14:35]>, [closure@src/main.rs:16:14: 16:63]>` in the current scope
  --> src/main.rs:17:10
   |
17 |         .collect::<Vec<(bool, Vec<i32>)>>();
   |          ^^^^^^^
   |
   = note: the method `collect` exists but the following trait bounds were not satisfied:
           `std::iter::Map<itertools::Groups<'_, bool, std::slice::Iter<'_, i32>, [closure@src/main.rs:14:19: 14:35]>, [closure@src/main.rs:16:14: 16:63]> : std::iter::Iterator`
           `&mut std::iter::Map<itertools::Groups<'_, bool, std::slice::Iter<'_, i32>, [closure@src/main.rs:14:19: 14:35]>, [closure@src/main.rs:16:14: 16:63]> : std::iter::Iterator`

error[E0593]: closure takes 2 arguments but 1 argument is required
  --> src/main.rs:16:10
   |
16 |         .map(|bool, group| (bool, group.collect::<Vec<i32>>()))
   |          ^^^ ------------------------------------------------- takes 2 arguments
   |          |
   |          expected closure that takes 1 argument

I'm not confident I understand this error, but I think the compiler wants the closure's group argument to have this explicit type:

itertools::Group<'_, bool, std::slice::Iter<'_, i32>, [closure@src\main.rs:26:15: 26:31]>

No one wants that in their code, so I hope I misunderstood.

The question is, whether it's possible, and how to use group_by without a for loop?

like image 436
Dominykas Mostauskis Avatar asked Dec 19 '17 11:12

Dominykas Mostauskis


1 Answers

The closure passed to map has the wrong number of arguments (two instead of one: use a tuple instead); the error message is not very helpful of course :)

Also I wouldn't use bool as a variable name, and you need to clone the integer references before you collect them (due to iter() vs. into_iter()).

Playground

extern crate itertools;

use itertools::Itertools;

fn main() {
    let data = vec![1, 3, -2, -2, 1, 0, 1, 2];

    let groups = data.iter()
        .group_by(|elt| **elt >= 0)
        .into_iter()
        .map(|(ge0, group)| (ge0, group.cloned().collect()))
        .collect::<Vec<(bool, Vec<i32>)>>();
    println!("{:?}", groups);
}
like image 164
Stefan Avatar answered Oct 29 '22 13:10

Stefan