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

paulk 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 662ac50b22 GROOVY-11603: Add Lazy generators for iterators (split into 
two classes, add @since)
662ac50b22 is described below

commit 662ac50b2264225de916179417ddfc3020b1725f
Author: Paul King <[email protected]>
AuthorDate: Thu Apr 10 15:49:01 2025 +1000

    GROOVY-11603: Add Lazy generators for iterators (split into two classes, 
add @since)
---
 src/main/java/groovy/util/Iterables.java | 122 +++++++++++++++++++++++++++++++
 src/main/java/groovy/util/Iterators.java |  96 ++++++++----------------
 2 files changed, 153 insertions(+), 65 deletions(-)

diff --git a/src/main/java/groovy/util/Iterables.java 
b/src/main/java/groovy/util/Iterables.java
new file mode 100644
index 0000000000..54248fdbd7
--- /dev/null
+++ b/src/main/java/groovy/util/Iterables.java
@@ -0,0 +1,122 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Provides some iterable based generators.
+ *
+ * @since 5.0.0
+ */
+public class Iterables {
+
+    /**
+     * An iterator returning a map for each combination of elements the 
iterables sources.
+     *
+     * <pre class="groovyTestCase">
+     * assert Iterables.combine(x: 1..2, y: 'a'..'c').collect().toString()
+     *     == '[[x:1, y:a], [x:1, y:b], [x:1, y:c], [x:2, y:a], [x:2, y:b], 
[x:2, y:c]]'
+     * assert Iterables.combine(x: 1..3, y: 'a'..'b').collect().toString()
+     *     == '[[x:1, y:a], [x:1, y:b], [x:2, y:a], [x:2, y:b], [x:3, y:a], 
[x:3, y:b]]'
+     * </pre>
+     *
+     * @param map the named source iterables
+     * @return the output iterator of named combinations
+     */
+    public static <K, T> Iterator<Map<K, T>> combine(Map<K, ? extends 
Iterable<T>> map) {
+        return new CrossProductIterator<>(map);
+    }
+
+    private static class CrossProductIterator<K, T> implements Iterator<Map<K, 
T>> {
+        private final Map<K, ? extends Iterable<T>> iterables;
+        private final Map<K, Iterator<T>> iterators = new LinkedHashMap<>();
+        private final List<K> keys = new ArrayList<>();
+        private Map<K, T> current;
+        private boolean loaded;
+        private boolean exhausted;
+
+        private CrossProductIterator(Map<K, ? extends Iterable<T>> iterables) {
+            this.iterables = iterables;
+            for (Map.Entry<K, ? extends Iterable<T>> entry : 
iterables.entrySet()) {
+                K key = entry.getKey();
+                keys.add(key);
+                iterators.put(key, entry.getValue().iterator());
+            }
+        }
+
+        private void loadFirst() {
+            current = new LinkedHashMap<>();
+            for (K key : keys) {
+                Iterator<T> iterator = iterators.get(key);
+                if (!iterator.hasNext()) {
+                    exhausted = true;
+                    return;
+                }
+                T next = iterator.next();
+                current.put(key, next);
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (!loaded) {
+                loadNext();
+                loaded = true;
+            }
+            return !exhausted;
+        }
+
+        @Override
+        public Map<K, T> next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException("CrossProductIterator has 
been exhausted and contains no more elements");
+            }
+            Map<K, T> ret = current;
+            loaded = false;
+            return ret;
+        }
+
+        private void loadNext() {
+            if (current == null) {
+                loadFirst();
+            } else {
+                current = new LinkedHashMap<>(current);
+                for (int i = keys.size() - 1; i >= 0; i--) {
+                    K key = keys.get(i);
+                    if (iterators.get(key).hasNext()) {
+                        T next = iterators.get(key).next();
+                        current.put(key, next);
+                        break;
+                    } else if (i > 0) {
+                        iterators.put(key, iterables.get(key).iterator());
+                        current.put(key, iterators.get(key).next());
+                    } else {
+                        exhausted = true;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/groovy/util/Iterators.java 
b/src/main/java/groovy/util/Iterators.java
index 0f7974d4b0..efff0d1b72 100644
--- a/src/main/java/groovy/util/Iterators.java
+++ b/src/main/java/groovy/util/Iterators.java
@@ -37,6 +37,8 @@ import java.util.function.UnaryOperator;
 
 /**
  * Provides some iterator based generators.
+ *
+ * @since 5.0.0
  */
 public class Iterators {
 
@@ -135,42 +137,25 @@ public class Iterators {
         }
     }
 
-    /**
-     * An iterator returning a map for each combination of elements the 
iterables sources.
-     *
-     * <pre class="groovyTestCase">
-     * assert Iterators.combine(x: 1..2, y: 'a'..'c').collect().toString()
-     *     == '[[x:1, y:a], [x:1, y:b], [x:1, y:c], [x:2, y:a], [x:2, y:b], 
[x:2, y:c]]'
-     * assert Iterators.combine(x: 1..3, y: 'a'..'b').collect().toString()
-     *     == '[[x:1, y:a], [x:1, y:b], [x:2, y:a], [x:2, y:b], [x:3, y:a], 
[x:3, y:b]]'
-     * </pre>
-     *
-     * @param map the source iterables
-     * @return the output iterator
-     */
-    public static <K, T> Iterator<Map<K, T>> combine(Map<K, ? extends 
Iterable<T>> map) {
-        return new CrossProductIterator<>(map);
-    }
-
     /**
      * An iterator returning a map for each combination of elements the 
iterator sources.
      * If one of the iterators is infinite, the produced elements will be 
infinite
      * and will need to be appropriately handled.
      * By necessity, elements from the iterators are cached, so you should
-     * use {@link #combine(Map)} if you have existing iterables as this will 
be more efficient.
+     * use {@link Iterables#combine(Map)} if you have existing iterables as 
this will be more efficient.
      *
      * <pre class="groovyTestCase">
-     * assert Iterators.combineLazy(x: (1..2).iterator(), y: 
('a'..'c').iterator()).collect().toString()
+     * assert Iterators.combine(x: (1..2).iterator(), y: 
('a'..'c').iterator()).collect().toString()
      *     == '[[x:1, y:a], [x:1, y:b], [x:1, y:c], [x:2, y:a], [x:2, y:b], 
[x:2, y:c]]'
-     * assert Iterators.combineLazy(x: (1..3).iterator(), y: 
('a'..'b').iterator()).collect().toString()
+     * assert Iterators.combine(x: (1..3).iterator(), y: 
('a'..'b').iterator()).collect().toString()
      *     == '[[x:1, y:a], [x:1, y:b], [x:2, y:a], [x:2, y:b], [x:3, y:a], 
[x:3, y:b]]'
      * </pre>
      *
-     * @param map the source iterators
-     * @return the output iterator
+     * @param map the named source iterators
+     * @return the output iterator of named combinations
      */
-    public static <K, T> Iterator<Map<K, T>> combineLazy(Map<K, ? extends 
Iterator<T>> map) {
-        return combineLazy(map, false);
+    public static <K, T> Iterator<Map<K, T>> combine(Map<K, ? extends 
Iterator<T>> map) {
+        return combine(map, false);
     }
 
     /**
@@ -178,10 +163,10 @@ public class Iterators {
      * If one of the iterators is infinite, the produced elements will be 
infinite
      * and will need to be appropriately handled.
      * By necessity, elements from the iterators are cached, so you should
-     * use {@link #combine(Map)} if you have existing iterables as this will 
be more efficient.
+     * use {@link Iterables#combine(Map)} if you have existing iterables as 
this will be more efficient.
      *
      * <pre class="groovyTestCase">
-     * assert Iterators.combineLazy(
+     * assert Iterators.combine(
      *     even: Iterators.iterate(0, n {@code ->} n + 2),
      *     odd: Iterators.iterate(1, n {@code ->} n + 2), false)
      *     .take(10).collect().join('\n')
@@ -198,7 +183,7 @@ public class Iterators {
      * [even:0, odd:19]
      * '''.trim()
      *
-     * assert Iterators.combineLazy(
+     * assert Iterators.combine(
      *     even: Iterators.iterate(0, n {@code ->} n + 2),
      *     odd: Iterators.iterate(1, n {@code ->} n + 2), true)
      *     .take(10).collect().join('\n')
@@ -216,37 +201,29 @@ public class Iterators {
      * '''.trim()
      * </pre>
      *
-     * @param map the source iterators
+     * @param map the named source iterators
      * @param fairOrdering determine the ordering in which combinations are 
processed, fair implies that all source iterators will be visited roughly 
equally (until exhausted)
-     * @return the output iterator
+     * @return the output iterator of named combinations
      */
-    public static <K, T> Iterator<Map<K, T>> combineLazy(Map<K, ? extends 
Iterator<T>> map, boolean fairOrdering) {
+    public static <K, T> Iterator<Map<K, T>> combine(Map<K, ? extends 
Iterator<T>> map, boolean fairOrdering) {
         if (fairOrdering) {
             return new CrossByIndexIterator<>(map);
         } else {
-            return new CrossProductIterator<>(false, map);
+            return new CrossProductIterator<>(map);
         }
     }
 
     private static class CrossProductIterator<K, T> implements Iterator<Map<K, 
T>> {
-        private final Map<K, Iterable<T>> iterables;
         private final Map<K, Collection<T>> cache;
         private final Map<K, Iterator<T>> iterators = new LinkedHashMap<>();
         private final List<K> keys = new ArrayList<>();
         private Map<K, T> current;
         private boolean loaded;
         private boolean exhausted;
-        private boolean usingIterables;
         private final Map<K, Boolean> usingCache = new LinkedHashMap<>();
 
-        private CrossProductIterator(Map<K, Iterable<T>> iterables, Map<K, 
Collection<T>> cache, int numItems) {
-            this.iterables = iterables;
-            this.cache = cache;
-        }
-
-        private CrossProductIterator(boolean usingIterables, Map<K, ? extends 
Iterator<T>> iterators) {
-            this(null, new LinkedHashMap<>(), iterators.size());
-            this.usingIterables = usingIterables;
+        private CrossProductIterator(Map<K, ? extends Iterator<T>> iterators) {
+            cache = new LinkedHashMap<>();
             for (Map.Entry<K, ? extends Iterator<T>> entry : 
iterators.entrySet()) {
                 K key = entry.getKey();
                 keys.add(key);
@@ -256,26 +233,17 @@ public class Iterators {
             }
         }
 
-        private CrossProductIterator(Map<K, ? extends Iterable<T>> iterables) {
-            this(new LinkedHashMap<>(), null, iterables.size());
-            this.usingIterables = true;
-            for (Map.Entry<K, ? extends Iterable<T>> entry : 
iterables.entrySet()) {
-                K key = entry.getKey();
-                keys.add(key);
-                this.iterables.put(key, entry.getValue());
-                iterators.put(key, entry.getValue().iterator());
-                usingCache.put(key, false);
-            }
-        }
-
         private void loadFirst() {
             current = new LinkedHashMap<>();
             for (K key : keys) {
-                T next = iterators.get(key).next();
-                current.put(key, next);
-                if (!usingIterables) {
-                    cache.get(key).add(next);
+                Iterator<T> iterator = iterators.get(key);
+                if (!iterator.hasNext()) {
+                    exhausted = true;
+                    return;
                 }
+                T next = iterator.next();
+                current.put(key, next);
+                cache.get(key).add(next);
             }
         }
 
@@ -291,7 +259,7 @@ public class Iterators {
         @Override
         public Map<K, T> next() {
             if (!hasNext()) {
-                throw new NoSuchElementException("Iterator has been exhausted 
and contains no more elements");
+                throw new NoSuchElementException("CrossProductIterator has 
been exhausted and contains no more elements");
             }
             Map<K, T> ret = current;
             loaded = false;
@@ -308,17 +276,13 @@ public class Iterators {
                     if (iterators.get(key).hasNext()) {
                         T next = iterators.get(key).next();
                         current.put(key, next);
-                        if (!usingIterables && !usingCache.get(key)) {
+                        if (!usingCache.get(key)) {
                             cache.get(key).add(next);
                         }
                         break;
                     } else if (i > 0) {
                         usingCache.put(key, true);
-                        if (usingIterables) {
-                            iterators.put(key, iterables.get(key).iterator());
-                        } else {
-                            iterators.put(key, cache.get(key).iterator());
-                        }
+                        iterators.put(key, cache.get(key).iterator());
                         current.put(key, iterators.get(key).next());
                     } else {
                         exhausted = true;
@@ -405,7 +369,9 @@ public class Iterators {
 
         @Override
         public Map<K, T> next() {
-            if (nextItem == null) throw new NoSuchElementException();
+            if (!hasNext()) {
+                throw new NoSuchElementException("CrossByIndexIterator has 
been exhausted and contains no more elements");
+            }
             Map<K, T> result = nextItem;
             fetchNext();
             return result;

Reply via email to