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 '''