You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/03/09 13:01:01 UTC

[causeway] branch master updated: CAUSEWAY-3304: [Commons] Can: adding methods for sub-list and partition

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new e888393319 CAUSEWAY-3304: [Commons] Can: adding methods for sub-list and partition
e888393319 is described below

commit e8883933194057b13f2eb078f3220d2725be139d
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Mar 9 14:00:56 2023 +0100

    CAUSEWAY-3304: [Commons] Can: adding methods for sub-list and partition
---
 .../apache/causeway/commons/collections/Can.java   | 57 ++++++++++++++++++++
 .../causeway/commons/collections/Can_Empty.java    | 21 ++++++++
 .../causeway/commons/collections/Can_Multiple.java | 48 +++++++++++++++++
 .../commons/collections/Can_Singleton.java         | 53 +++++++++++++++++++
 .../causeway/commons/collections/CanTest.java      | 61 ++++++++++++++++++++++
 5 files changed, 240 insertions(+)

diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
index 3004042630..dcacf60eed 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
@@ -37,6 +37,7 @@ import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import org.springframework.lang.Nullable;
@@ -517,10 +518,66 @@ extends ImmutableCollection<T>, Comparable<Can<T>>, Serializable {
      * )
      * </pre>
      * (where nulls are being ignored)
+     * <p>
+     * In other words: Out of bounds picking is simply ignored.
      * @param indices - null-able
      */
     Can<T> pickByIndex(@Nullable int ...indices);
 
+    /**
+     * Returns a {@link Can} that is made of the elements from this {@link Can},
+     * picked by index using the given {@link IntStream} (in the order of picking).
+     * <p>
+     * Out of bounds picking is simply ignored.
+     */
+    Can<T> pickByIndex(@Nullable IntStream intStream);
+
+    // -- SUB SETS AND PARTITIONS
+
+    /**
+     * Returns a sub-{@link Can} that is made of elements from this {@link Can},
+     * when selected by those indices,
+     * that result from given range {@code[startInclusive, endExclusive)}.
+     * <p>
+     * Out of bounds picking is simply ignored.
+     *
+     * @param startInclusive the (inclusive) initial index
+     * @param endExclusive the exclusive upper bound index
+     */
+    Can<T> subCan(int startInclusive, int endExclusive);
+
+    /**
+     * Returns consecutive {@link #subCan(int, int) subCan},
+     * each of the same maxInnerSize, while the final {@link Can} may be smaller.
+     * <p>
+     * For example,
+     * partitioning a {@link Can} containing {@code [a, b, c, d, e]} with a partition
+     * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer {@link Can} containing
+     * two inner {@link Can}s of three and two elements, all in the original order.
+     *
+     * @param maxInnerSize
+     *            the desired size of each sub-{@link Can}s (the last may be smaller)
+     * @return a {@link Can} of consecutive sub-{@link Can}s
+     * @apiNote an alternative approach would be to distribute inner sizes as fair as possible,
+     *      but this method does not
+     */
+    Can<Can<T>> partitionInnerBound(int maxInnerSize);
+
+    /**
+     * Tries to split this {@link Can} into outerSizeYield consecutive {@link #subCan(int, int) subCan},
+     * each of the same calculated max-inner-size, while the final {@link Can} may be smaller.
+     * <p>
+     * An outer cardinality of outerSizeYield is either exactly met or under-represented,
+     * based on how many elements are actually available.
+     *
+     * @param outerSizeYield
+     *            the desired number of sub-{@link Can}s
+     * @return a {@link Can} of consecutive sub-{@link Can}s
+     * @apiNote an alternative approach would be to distribute inner sizes as fair as possible,
+     *      but this method does not
+     */
+    Can<Can<T>> partitionOuterBound(int outerSizeYield);
+
     // -- SEARCH
 
     /**
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
index a95715293b..81c28ae7d7 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
@@ -32,6 +32,7 @@ import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import org.springframework.lang.Nullable;
@@ -200,6 +201,26 @@ final class Can_Empty<T> implements Can<T> {
         return Can.empty();
     }
 
+    @Override
+    public Can<T> pickByIndex(final @Nullable IntStream intStream) {
+        return Can.empty();
+    }
+
+    @Override
+    public Can<T> subCan(final int startInclusive, final int endExclusive) {
+        return Can.empty();
+    }
+
+    @Override
+    public Can<Can<T>> partitionInnerBound(final int maxInnerSize) {
+        return Can.empty();
+    }
+
+    @Override
+    public Can<Can<T>> partitionOuterBound(final int outerSizeYield) {
+        return Can.empty();
+    }
+
     @Override
     public int indexOf(final @Nullable T element) {
         return -1;
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
index f43f462466..e4d4a78774 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
@@ -35,6 +35,7 @@ import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import org.springframework.lang.Nullable;
@@ -286,6 +287,53 @@ final class Can_Multiple<T> implements Can<T> {
         return Can.ofCollection(newElements);
     }
 
+    @Override
+    public Can<T> pickByIndex(final @Nullable IntStream intStream) {
+        if(intStream==null) {
+            return Can.empty();
+        }
+        val newElements = new ArrayList<T>();
+        final int maxIndex = size()-1;
+        intStream
+        .filter(index->index>=0 && index<=maxIndex)
+        .forEach(index->{
+            newElements.add(elements.get(index));
+        });
+        return _CanFactory.ofNonNullElements(newElements);
+    }
+
+    @Override
+    public Can<T> subCan(final int startInclusive, final int endExclusive) {
+        if (startInclusive >= endExclusive) {
+            return Can.empty();
+        }
+        return pickByIndex(IntStream.range(startInclusive, endExclusive));
+    }
+
+    @Override
+    public Can<Can<T>> partitionInnerBound(final int maxInnerSize) {
+        if(maxInnerSize<1) {
+            throw _Exceptions.illegalArgument("maxInnerSize %d must be grater or equal to 1", maxInnerSize);
+        }
+        final int n = size();
+        final int subCanCount = (n - 1)/maxInnerSize + 1;
+        val newElements = new ArrayList<Can<T>>(subCanCount);
+        for(int i=0; i<n; i+=maxInnerSize) {
+            newElements.add(subCan(i, i + maxInnerSize)); // index overflow is ignored
+        }
+        return _CanFactory.ofNonNullElements(newElements);
+    }
+
+    @Override
+    public Can<Can<T>> partitionOuterBound(final int outerSizeYield) {
+        if(outerSizeYield<1) {
+            throw _Exceptions.illegalArgument("outerSizeYield %d must be grater or equal to 1", outerSizeYield);
+        }
+        final int n = size();
+        final int maxInnerSize = (n - 1)/outerSizeYield + 1;
+        return partitionInnerBound(maxInnerSize);
+    }
+
     @Override
     public int indexOf(final @Nullable T element) {
         return this.elements.indexOf(element);
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
index 0103f1f755..8e10a40415 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
@@ -34,6 +34,7 @@ import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import org.springframework.lang.Nullable;
@@ -252,6 +253,58 @@ final class Can_Singleton<T> implements Can<T> {
         return _CanFactory.ofNonNullElements(newElements);
     }
 
+    @Override
+    public Can<T> pickByIndex(final @Nullable IntStream intStream) {
+        if(intStream==null) {
+            return Can.empty();
+        }
+        final long pickCountL = intStream.filter(index->index==0).count();
+        if(pickCountL==0L) {
+            return Can.empty();
+        }
+        if(pickCountL==1L) {
+            return this;
+        }
+        if(pickCountL>Integer.MAX_VALUE) {
+            throw _Exceptions.illegalArgument("pickCount %d is too large to fit into an int", pickCountL);
+        }
+        final int pickCount = (int) pickCountL;
+        val newElements = new ArrayList<T>(pickCount);
+        for(int i=0; i<pickCount; i++) {
+            newElements.add(element);
+        }
+        return _CanFactory.ofNonNullElements(newElements);
+    }
+
+    @Override
+    public Can<T> subCan(final int startInclusive, final int endExclusive) {
+        if (startInclusive >= endExclusive) {
+            return Can.empty();
+        }
+        return (startInclusive<=0
+                    && endExclusive>0)
+                ? this
+                : Can.empty();
+    }
+
+    @Override
+    public Can<Can<T>> partitionInnerBound(final int maxInnerSize) {
+        if(maxInnerSize<1) {
+            throw _Exceptions.illegalArgument("maxInnerSize %d must be grater or equal to 1", maxInnerSize);
+        }
+        // a singular always fits into a single slot
+        return Can.of(this);
+    }
+
+    @Override
+    public Can<Can<T>> partitionOuterBound(final int outerSizeYield) {
+        if(outerSizeYield<1) {
+            throw _Exceptions.illegalArgument("outerSizeYield %d must be grater or equal to 1", outerSizeYield);
+        }
+        // a singular always fits into a single slot
+        return Can.of(this);
+    }
+
     @Override
     public int indexOf(final @Nullable T element) {
         return this.element.equals(element) ? 0 : -1;
diff --git a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
index f069019b53..d8ef8de3a6 100644
--- a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
@@ -242,6 +242,67 @@ class CanTest {
 
     }
 
+    @Test
+    void partitioningInner_whenLastIsSmaller() {
+        final Can<Integer> origin = Can.of(1, 2, 3, 4, 5, 6, 7, 8);
+        final Can<Can<Integer>> subCans = origin.partitionInnerBound(3);
+        assertEquals(3, subCans.size());
+        assertEquals(Can.of(1, 2, 3),
+                subCans.getElseFail(0));
+        assertEquals(Can.of(4, 5, 6),
+                subCans.getElseFail(1));
+        assertEquals(Can.of(7, 8),
+                subCans.getElseFail(2));
+    }
+
+    @Test
+    void partitioningInner_whenLastIsFull() {
+        final Can<Integer> origin = Can.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
+        final Can<Can<Integer>> subCans = origin.partitionInnerBound(3);
+        assertEquals(3, subCans.size());
+        assertEquals(Can.of(1, 2, 3),
+                subCans.getElseFail(0));
+        assertEquals(Can.of(4, 5, 6),
+                subCans.getElseFail(1));
+        assertEquals(Can.of(7, 8, 9),
+                subCans.getElseFail(2));
+    }
+
+    @Test
+    void partitioningOuter_whenEvenlySized() {
+        final Can<Integer> origin = Can.of(1, 2, 3, 4, 5, 6);
+        final Can<Can<Integer>> subCans = origin.partitionOuterBound(2);
+        assertEquals(2, subCans.size());
+        assertEquals(Can.of(1, 2, 3),
+                subCans.getElseFail(0));
+        assertEquals(Can.of(4, 5, 6),
+                subCans.getElseFail(1));
+    }
+
+    @Test
+    void partitioningOuter_whenUnevenlySized() {
+        final Can<Integer> origin = Can.of(1, 2, 3, 4, 5);
+        final Can<Can<Integer>> subCans = origin.partitionOuterBound(2);
+        assertEquals(2, subCans.size());
+        assertEquals(Can.of(1, 2, 3),
+                subCans.getElseFail(0));
+        assertEquals(Can.of(4, 5),
+                subCans.getElseFail(1));
+    }
+
+    @Test
+    void partitioningOuter_whenUnderRepresented() {
+        final Can<Integer> origin = Can.of(1, 2, 3);
+        final Can<Can<Integer>> subCans = origin.partitionOuterBound(5);
+        assertEquals(3, subCans.size());
+        assertEquals(Can.of(1),
+                subCans.getElseFail(0));
+        assertEquals(Can.of(2),
+                subCans.getElseFail(1));
+        assertEquals(Can.of(3),
+                subCans.getElseFail(2));
+    }
+
 
     // -- HEPER