This is an automated email from the ASF dual-hosted git repository.
sunlan 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 9befe6e770 GROOVY-11586: Provide an injectAll DGM method (#2166)
9befe6e770 is described below
commit 9befe6e770e671cfccfca8c9c780c9b83293de35
Author: Paul King <[email protected]>
AuthorDate: Sat Mar 29 20:09:10 2025 +1100
GROOVY-11586: Provide an injectAll DGM method (#2166)
* GROOVY-11586: Provide an injectAll DGM method
* GROOVY-11586: Provide an injectAll DGM method
---
.../groovy/runtime/ArrayGroovyMethods.java | 51 ++++++++++++
.../groovy/runtime/DefaultGroovyMethods.java | 96 +++++++++++++++++++++-
2 files changed, 146 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
index fa5b8fb935..f274c31ab9 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
@@ -4841,6 +4841,57 @@ public class ArrayGroovyMethods extends
DefaultGroovyMethodsSupport {
return value;
}
+
//--------------------------------------------------------------------------
+ // injectAll
+
+ /**
+ * Iterates through the given array, injecting values as per
<tt>inject</tt>
+ * but returns the list of all calculated values instead of just the final
result.
+ *
+ * @since 5.0.0
+ */
+ public static <E extends T, T, V extends T> List<T> injectAll(E[] self,
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+ if (self.length == 0) {
+ throw new NoSuchElementException("Cannot call inject() on an empty
array without passing an initial value.");
+ }
+ List<T> result = new ArrayList<>();
+ T value = self[0];
+ Object[] params = new Object[2];
+ for (int i = 1; i < self.length; i += 1) {
+ params[0] = value;
+ params[1] = self[i];
+ value = closure.call(params);
+ result.add(value);
+ }
+ return result;
+ }
+
+ /**
+ * Iterates through the given array, injecting values as per
<tt>inject</tt>
+ * but returns the list of all calculated values instead of just the final
result.
+ *
+ * <pre class="groovyTestCase">
+ * Integer[] nums = 1..5
+ * var prefixSum = nums.injectAll(0, Integer::sum)
+ * Arrays.parallelPrefix(nums, Integer::sum) // built-in JDK mutating
method
+ * assert nums == prefixSum
+ * </pre>
+ *
+ * @since 5.0.0
+ */
+ public static <E, T, U extends T, V extends T> List<T> injectAll(E[] self,
U initialValue, @ClosureParams(value=FromString.class,options="T,E") Closure<V>
closure) {
+ List<T> result = new ArrayList<>();
+ T value = initialValue;
+ Object[] params = new Object[2];
+ for (E e : self) {
+ params[0] = value;
+ params[1] = e;
+ value = closure.call(params);
+ result.add(value);
+ }
+ return result;
+ }
+
//--------------------------------------------------------------------------
// iterator
diff --git
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 298eca81d1..74c94b4b9e 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -7409,7 +7409,7 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
if (!iter.hasNext()) {
throw new NoSuchElementException("Cannot call inject() on an empty
iterable without passing an initial value.");
}
- return (T) inject(iter, iter.next(), closure);
+ return inject(iter, iter.next(), closure);
}
//
@@ -7543,6 +7543,100 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return value;
}
+
//--------------------------------------------------------------------------
+ // injectAll
+
+ /**
+ * Iterates through the given iterable, injecting values as per
<tt>inject</tt>
+ * but returns the list of all calculated values instead of just the final
result.
+ * <p>
+ * Also known as <em>prefix sum</em> or <em>scan</em> in functional
parlance.
+ * <pre class="groovyTestCase">
+ * assert (1..3).injectAll(''){ carry, next {@code ->} carry + next } ==
['1', '12', '123']
+ * var runningAvg = [1.0, 2.0, 3.0].injectAll([0.0, 0, null]){ accum, next
{@code ->}
+ * var total = accum[0] + next
+ * var count = accum[1] + 1
+ * [total, count, total/count]
+ * }
+ * assert runningAvg*.get(2) == [1.0, 1.5, 2.0]
+ * </pre>
+ *
+ * @param self an iterator
+ * @param initialValue some initial value
+ * @param closure a closure
+ * @return the list of all calculated values
+ * @since 5.0.0
+ */
+ public static <E, T, U extends T, V extends T> List<T>
injectAll(Iterable<E> self, U initialValue,
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+ return injectAll(self.iterator(), initialValue, closure);
+ }
+
+ /**
+ * Iterates through the given iterable, injecting values as per
<tt>inject</tt>
+ * but returns the list of all calculated values instead of just the final
result.
+ *
+ * @since 5.0.0
+ */
+ public static <E extends T, T, V extends T> List<T> injectAll(Iterable<E>
self, @ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+ Iterator<E> iter = self.iterator();
+ if (!iter.hasNext()) {
+ throw new NoSuchElementException("Cannot call injectAll() on an
empty iterable without passing an initial value.");
+ }
+ return injectAll(iter, iter.next(), closure);
+ }
+
+ /**
+ * Iterates through the given iterator, injecting values as per
<tt>inject</tt>
+ * but returns the list of all calculated values instead of just the final
result.
+ *
+ * @since 5.0.0
+ */
+ public static <E, T, U extends T, V extends T> List<T>
injectAll(Iterator<E> self, U initialValue,
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+ List<T> result = new ArrayList<>();
+ T value = initialValue;
+ Object[] params = new Object[2];
+ while (self.hasNext()) {
+ params[0] = value;
+ params[1] = self.next();
+ value = closure.call(params);
+ result.add(value);
+ }
+ return result;
+ }
+
+ /**
+ * Iterates through the given map, passing in the initial value to
+ * the 2-arg Closure along with the first item (or 3-arg Closure along
with the first key and value)
+ * and then similarly for other map entries. The results are collected in
a list.
+ *
+ * Examples:
+ * <pre class="groovyTestCase">
+ * def map = [a:1, b:2, c:3]
+ * assert map.injectAll('') { carry, k, v {@code ->}
+ * carry + k * v
+ * } == ['a', 'abb', 'abbccc']
+ * </pre>
+ *
+ * @param self a map
+ * @param initialValue some initial value
+ * @param closure a 2 or 3 arg Closure
+ * @return the accumulated results from each closure call
+ * @since 5.0.0
+ */
+ public static <K, V, T, U extends T, W extends T> List<T> injectAll(Map<K,
V> self, U initialValue,
@ClosureParams(value=FromString.class,options={"T,Map.Entry<K,V>","T,K,V"})
Closure<W> closure) {
+ List<T> result = new ArrayList<>();
+ T value = initialValue;
+ for (Map.Entry<K, V> entry : self.entrySet()) {
+ if (closure.getMaximumNumberOfParameters() == 3) {
+ value = closure.call(value, entry.getKey(), entry.getValue());
+ } else {
+ value = closure.call(value, entry);
+ }
+ result.add(value);
+ }
+ return result;
+ }
+
//--------------------------------------------------------------------------
// inspect