You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/07/29 09:09:01 UTC

(groovy) branch master updated: minor refactor: some doco improvements

This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new ef807d4d11 minor refactor: some doco improvements
ef807d4d11 is described below

commit ef807d4d1122ddce8f929999d715223f3f48e606
Author: Paul King <pa...@asert.com.au>
AuthorDate: Wed Jul 29 19:08:06 2026 +1000

    minor refactor: some doco improvements
---
 src/spec/doc/_working-with-arrays.adoc             |  6 +-
 src/spec/doc/_working-with-collections.adoc        | 55 ++++++++++++
 src/spec/test/gdk/WorkingWithArraysTest.groovy     |  9 ++
 .../test/gdk/WorkingWithCollectionsTest.groovy     | 97 ++++++++++++++++++++++
 4 files changed, 164 insertions(+), 3 deletions(-)

diff --git a/src/spec/doc/_working-with-arrays.adoc b/src/spec/doc/_working-with-arrays.adoc
index 44bf36b656..b50b2bf26d 100644
--- a/src/spec/doc/_working-with-arrays.adoc
+++ b/src/spec/doc/_working-with-arrays.adoc
@@ -38,10 +38,10 @@ expression when given an explicit array type.
 include::../test/gdk/WorkingWithArraysTest.groovy[tags=array_literals,indent=0]
 -------------------------------------
 
-=== Iterating on a list
+=== Iterating on an array
 
-Iterating on elements of a list is usually done calling the `each` and `eachWithIndex` methods, which execute code on each
-item of a list:
+Iterating on elements of an array is usually done calling the `each` and `eachWithIndex` methods, which execute code on each
+item of an array:
 
 [source,groovy]
 ----------------------------------------------------------------------------
diff --git a/src/spec/doc/_working-with-collections.adoc b/src/spec/doc/_working-with-collections.adoc
index 2fcb5f6f2d..91a301f4ae 100644
--- a/src/spec/doc/_working-with-collections.adoc
+++ b/src/spec/doc/_working-with-collections.adoc
@@ -85,6 +85,29 @@ something else. This operation, often called mapping, is done in Groovy thanks t
 include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collect,indent=0]
 ----------------------------------------------------------------------------
 
+When each element maps to a collection of results, `collectMany` transforms and flattens in a single step.
+It is the idiomatic (and more efficient) alternative to `collect { ... }.flatten()`:
+
+[source,groovy]
+----------------------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collectMany,indent=0]
+----------------------------------------------------------------------------
+
+To build a `Map` from a list, `collectEntries` collects a key/value pair for each element:
+
+[source,groovy]
+----------------------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collectEntries,indent=0]
+----------------------------------------------------------------------------
+
+When you need each element together with its position, `withIndex` provides a more composable alternative
+to `eachWithIndex`, while `indexed` returns a map from index to element:
+
+[source,groovy]
+----------------------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_withIndex,indent=0]
+----------------------------------------------------------------------------
+
 === Manipulating lists
 
 [[List-Filtering]]
@@ -191,6 +214,26 @@ from using closures to comparators, as in the following examples:
 include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_sort,indent=0]
 ----------------------------------------------------------------------------
 
+==== Removing duplicates
+
+The `unique` method removes duplicate elements *in place*, while `toUnique` returns a *new* list, leaving the
+original untouched. Both accept an optional closure or comparator to define what "the same" means:
+
+[source,groovy]
+----------------------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_unique,indent=0]
+----------------------------------------------------------------------------
+
+==== Taking and dropping elements
+
+Groovy can select or discard elements from either end of a list, either by count (`take`, `drop`, `takeRight`,
+`dropRight`) or by predicate (`takeWhile`, `dropWhile`). None of these methods mutate the original list:
+
+[source,groovy]
+----------------------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=list_take_drop,indent=0]
+----------------------------------------------------------------------------
+
 ==== Duplicating elements
 
 The {gdk} also takes advantage of operator overloading to provide methods allowing duplication of elements
@@ -436,6 +479,18 @@ We can group a list into a map using some criteria:
 include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk3,indent=0]
 ----------------------------------------------------------------
 
+[[Maps-WithDefault]]
+==== Maps with a default value
+
+The `withDefault` method wraps a map so that reading a missing key returns (and stores) a value computed by
+a closure. This is particularly handy for accumulating into per-key collections without having to pre-populate
+them:
+
+[source,groovy]
+----------------------------------------------------------------
+include::../test/gdk/WorkingWithCollectionsTest.groovy[tags=map_withDefault,indent=0]
+----------------------------------------------------------------
+
 [[Collections-Ranges]]
 == Ranges
 
diff --git a/src/spec/test/gdk/WorkingWithArraysTest.groovy b/src/spec/test/gdk/WorkingWithArraysTest.groovy
index 68cf1739bb..f5681f9dc9 100644
--- a/src/spec/test/gdk/WorkingWithArraysTest.groovy
+++ b/src/spec/test/gdk/WorkingWithArraysTest.groovy
@@ -104,6 +104,15 @@ final class WorkingWithArraysTest {
         assert nums.sum() == 6
         assert nums.indices == [0, 1, 2]
         assert nums.swap(0, 2) == [3, 2, 1] as int[]
+
+        Integer[] moreNums = [10, 20, 30, 40]
+        assert moreNums.find { it > 15 } == 20              // first matching element
+        assert moreNums.findAll { it > 15 } == [20, 30, 40] // all matching elements (as a List)
+        assert moreNums[1..2] == [20, 30]                   // slice an array with a range (returns a List)
+        assert moreNums[-1] == 40                            // negative indices count from the end
+
+        int[][] pairs = [[1, 2], [3, 4]]
+        assert pairs.flatten() == [1, 2, 3, 4]              // flatten nested arrays into a single List
         // end::array_gdk[]
     }
 }
diff --git a/src/spec/test/gdk/WorkingWithCollectionsTest.groovy b/src/spec/test/gdk/WorkingWithCollectionsTest.groovy
index 2d953120fa..db36927590 100644
--- a/src/spec/test/gdk/WorkingWithCollectionsTest.groovy
+++ b/src/spec/test/gdk/WorkingWithCollectionsTest.groovy
@@ -185,6 +185,51 @@ final class WorkingWithCollectionsTest {
         // end::list_collect[]
     }
 
+    @Test
+    void testListCollectMany() {
+        // tag::list_collectMany[]
+        def words = ['a', 'bb', 'ccc']
+        // collectMany transforms each element into a collection, then flattens one level;
+        // it is the idiomatic (and more efficient) alternative to collect {}.flatten()
+        assert words.collectMany { [it, it.size()] } == ['a', 1, 'bb', 2, 'ccc', 3]
+
+        // handy for expanding each element into zero, one or many results
+        assert [1, 2, 3].collectMany { it % 2 == 0 ? [it, it] : [] } == [2, 2]
+        // end::list_collectMany[]
+    }
+
+    @Test
+    void testListCollectEntries() {
+        // tag::list_collectEntries[]
+        def languages = ['Groovy', 'Java', 'Kotlin']
+        // collectEntries builds a map from a list; the closure returns a [key, value] pair per element
+        assert languages.collectEntries { [it, it.size()] } == [Groovy: 6, Java: 4, Kotlin: 6]
+
+        // a map literal can be returned instead of a two-element list
+        assert languages.collectEntries { [(it): it.toUpperCase()] } ==
+                [Groovy: 'GROOVY', Java: 'JAVA', Kotlin: 'KOTLIN']
+        // end::list_collectEntries[]
+    }
+
+    @Test
+    void testListWithIndex() {
+        // tag::list_withIndex[]
+        def vowels = ['a', 'e', 'i', 'o', 'u']
+
+        // withIndex pairs each element with its index (starting at 0 by default)
+        assert vowels.withIndex() == [['a', 0], ['e', 1], ['i', 2], ['o', 3], ['u', 4]]
+
+        // an optional offset sets the starting index
+        assert vowels.withIndex(1) == [['a', 1], ['e', 2], ['i', 3], ['o', 4], ['u', 5]]
+
+        // often chained with collect for a cleaner alternative to eachWithIndex
+        assert vowels.withIndex().collect { v, i -> "$i:$v" } == ['0:a', '1:e', '2:i', '3:o', '4:u']
+
+        // indexed returns a map from index to element
+        assert vowels.indexed(1) == [1: 'a', 2: 'e', 3: 'i', 4: 'o', 5: 'u']
+        // end::list_withIndex[]
+    }
+
     @Test
     void testListGDK() {
         // tag::list_gdk1[]
@@ -367,6 +412,42 @@ final class WorkingWithCollectionsTest {
         // end::list_sort[]
     }
 
+    @Test
+    void testListUnique() {
+        // tag::list_unique[]
+        // unique removes duplicates IN PLACE and returns the (now mutated) list
+        def list = [3, 2, 1, 3, 3, 2]
+        assert list.unique() == [3, 2, 1]
+        assert list == [3, 2, 1]                            // the original list was mutated
+
+        // toUnique returns a NEW list, leaving the original untouched
+        def fruits = ['apple', 'apricot', 'banana', 'blueberry']
+        assert fruits.toUnique { it[0] } == ['apple', 'banana']  // uniqueness by first letter
+        assert fruits.size() == 4                           // original list unchanged
+        // end::list_unique[]
+    }
+
+    @Test
+    void testListTakeDrop() {
+        // tag::list_take_drop[]
+        def list = [1, 2, 3, 4, 5]
+
+        // take/drop select or discard a number of leading elements
+        assert list.take(2) == [1, 2]
+        assert list.drop(2) == [3, 4, 5]
+
+        // takeRight/dropRight work from the end of the list
+        assert list.takeRight(2) == [4, 5]
+        assert list.dropRight(2) == [1, 2, 3]
+
+        // takeWhile/dropWhile use a predicate instead of a count, stopping at the first mismatch
+        assert list.takeWhile { it < 3 } == [1, 2]
+        assert list.dropWhile { it < 3 } == [3, 4, 5]
+
+        assert list == [1, 2, 3, 4, 5]                      // none of the above mutate the original
+        // end::list_take_drop[]
+    }
+
     @Test
     void testListMultiply() {
         // tag::list_multiply[]
@@ -777,6 +858,22 @@ final class WorkingWithCollectionsTest {
         '''
     }
 
+    @Test
+    void testMapWithDefault() {
+        // tag::map_withDefault[]
+        // withDefault wraps a map so that missing keys are computed on demand
+        def wordsByLetter = [:].withDefault { [] }
+        ['apple', 'banana', 'avocado', 'cherry'].each { word ->
+            wordsByLetter[word[0]] << word          // no need to pre-populate the per-letter lists
+        }
+        assert wordsByLetter == [a: ['apple', 'avocado'], b: ['banana'], c: ['cherry']]
+
+        // reading an absent key returns (and stores) the computed default
+        def counts = [:].withDefault { 0 }
+        assert counts['missing'] == 0
+        // end::map_withDefault[]
+    }
+
     @Test
     void testShouldNotUseGStringAsKey() {
         assertScript '''