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

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

commit 909edd5a3c61300d09e24f8a592e3f0f242d5748
Author: Paul King <[email protected]>
AuthorDate: Thu Mar 27 21:49:39 2025 +1000

    GROOVY-11586: Provide an injectAll DGM method
---
 .../groovy/runtime/ArrayGroovyMethods.java         | 44 ++++++++++
 .../groovy/runtime/DefaultGroovyMethods.java       | 96 +++++++++++++++++++++-
 2 files changed, 139 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..3b58ecb719 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
@@ -4841,6 +4841,50 @@ 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.
+     *
+     * @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
 

Reply via email to