Repository: rust-itertools/itertools (language: rust)

Issue:
`group_by` is weird
I spent several hours debugging my code written using the `group_by` function. 

I expected from this function the logic of grouping the same objects by a key, how surprised I was that he groups only some nearby elements while the group key has the same value.

In the documentation it is also indicated somehow strange and until the last, I was not sure if this is a bug or feature. I carefully read the documentation 5 times and it turned out to be a feature! Awful incomprehensible feature!

I do not think that this is the behavior that many expect for this method name. I demand to rename this method to something more suitable like `cons_group_by` or `lazy_group_by` not to mislead users.

[Example](https://play.integer32.com/?gist=09d53af6fff65075c756905d45400cf0&version=stable)

[I'm not the only one to think so](https://www.reddit.com/r/rust/comments/6d4no8/group_by_on_a_vect/di0j7tt/)


Discussion / hints:
I agree that we could make the examples in the documentation a bit clearer.

You may be looking for [`partition`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.partition).
Thank you for your answer.

This example is not my case, but it shows the strange logic of the method, from the name of which you do not expect this.

What I do is [here](https://play.integer32.com/?version=stable&mode=debug&edition=2015&gist=a1742bd9184094d0dc55bb3f0448e540), but it should be [like this](https://play.integer32.com/?version=stable&mode=debug&edition=2015&gist=5980743a843583340db4dd2b1f94371d). It looks like I need `into_group_map`, but, guys, I think these names stole not only my hours of life.
It groups consecutive runs, just like python's `itertools.groupby`, so there is some precedent.
This is an angry post about the contracts and expectations that users expect from methods with some name. I write in Rust not so long ago and CLion automatically advised me to import `itertools` when I wrote `group_by`, but this is not what I expected.

@bluss Hm, I have no experience with python and its itertools and I think it is also misleading. But I have a lot of Java/Scala/C#/.../SQL experience and it is the different.

I understand that now you can't rename the method for backward compatibility, but at least you can add the result of an example to the documentation. Or maybe you can rename it in the next major version update?
I just had the exact same problem of expecting group_by to work like in most other languages, and I think I have an idea: the current **group_by** function should be renamed **chunk_by**.

Don't you think it would be a better name for its current behavior?

I agree, `chunk_by` seems to me to intuitively match the behavior of the `chunk` methods in the standard library.
The documentation states pretty clearly that it groups "consecutive elements". The example also shows this, though still I think #375 is a good change anyway.
I don't find the name misleading, but I like `chunk_by` just as much.
Maybe I just have the advantage of reading this issue first and knowing that I need a more careful look at the documented semantics though. Anyways, just my 2¢.
just want to start off by saying this is one of my favorite crates and makes it a lot easier to write functional Rust!

that said, I have made this `group_by` mistake myself more than once. I've even read the docs, fixed my code, and then made the same mistake a few months later after forgetting the non-obvious semantics. I've seen some colleagues introduce the same bug as well.

the current `group_by` semantics are nice and efficient when we know the input iterator is already sorted, and as mentioned above there is precedent in other languages. it's also similar to the coreutils [`comm`](https://linux.die.net/man/1/comm) command, which assumes that the files you're comparing are pre-sorted.

unfortunately though, in all of these cases it's easy to forget. worse, it's easy to pass unit tests and only run into this issue in production, once you're iterating through a larger set of data that exposes the bug due to lack of sorting.

some potential improvements that could help here, which unfortunately all introduce breaking changes to varying degrees:
 * rename to `presorted_group_by` or `unchecked_group_by` (the `unchecked` word seems to have pretty widespread usage in Rust to imply "users are responsible for ensuring some invariant"); `chunk_by` as suggested above seems fine too, though perhaps a bit less self-explanatory
 * add a `Sorted` marker trait (or similar) that gets returned by `sort_by_key` and its friends or that users can manually `impl` on their types. then, move this function to a `SortedItertools` or  something trait that is only `impl`ed on `Iterator + 
 Sorted`
 * for debug builds, add (expensive) pre-sort checks and panic on failure
Adding another anecdote supporting the "I'm confused" position. I acknowledge that upon rereading "Consecutive elements that map to the same key (“runs”), are assigned to the same group." it lines up with the behavior, but as someone with a preconceived notion of what I expected (and unfortunately for the first set of data it lined up) I just glossed over that statement because it didn't make sense to me. It could be argued that users should understand what they're doing, but I'd counter that a quality of a strong interface is that it's hard to misuse and I think `group_by` fails that test.

> It groups consecutive runs, just like python's itertools.groupby, so there is some precedent.

While acknowledging that this may be strictly true, the existence of SQL's `group by` doing something different feels like it muddies the waters significantly. Heavily biased, but I feel like the SQL interpretation of "group by" matches its name more intuitively. The confusion is particularly insidious because certain inputs will provide the same outputs with both interpretations (how I ended up here). 

I'm a strong +1 for suggesting the function name be deprecated and moved to something else. That it can be broadly misunderstood is one thing, but that it can _look_ like it's working as expected (when misunderstood) and then act "differently" with a different set of data is pretty troubling.
Is `group_consecutive_by` or `group_adjacent_by` a better name?
I don't feel like I understand why `group_by` as it exists today is actually useful in practice, so I'm not sure I can suggest a descriptive name. Is it a subset of the SQL-group-by, as in an optimization for pre-sorted lists, or is it useful in its own right?
I get that people are not familiar with python (where this came from) but its own [documentation](https://docs.python.org/3/library/itertools.html#itertools.groupby) is interesting. So I'm quoting it here:

> Make an iterator that returns consecutive keys and groups from the iterable. The key is a function computing a key value for each element. If not specified or is None, key defaults to an identity function and returns the element unchanged. Generally, the iterable needs to already be sorted on the same key function.
> 
> The operation of [groupby()](https://docs.python.org/3/library/itertools.html#itertools.groupby) is similar to the uniq filter in Unix. It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function). That behavior differs from SQL’s GROUP BY which aggregates common elements regardless of their input order.
> 
> The returned group is itself an iterator that shares the underlying iterable with [groupby()](https://docs.python.org/3/library/itertools.html#itertools.groupby). Because the source is shared, when the [groupby()](https://docs.python.org/3/library/itertools.html#itertools.groupby) object is advanced, the previous group is no longer visible. So, if that data is needed later, it should be stored as a list:
I think I understand the implementation and that it can theoretically be more efficient if the data is sorted or the aggregated groups aren't actually needed, I'm just not sure where this would be used. From the python docs:

> Generally, the iterable needs to already be sorted on the same key function.

Does anyone have some examples of where this would be useful outside of this case? That one string compression interview question perhaps (aaabbcccc -> a3b2c4) but surely that's not a compelling use case. It feels like maybe leaning in to interpreting this as <new-method-name> is an optimized version of group_by for pre-sorted data makes sense.

My totally biased and likely somewhat ignorant opinion is that this version of `group_by`'s applicability is extremely narrow and nuanced and SQL-style group by is common. Picking a "bad" name for this so only people who know the implications (if it's not supported by the type system) end up using it feels like it's a relatively small downside.

Support above seems to be for `chunk_by`, I marginally like `group_consecutive_by` more than `group_adjacent_by` but all 3 would work for me not assuming it's not the "group_by" method I was looking for.

This result on StackOverflow is pretty damning for the same choice made by Python's itertools: https://stackoverflow.com/questions/773/how-do-i-use-itertools-groupby. (Bonus points for the extra StackOverflow toxicity of "The example on the Python docs is quite straightforward").
I used it mostly when using python and each time because I knew something about the iterable data (that elements were consecutively grouped for some reason) or I wanted to know things about groups in the data.
I do not think I've sorted data and then grouped it by a same key, that would be the exception for me.

I certainly see that group elements **non-consecutively** has more usage.
We have other grouping methods like `into_grouping_map[_by]` (which has several useful methods) and `into_group_map[_by]` resulting in a `HashMap` (and if your usage is not covered but common enough, we might consider adding another).

I'm personally not opposed to changing the method name (I would say `chunk_by` or `group_consecutive_by`) but it's a breaking change and I'm wondering if we could just clarify the documentation enough to avoid any weirdness.
> I'm wondering if we could just clarify the documentation enough to avoid any weirdness.

My instinctual reaction is "no, this can't effectively be fixed by documentation". 

I think the name "group by" means "SQL-style group by" to significantly more people than it means "Python-itertools-style group by". The StackOverflow question with a huge number of people publicly confused by how it's intended to be used supports that position. To me, the issue is the method name itself not aligning with the expected functionality - documentation won't fix that for the majority of consumers. Even within Python, it looks like the Panda GroupBy does not align with the itertools interpretation.

Put another way, if it's feeling like the first line of the documentation should be "this method likely doesn't do what you think it does", I'd argue that's not a great result and a breaking change is a better result. A deprecation and replacement feels pretty palatable from that perspective - renames are relatively easy to accommodate in Rust.
@jswrenn @phimuemue It's not ideal but I think we should consider deprecate `group_by` and rename it `group_consecutive_by`.
`chunk_by` is also a possibility but I don't find it clear enough for my taste.
I'm not opposed, but I'd like to wait for the naming debate in https://github.com/rust-lang/rust/issues/80552 to settle. There's a strong case to be made for consistency with the Rust standard library.
I think that naming debate has settled? The PR is up for standard library to use `chunk_by`, i.e.. https://github.com/rust-lang/rust/pull/117678
I stumbled yet again on this confusion after 2 hours of debugging while re-reading and missing consecutive multiple times. :)

Fix patch:
diff --git a/benches/bench1.rs b/benches/bench1.rs
index 8d8881676..f718ce2c9 100644
--- a/benches/bench1.rs
+++ b/benches/bench1.rs
@@ -390,7 +390,7 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) {
     });
 }
 
-fn group_by_lazy_1(c: &mut Criterion) {
+fn chunk_by_lazy_1(c: &mut Criterion) {
     let mut data = vec![0; 1024];
     for (index, elt) in data.iter_mut().enumerate() {
         *elt = index / 10;
@@ -398,10 +398,10 @@ fn group_by_lazy_1(c: &mut Criterion) {
 
     let data = black_box(data);
 
-    c.bench_function("group by lazy 1", move |b| {
+    c.bench_function("chunk by lazy 1", move |b| {
         b.iter(|| {
-            for (_key, group) in &data.iter().group_by(|elt| **elt) {
-                for elt in group {
+            for (_key, chunk) in &data.iter().chunk_by(|elt| **elt) {
+                for elt in chunk {
                     black_box(elt);
                 }
             }
@@ -409,7 +409,7 @@ fn group_by_lazy_1(c: &mut Criterion) {
     });
 }
 
-fn group_by_lazy_2(c: &mut Criterion) {
+fn chunk_by_lazy_2(c: &mut Criterion) {
     let mut data = vec![0; 1024];
     for (index, elt) in data.iter_mut().enumerate() {
         *elt = index / 2;
@@ -417,10 +417,10 @@ fn group_by_lazy_2(c: &mut Criterion) {
 
     let data = black_box(data);
 
-    c.bench_function("group by lazy 2", move |b| {
+    c.bench_function("chunk by lazy 2", move |b| {
         b.iter(|| {
-            for (_key, group) in &data.iter().group_by(|elt| **elt) {
-                for elt in group {
+            for (_key, chunk) in &data.iter().chunk_by(|elt| **elt) {
+                for elt in chunk {
                     black_box(elt);
                 }
             }
@@ -436,8 +436,8 @@ fn slice_chunks(c: &mut Criterion) {
 
     c.bench_function("slice chunks", move |b| {
         b.iter(|| {
-            for group in data.chunks(sz) {
-                for elt in group {
+            for chunk in data.chunks(sz) {
+                for elt in chunk {
                     black_box(elt);
                 }
             }
@@ -453,8 +453,8 @@ fn chunks_lazy_1(c: &mut Criterion) {
 
     c.bench_function("chunks lazy 1", move |b| {
         b.iter(|| {
-            for group in &data.iter().chunks(sz) {
-                for elt in group {
+            for chunk in &data.iter().chunks(sz) {
+                for elt in chunk {
                     black_box(elt);
                 }
             }
@@ -813,8 +813,8 @@ criterion_group!(
     zipdot_i32_unchecked_counted_loop,
     zipdot_f32_unchecked_counted_loop,
     zip_unchecked_counted_loop3,
-    group_by_lazy_1,
-    group_by_lazy_2,
+    chunk_by_lazy_1,
+    chunk_by_lazy_2,
     slice_chunks,
     chunks_lazy_1,
     equal,
diff --git a/examples/iris.rs b/examples/iris.rs
index 5f5525154..a041b2849 100644
--- a/examples/iris.rs
+++ b/examples/iris.rs
@@ -77,15 +77,15 @@ fn main() {
     let mut plot_symbols = "+ox".chars().cycle();
     let mut symbolmap = HashMap::new();
 
-    // using Itertools::group_by
-    for (species, species_group) in &irises.iter().group_by(|iris| &iris.name) {
+    // using Itertools::chunk_by
+    for (species, species_chunk) in &irises.iter().chunk_by(|iris| &iris.name) {
         // assign a plot symbol
         symbolmap
             .entry(species)
             .or_insert_with(|| plot_symbols.next().unwrap());
         println!("{} (symbol={})", species, symbolmap[species]);
 
-        for iris in species_group {
+        for iris in species_chunk {
             // using Itertools::format for lazy formatting
             println!("{:>3.1}", iris.data.iter().format(", "));
         }
diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs
index c318ec7b3..007fea6f6 100644
--- a/src/groupbylazy.rs
+++ b/src/groupbylazy.rs
@@ -1,7 +1,7 @@
 use alloc::vec::{self, Vec};
 use std::cell::{Cell, RefCell};
 
-/// A trait to unify `FnMut` for `GroupBy` with the chunk key in `IntoChunks`
+/// A trait to unify `FnMut` for `ChunkBy` with the chunk key in `IntoChunks`
 trait KeyFunction<A> {
     type Key;
     fn call_mut(&mut self, arg: A) -> Self::Key;
@@ -282,10 +282,14 @@ where
     }
 }
 
-/// `GroupBy` is the storage for the lazy grouping operation.
+#[deprecated(note = "Use `ChunkBy` instead", since = "0.13.0")]
+/// See [`ChunkBy`](crate::structs::ChunkBy).
+pub type GroupBy<K, I, F> = ChunkBy<K, I, F>;
+
+/// `ChunkBy` is the storage for the lazy grouping operation.
 ///
 /// If the groups are consumed in their original order, or if each
-/// group is dropped without keeping it around, then `GroupBy` uses
+/// group is dropped without keeping it around, then `ChunkBy` uses
 /// no allocations. It needs allocations only if several group iterators
 /// are alive at the same time.
 ///
@@ -294,9 +298,9 @@ where
 /// value. It should be stored in a local variable or temporary and
 /// iterated.
 ///
-/// See [`.group_by()`](crate::Itertools::group_by) for more information.
+/// See [`.chunk_by()`](crate::Itertools::chunk_by) for more information.
 #[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
-pub struct GroupBy<K, I, F>
+pub struct ChunkBy<K, I, F>
 where
     I: Iterator,
 {
@@ -307,12 +311,12 @@ where
 }
 
 /// Create a new
-pub fn new<K, J, F>(iter: J, f: F) -> GroupBy<K, J::IntoIter, F>
+pub fn new<K, J, F>(iter: J, f: F) -> ChunkBy<K, J::IntoIter, F>
 where
     J: IntoIterator,
     F: FnMut(&J::Item) -> K,
 {
-    GroupBy {
+    ChunkBy {
         inner: RefCell::new(GroupInner {
             key: f,
             iter: iter.into_iter(),
@@ -329,7 +333,7 @@ where
     }
 }
 
-impl<K, I, F> GroupBy<K, I, F>
+impl<K, I, F> ChunkBy<K, I, F>
 where
     I: Iterator,
 {
@@ -348,7 +352,7 @@ where
     }
 }
 
-impl<'a, K, I, F> IntoIterator for &'a GroupBy<K, I, F>
+impl<'a, K, I, F> IntoIterator for &'a ChunkBy<K, I, F>
 where
     I: Iterator,
     I::Item: 'a,
@@ -368,7 +372,7 @@ where
 /// Iterator element type is `(K, Group)`:
 /// the group's key `K` and the group's iterator.
 ///
-/// See [`.group_by()`](crate::Itertools::group_by) for more information.
+/// See [`.chunk_by()`](crate::Itertools::chunk_by) for more information.
 #[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
 pub struct Groups<'a, K, I, F>
 where
@@ -377,7 +381,7 @@ where
     K: 'a,
     F: 'a,
 {
-    parent: &'a GroupBy<K, I, F>,
+    parent: &'a ChunkBy<K, I, F>,
 }
 
 impl<'a, K, I, F> Iterator for Groups<'a, K, I, F>
@@ -418,7 +422,7 @@ where
     K: 'a,
     F: 'a,
 {
-    parent: &'a GroupBy<K, I, F>,
+    parent: &'a ChunkBy<K, I, F>,
     index: usize,
     first: Option<I::Item>,
 }
@@ -476,7 +480,7 @@ where
 
 /// `ChunkLazy` is the storage for a lazy chunking operation.
 ///
-/// `IntoChunks` behaves just like `GroupBy`: it is iterable, and
+/// `IntoChunks` behaves just like `ChunkBy`: it is iterable, and
 /// it only buffers if several chunk iterators are alive at the same time.
 ///
 /// This type implements [`IntoIterator`] (it is **not** an iterator
diff --git a/src/lib.rs b/src/lib.rs
index 1f39379e2..f12d95a1d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -37,7 +37,7 @@
 //! - `use_std`
 //!   - Enabled by default.
 //!   - Disable to compile itertools using `#![no_std]`. This disables
-//!     any items that depend on collections (like `group_by`, `unique`,
+//!     any items that depend on collections (like `chunk_by`, `unique`,
 //!     `kmerge`, `join` and many more).
 //!
 //! ## Rust Version
@@ -99,8 +99,11 @@ pub mod structs {
     pub use crate::exactly_one_err::ExactlyOneError;
     pub use crate::flatten_ok::FlattenOk;
     pub use crate::format::{Format, FormatWith};
+    #[allow(deprecated)]
+    #[cfg(feature = "use_alloc")]
+    pub use crate::groupbylazy::GroupBy;
     #[cfg(feature = "use_alloc")]
-    pub use crate::groupbylazy::{Chunk, Chunks, Group, GroupBy, Groups, IntoChunks};
+    pub use crate::groupbylazy::{Chunk, ChunkBy, Chunks, Group, Groups, IntoChunks};
     #[cfg(feature = "use_std")]
     pub use crate::grouping_map::{GroupingMap, GroupingMapBy};
     pub use crate::intersperse::{Intersperse, IntersperseWith};
@@ -575,10 +578,10 @@ pub trait Itertools: Iterator {
     /// Consecutive elements that map to the same key (“runs”), are assigned
     /// to the same group.
     ///
-    /// `GroupBy` is the storage for the lazy grouping operation.
+    /// `ChunkBy` is the storage for the lazy grouping operation.
     ///
     /// If the groups are consumed in order, or if each group's iterator is
-    /// dropped without keeping it around, then `GroupBy` uses no
+    /// dropped without keeping it around, then `ChunkBy` uses no
     /// allocations.  It needs allocations only if several group iterators
     /// are alive at the same time.
     ///
@@ -593,20 +596,20 @@ pub trait Itertools: Iterator {
     /// ```
     /// use itertools::Itertools;
     ///
-    /// // group data into runs of larger than zero or not.
+    /// // chunk data into runs of larger than zero or not.
     /// let data = vec![1, 3, -2, -2, 1, 0, 1, 2];
-    /// // groups:     |---->|------>|--------->|
+    /// // chunks:     |---->|------>|--------->|
     ///
-    /// // Note: The `&` is significant here, `GroupBy` is iterable
+    /// // Note: The `&` is significant here, `ChunkBy` is iterable
     /// // only by reference. You can also call `.into_iter()` explicitly.
     /// let mut data_grouped = Vec::new();
-    /// for (key, group) in &data.into_iter().group_by(|elt| *elt >= 0) {
-    ///     data_grouped.push((key, group.collect()));
+    /// for (key, chunk) in &data.into_iter().chunk_by(|elt| *elt >= 0) {
+    ///     data_grouped.push((key, chunk.collect()));
     /// }
     /// assert_eq!(data_grouped, vec![(true, vec![1, 3]), (false, vec![-2, -2]), (true, vec![1, 0, 1, 2])]);
     /// ```
     #[cfg(feature = "use_alloc")]
-    fn group_by<K, F>(self, key: F) -> GroupBy<K, Self, F>
+    fn chunk_by<K, F>(self, key: F) -> ChunkBy<K, Self, F>
     where
         Self: Sized,
         F: FnMut(&Self::Item) -> K,
@@ -615,13 +618,25 @@ pub trait Itertools: Iterator {
         groupbylazy::new(self, key)
     }
 
+    /// See [`.chunk_by()`](Itertools::chunk_by).
+    #[deprecated(note = "Use .chunk_by() instead", since = "0.13.0")]
+    #[cfg(feature = "use_alloc")]
+    fn group_by<K, F>(self, key: F) -> ChunkBy<K, Self, F>
+    where
+        Self: Sized,
+        F: FnMut(&Self::Item) -> K,
+        K: PartialEq,
+    {
+        self.chunk_by(key)
+    }
+
     /// Return an *iterable* that can chunk the iterator.
     ///
     /// Yield subiterators (chunks) that each yield a fixed number elements,
     /// determined by `size`. The last chunk will be shorter if there aren't
     /// enough elements.
     ///
-    /// `IntoChunks` is based on `GroupBy`: it is iterable (implements
+    /// `IntoChunks` is based on `ChunkBy`: it is iterable (implements
     /// `IntoIterator`, **not** `Iterator`), and it only buffers if several
     /// chunk iterators are alive at the same time.
     ///


Test patch:
diff --git a/tests/quick.rs b/tests/quick.rs
index 954655a43..f96418f40 100644
--- a/tests/quick.rs
+++ b/tests/quick.rs
@@ -1053,58 +1053,58 @@ quickcheck! {
 }
 
 quickcheck! {
-    fn fuzz_group_by_lazy_1(it: Iter<u8>) -> bool {
+    fn fuzz_chunk_by_lazy_1(it: Iter<u8>) -> bool {
         let jt = it.clone();
-        let groups = it.group_by(|k| *k);
-        itertools::equal(jt, groups.into_iter().flat_map(|(_, x)| x))
+        let chunks = it.chunk_by(|k| *k);
+        itertools::equal(jt, chunks.into_iter().flat_map(|(_, x)| x))
     }
 }
 
 quickcheck! {
-    fn fuzz_group_by_lazy_2(data: Vec<u8>) -> bool {
-        let groups = data.iter().group_by(|k| *k / 10);
-        let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x));
+    fn fuzz_chunk_by_lazy_2(data: Vec<u8>) -> bool {
+        let chunks = data.iter().chunk_by(|k| *k / 10);
+        let res = itertools::equal(data.iter(), chunks.into_iter().flat_map(|(_, x)| x));
         res
     }
 }
 
 quickcheck! {
-    fn fuzz_group_by_lazy_3(data: Vec<u8>) -> bool {
-        let grouper = data.iter().group_by(|k| *k / 10);
-        let groups = grouper.into_iter().collect_vec();
-        let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x));
+    fn fuzz_chunk_by_lazy_3(data: Vec<u8>) -> bool {
+        let grouper = data.iter().chunk_by(|k| *k / 10);
+        let chunks = grouper.into_iter().collect_vec();
+        let res = itertools::equal(data.iter(), chunks.into_iter().flat_map(|(_, x)| x));
         res
     }
 }
 
 quickcheck! {
-    fn fuzz_group_by_lazy_duo(data: Vec<u8>, order: Vec<(bool, bool)>) -> bool {
-        let grouper = data.iter().group_by(|k| *k / 3);
-        let mut groups1 = grouper.into_iter();
-        let mut groups2 = grouper.into_iter();
+    fn fuzz_chunk_by_lazy_duo(data: Vec<u8>, order: Vec<(bool, bool)>) -> bool {
+        let grouper = data.iter().chunk_by(|k| *k / 3);
+        let mut chunks1 = grouper.into_iter();
+        let mut chunks2 = grouper.into_iter();
         let mut elts = Vec::<&u8>::new();
-        let mut old_groups = Vec::new();
+        let mut old_chunks = Vec::new();
 
         let tup1 = |(_, b)| b;
         for &(ord, consume_now) in &order {
-            let iter = &mut [&mut groups1, &mut groups2][ord as usize];
+            let iter = &mut [&mut chunks1, &mut chunks2][ord as usize];
             match iter.next() {
                 Some((_, gr)) => if consume_now {
-                    for og in old_groups.drain(..) {
+                    for og in old_chunks.drain(..) {
                         elts.extend(og);
                     }
                     elts.extend(gr);
                 } else {
-                    old_groups.push(gr);
+                    old_chunks.push(gr);
                 },
                 None => break,
             }
         }
-        for og in old_groups.drain(..) {
+        for og in old_chunks.drain(..) {
             elts.extend(og);
         }
-        for gr in groups1.map(&tup1) { elts.extend(gr); }
-        for gr in groups2.map(&tup1) { elts.extend(gr); }
+        for gr in chunks1.map(&tup1) { elts.extend(gr); }
+        for gr in chunks2.map(&tup1) { elts.extend(gr); }
         itertools::assert_equal(&data, elts);
         true
     }
diff --git a/tests/test_std.rs b/tests/test_std.rs
index 5d2864573..7266fdfef 100644
--- a/tests/test_std.rs
+++ b/tests/test_std.rs
@@ -275,7 +275,7 @@ fn all_equal() {
     assert!("A".chars().all_equal());
     assert!(!"AABBCCC".chars().all_equal());
     assert!("AAAAAAA".chars().all_equal());
-    for (_key, mut sub) in &"AABBCCC".chars().group_by(|&x| x) {
+    for (_key, mut sub) in &"AABBCCC".chars().chunk_by(|&x| x) {
         assert!(sub.all_equal());
     }
 }
@@ -797,14 +797,14 @@ fn pad_using() {
 }
 
 #[test]
-fn group_by() {
-    for (ch1, sub) in &"AABBCCC".chars().group_by(|&x| x) {
+fn chunk_by() {
+    for (ch1, sub) in &"AABBCCC".chars().chunk_by(|&x| x) {
         for ch2 in sub {
             assert_eq!(ch1, ch2);
         }
     }
 
-    for (ch1, sub) in &"AAABBBCCCCDDDD".chars().group_by(|&x| x) {
+    for (ch1, sub) in &"AAABBBCCCCDDDD".chars().chunk_by(|&x| x) {
         for ch2 in sub {
             assert_eq!(ch1, ch2);
             if ch1 == 'C' {
@@ -817,8 +817,8 @@ fn group_by() {
 
     // try all possible orderings
     for indices in permutohedron::Heap::new(&mut [0, 1, 2, 3]) {
-        let groups = "AaaBbbccCcDDDD".chars().group_by(&toupper);
-        let mut subs = groups.into_iter().collect_vec();
+        let chunks = "AaaBbbccCcDDDD".chars().chunk_by(&toupper);
+        let mut subs = chunks.into_iter().collect_vec();
 
         for &idx in &indices[..] {
             let (key, text) = match idx {
@@ -833,8 +833,8 @@ fn group_by() {
         }
     }
 
-    let groups = "AAABBBCCCCDDDD".chars().group_by(|&x| x);
-    let mut subs = groups.into_iter().map(|(_, g)| g).collect_vec();
+    let chunks = "AAABBBCCCCDDDD".chars().chunk_by(|&x| x);
+    let mut subs = chunks.into_iter().map(|(_, g)| g).collect_vec();
 
     let sd = subs.pop().unwrap();
     let sc = subs.pop().unwrap();
@@ -851,7 +851,7 @@ fn group_by() {
     {
         let mut ntimes = 0;
         let text = "AABCCC";
-        for (_, sub) in &text.chars().group_by(|&x| {
+        for (_, sub) in &text.chars().chunk_by(|&x| {
             ntimes += 1;
             x
         }) {
@@ -863,7 +863,7 @@ fn group_by() {
     {
         let mut ntimes = 0;
         let text = "AABCCC";
-        for _ in &text.chars().group_by(|&x| {
+        for _ in &text.chars().chunk_by(|&x| {
             ntimes += 1;
             x
         }) {}
@@ -872,81 +872,80 @@ fn group_by() {
 
     {
         let text = "ABCCCDEEFGHIJJKK";
-        let gr = text.chars().group_by(|&x| x);
+        let gr = text.chars().chunk_by(|&x| x);
         it::assert_equal(gr.into_iter().flat_map(|(_, sub)| sub), text.chars());
     }
 }
 
 #[test]
-fn group_by_lazy_2() {
+fn chunk_by_lazy_2() {
     let data = [0, 1];
-    let groups = data.iter().group_by(|k| *k);
-    let gs = groups.into_iter().collect_vec();
+    let chunks = data.iter().chunk_by(|k| *k);
+    let gs = chunks.into_iter().collect_vec();
     it::assert_equal(data.iter(), gs.into_iter().flat_map(|(_k, g)| g));
 
     let data = [0, 1, 1, 0, 0];
-    let groups = data.iter().group_by(|k| *k);
-    let mut gs = groups.into_iter().collect_vec();
+    let chunks = data.iter().chunk_by(|k| *k);
+    let mut gs = chunks.into_iter().collect_vec();
     gs[1..].reverse();
     it::assert_equal(&[0, 0, 0, 1, 1], gs.into_iter().flat_map(|(_, g)| g));
 
-    let grouper = data.iter().group_by(|k| *k);
-    let mut groups = Vec::new();
-    for (k, group) in &grouper {
+    let grouper = data.iter().chunk_by(|k| *k);
+    let mut chunks = Vec::new();
+    for (k, chunk) in &grouper {
         if *k == 1 {
-            groups.push(group);
+            chunks.push(chunk);
         }
     }
-    it::assert_equal(&mut groups[0], &[1, 1]);
+    it::assert_equal(&mut chunks[0], &[1, 1]);
 
     let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3];
-    let grouper = data.iter().group_by(|k| *k);
-    let mut groups = Vec::new();
-    for (i, (_, group)) in grouper.into_iter().enumerate() {
+    let grouper = data.iter().chunk_by(|k| *k);
+    let mut chunks = Vec::new();
+    for (i, (_, chunk)) in grouper.into_iter().enumerate() {
         if i < 2 {
-            groups.push(group);
+            chunks.push(chunk);
         } else if i < 4 {
-            for _ in group {}
+            for _ in chunk {}
         } else {
-            groups.push(group);
+            chunks.push(chunk);
         }
     }
-    it::assert_equal(&mut groups[0], &[0, 0, 0]);
-    it::assert_equal(&mut groups[1], &[1, 1]);
-    it::assert_equal(&mut groups[2], &[3, 3]);
+    it::assert_equal(&mut chunks[0], &[0, 0, 0]);
+    it::assert_equal(&mut chunks[1], &[1, 1]);
+    it::assert_equal(&mut chunks[2], &[3, 3]);
 
-    // use groups as chunks
     let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3];
     let mut i = 0;
-    let grouper = data.iter().group_by(move |_| {
+    let grouper = data.iter().chunk_by(move |_| {
         let k = i / 3;
         i += 1;
         k
     });
-    for (i, group) in &grouper {
+    for (i, chunk) in &grouper {
         match i {
-            0 => it::assert_equal(group, &[0, 0, 0]),
-            1 => it::assert_equal(group, &[1, 1, 0]),
-            2 => it::assert_equal(group, &[0, 2, 2]),
-            3 => it::assert_equal(group, &[3, 3]),
+            0 => it::assert_equal(chunk, &[0, 0, 0]),
+            1 => it::assert_equal(chunk, &[1, 1, 0]),
+            2 => it::assert_equal(chunk, &[0, 2, 2]),
+            3 => it::assert_equal(chunk, &[3, 3]),
             _ => unreachable!(),
         }
     }
 }
 
 #[test]
-fn group_by_lazy_3() {
-    // test consuming each group on the lap after it was produced
+fn chunk_by_lazy_3() {
+    // test consuming each chunk on the lap after it was produced
     let data = [0, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2];
-    let grouper = data.iter().group_by(|elt| *elt);
+    let grouper = data.iter().chunk_by(|elt| *elt);
     let mut last = None;
-    for (key, group) in &grouper {
+    for (key, chunk) in &grouper {
         if let Some(gr) = last.take() {
             for elt in gr {
                 assert!(elt != key && i32::abs(elt - key) == 1);
             }
         }
-        last = Some(group);
+        last = Some(chunk);
     }
 }