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 d89cad7bb8 CAUSEWAY-3404: adds subgraph algorithm and IntList picking
d89cad7bb8 is described below

commit d89cad7bb82904afed442274cbf64a0925dde0bc
Author: Andi Huber <[email protected]>
AuthorDate: Sat Jan 20 09:42:59 2024 +0100

    CAUSEWAY-3404: adds subgraph algorithm and IntList picking
---
 .../services/metamodel/objgraph/ObjectGraph.java   |   8 +-
 .../apache/causeway/commons/graph/GraphUtils.java  |  22 +++++
 .../collections/_PrimitiveCollections.java         | 105 +++++++++++++++++++--
 .../causeway/commons/graph/GraphUtilsTest.java     |  45 +++++++++
 .../collections/_PrimitiveCollectionsTest.java     |  29 ++++++
 5 files changed, 196 insertions(+), 13 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
index 4fe2b1c422..3e36c7c418 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
@@ -25,6 +25,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 
@@ -257,9 +258,10 @@ public class ObjectGraph {
             g.objects().clear();
             subSet.forEach(g.objects()::add);
             var objectIds = g.objectById().keySet();
-            g.relations().removeIf(rel->
-                !(objectIds.contains(rel.fromId())
-                    || objectIds.contains(rel.fromId())));
+            var isInSubgraph = (Predicate<ObjectGraph.Relation>) rel->
+                objectIds.contains(rel.fromId())
+                && objectIds.contains(rel.toId());
+            g.relations().removeIf(isInSubgraph.negate());
             return g;
         });
         return subGraph;
diff --git 
a/commons/src/main/java/org/apache/causeway/commons/graph/GraphUtils.java 
b/commons/src/main/java/org/apache/causeway/commons/graph/GraphUtils.java
index ea22753173..486ccd5509 100644
--- a/commons/src/main/java/org/apache/causeway/commons/graph/GraphUtils.java
+++ b/commons/src/main/java/org/apache/causeway/commons/graph/GraphUtils.java
@@ -24,6 +24,8 @@ import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
+import org.springframework.lang.Nullable;
+
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.collections.ImmutableEnumSet;
 import 
org.apache.causeway.commons.graph.GraphUtils.GraphKernel.GraphCharacteristic;
@@ -92,6 +94,9 @@ public class GraphUtils {
         public boolean isUndirected() {
             return characteristics.contains(GraphCharacteristic.UNDIRECTED);
         }
+        public int edgeCount() {
+            return adjacencyList.stream().mapToInt(IntList::size).sum();
+        }
         public void addEdge(final int u, final int v) {
             boundsCheck(u);
             boundsCheck(v);
@@ -123,6 +128,23 @@ public class GraphUtils {
             }
             return copy;
         }
+        /**
+         * Returns a sub-graph comprised only of nodes as picked per (zero 
based) indexes {@code int[]}.
+         * @apiNote assumes nodeIndexes are unique and valid
+         */
+        public GraphKernel subGraph(final @Nullable int[] nodeIndexes) {
+            var pickedSubset = new IntList(nodeIndexes);
+            final int subsize = pickedSubset.size();
+            var sub = new GraphKernel(subsize, characteristics);
+            for (int i = 0; i < subsize; i++) {
+                final int fromIndex = i;
+                for (int v : adjacencyList.get(nodeIndexes[i])) {
+                    pickedSubset.indexOf(v)
+                        .ifPresent(toIndex->sub.addEdge(fromIndex, toIndex));
+                }
+            }
+            return sub;
+        }
         /**
          * Returns a list of {@code int[]},
          * where each list entry contains the zero based indexes of weakly 
connected graph nodes.
diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollections.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollections.java
index c39a4a22be..4e8f8a761f 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollections.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollections.java
@@ -18,16 +18,23 @@
  */
 package org.apache.causeway.commons.internal.collections;
 
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.OptionalInt;
 import java.util.PrimitiveIterator;
 import java.util.stream.IntStream;
 
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.commons.internal.base._NullSafe;
+
+import lombok.NoArgsConstructor;
+import lombok.val;
 import lombok.experimental.UtilityClass;
 
 /**
  * <h1>- internal use only -</h1>
- * Primitive int list implementation.
+ * Utility class providing some primitive collections.
  * <p>
  * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this 
package! <br/>
  * These may be changed or removed without notice!
@@ -39,16 +46,39 @@ public class _PrimitiveCollections {
 
     /**
      * Primitive int list implementation. Can also operate as a set,
-     * if {@link IntList#addUnique(int)} is used over {@link IntList#add(int)}.
+     * if {@link IntList#addUnique(int)} (and variants) are used instead of 
{@link IntList#add(int)}.
      * @implNote not thread-safe
      */
+    @NoArgsConstructor
     public static class IntList implements Iterable<Integer> {
-
         private static final int DEFAULT_INITIAL_CAPACITY = 8;
 
+        // -- CONSTRUCTION
+
         private int[] buf;
         private int size = 0;
 
+        public IntList(final int initialCapacity) {
+            if(initialCapacity<0) {
+                throw new IndexOutOfBoundsException(initialCapacity);
+            }
+            this.buf = initialCapacity == 0
+                    ? null // otherwise cannot use buffer doubling algorithm 
below, when adding
+                    : new int[initialCapacity];
+        }
+
+        public IntList(final @Nullable int[] array) {
+            this.size = _NullSafe.size(array);
+            if(size == 0) {
+                // don't init buf to 0 size, otherwise cannot use buffer 
doubling algorithm below, when adding
+                return;
+            }
+            this.buf = new int[size];
+            System.arraycopy(array, 0, buf, 0, size);
+        }
+
+        // -- SIZE
+
         public int size() {
             return size;
         }
@@ -60,10 +90,7 @@ public class _PrimitiveCollections {
             return size!=0;
         }
 
-        public IntList addUnique(final int v) {
-            if(!contains(v)) return add(v);
-            return this;
-        }
+        // -- ADDING
 
         public IntList add(final int v) {
             if(buf==null) {
@@ -77,6 +104,36 @@ public class _PrimitiveCollections {
             return this;
         }
 
+        //TODO should perhaps return some feedback, as to whether v was added 
or not
+        public IntList addUnique(final int v) {
+            if(!contains(v)) return add(v);
+            return this;
+        }
+
+        public IntList addAll(final @Nullable int[] array) {
+            final int n = _NullSafe.size(array);
+            if(n!=0) {
+                var old = buf;
+                this.buf = new int[size + n];
+                System.arraycopy(old, 0, buf, 0, size);
+                System.arraycopy(array, 0, buf, size, n);
+                this.size += n;
+            }
+            return this;
+        }
+
+        public IntList addAllUnique(final @Nullable int[] array) {
+            final int n = _NullSafe.size(array);
+            if(n!=0) {
+                for (int v : array) {
+                    addUnique(v);
+                }
+            }
+            return this;
+        }
+
+        // -- QUERIES
+
         public int get(final int index) {
             if(index<0
                     || index>=size) {
@@ -101,12 +158,34 @@ public class _PrimitiveCollections {
             return false;
         }
 
-        public IntStream stream() {
-            return IntStream.of(toArray());
+        // -- PICKING
+
+        /**
+         * Ignores out of bounds picks.
+         */
+        public int[] toArrayPickByIndex(final @Nullable int... indexes) {
+            final int n = _NullSafe.size(indexes);
+            if(n==0) return new int[0];
+
+            val newElements = new int[n];
+            final int maxIndex = size()-1;
+            int elementCount = 0;
+            for(int index : indexes) {
+                if(index>=0
+                        && index<=maxIndex) {
+                    newElements[elementCount++] = buf[index];
+                }
+            }
+            return elementCount == n
+                    ? newElements
+                    : Arrays.copyOf(newElements, elementCount); // trim to 
actual size
         }
 
+        // -- CONVERSION
+
         /**
-         * @return a new array containing all the int(s) of this list
+         * Returns a new array containing all the int(s) of this list.
+         * @return non-null
          */
         public int[] toArray() {
             var result = new int[size];
@@ -116,6 +195,12 @@ public class _PrimitiveCollections {
             return result;
         }
 
+        // -- TRAVERSAL
+
+        public IntStream stream() {
+            return IntStream.of(toArray());
+        }
+
         @Override
         public Iterator<Integer> iterator() {
             var defensiveCopy = toArray();
diff --git 
a/commons/src/test/java/org/apache/causeway/commons/graph/GraphUtilsTest.java 
b/commons/src/test/java/org/apache/causeway/commons/graph/GraphUtilsTest.java
new file mode 100644
index 0000000000..7baa30e8ad
--- /dev/null
+++ 
b/commons/src/test/java/org/apache/causeway/commons/graph/GraphUtilsTest.java
@@ -0,0 +1,45 @@
+package org.apache.causeway.commons.graph;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.apache.causeway.commons.collections.ImmutableEnumSet;
+import org.apache.causeway.commons.graph.GraphUtils.GraphKernel;
+import 
org.apache.causeway.commons.graph.GraphUtils.GraphKernel.GraphCharacteristic;
+
+class GraphUtilsTest {
+
+    @Test
+    void subgraph() {
+        var graph = new GraphKernel(4, 
ImmutableEnumSet.noneOf(GraphCharacteristic.class));
+        graph.addEdge(0, 1);
+        graph.addEdge(1, 2);
+        graph.addEdge(2, 3);
+
+        assertFalse(graph.isUndirected());
+        assertEquals(4, graph.nodeCount());
+        assertEquals(3, graph.edgeCount());
+
+        // identity
+        var subgraphId = graph.subGraph(new int[] {0, 1, 2, 3});
+        assertFalse(subgraphId.isUndirected());
+        assertEquals(4, subgraphId.nodeCount());
+        assertEquals(3, subgraphId.edgeCount());
+
+        // disjoint
+        var subgraphDisjointNoEdges = graph.subGraph(new int[] {0, 3});
+        assertFalse(subgraphDisjointNoEdges.isUndirected());
+        assertEquals(2, subgraphDisjointNoEdges.nodeCount());
+        assertEquals(0, subgraphDisjointNoEdges.edgeCount());
+
+        // subgraph w/ 3 nodes, reordered
+        var subgraph3 = graph.subGraph(new int[] {3, 1, 2});
+        assertFalse(subgraph3.isUndirected());
+        assertEquals(3, subgraph3.nodeCount());
+        assertEquals(2, subgraph3.edgeCount());
+
+    }
+
+}
diff --git 
a/commons/src/test/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollectionsTest.java
 
b/commons/src/test/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollectionsTest.java
index 8d0e26dbfa..ec6f84e00b 100644
--- 
a/commons/src/test/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollectionsTest.java
+++ 
b/commons/src/test/java/org/apache/causeway/commons/internal/collections/_PrimitiveCollectionsTest.java
@@ -22,6 +22,7 @@ import java.util.stream.IntStream;
 
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -101,5 +102,33 @@ class _PrimitiveCollectionsTest {
         assertEquals(2, intList.indexOf(7).orElse(-1));
     }
 
+    @Test
+    void wrapping() {
+        var array = new int[]{
+                1, 3, 2, 5,
+                1, 3, 2, 5,
+                1, 3, 2, 5,
+                1, 3, 2, 5};
+        var intList = new IntList(array);
+        assertEquals(4*4, intList.size());
+        assertFalse(intList.isEmpty());
+        assertEquals(44, intList.stream().sum());
+    }
+
+    @Test
+    void addAll() {
+        var array = new int[]{1, 3, 2, 5, 1, 3, 2, 5};
+        var intList = new IntList(array).addAll(array);
+        assertEquals(4*4, intList.size());
+        assertFalse(intList.isEmpty());
+        assertEquals(44, intList.stream().sum());
+    }
+
+    @Test
+    void pickByIndex() {
+        var array = new int[]{1, 3, 2, 5};
+        var intList = new IntList(array);
+        assertArrayEquals(new int[]{1, 1, 5}, intList.toArrayPickByIndex(0, 0, 
9, -1, 3));
+    }
 
 }

Reply via email to