Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into v3
Browse files Browse the repository at this point in the history
  • Loading branch information
andi-huber committed Oct 14, 2024
2 parents 24f9f1d + 50581c3 commit 2e76cef
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand All @@ -40,6 +40,7 @@
import org.apache.causeway.commons.functional.IndexedConsumer;
import org.apache.causeway.commons.graph.GraphUtils.GraphKernel.GraphCharacteristic;
import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.collections._PrimitiveCollections.IntList;
import org.apache.causeway.commons.internal.primitives._Longs;

Expand Down Expand Up @@ -361,7 +362,43 @@ public void visitNeighborsIndexed(final int nodeIndex,
* Returns an isomorphic graph with this graph's nodes replaced by given mapping function.
*/
public <R> Graph<R> map(final Function<T, R> nodeMapper) {
return new Graph<R>(kernel, nodes.map(nodeMapper), edgeAttributeByPackedEdgeIndex());
var graph = new Graph<R>(kernel, nodes.map(nodeMapper), edgeAttributeByPackedEdgeIndex());
_Assert.assertEquals(kernel.nodeCount(), graph.nodes().size());
return graph;
}

/**
* Returns a sub-graph with any nodes removed from this graph, that do not pass the filter.
*/
public Graph<T> filter(final Predicate<T> filter) {
if(nodes.isEmpty()) return this;

var nodeType = _Casts.<Class<T>>uncheckedCast(nodes.getFirst().get().getClass());
var builder = new GraphBuilder<T>(nodeType, kernel().characteristics);
var isUndirected = kernel().isUndirected();

nodes.forEach(IndexedConsumer.zeroBased((nodeIndex, node)->{
if(filter.test(node)) {
builder.addNode(node);
Graph.this.visitNeighborsIndexed(nodeIndex, (neighborIndex, neighbor)->{
if(isUndirected
&& neighborIndex<nodeIndex) {
return;
}
if(filter.test(neighbor)) {
var edgeAttributes = edgeAttributeByPackedEdgeIndex()
.get(_Longs.pack(nodeIndex, neighborIndex));
if(edgeAttributes!=null) {
builder.addEdge(node, neighbor, edgeAttributes);
} else {
builder.addEdge(node, neighbor);
}
}
});
}
}));

return builder.build();
}

// -- EDGE ATTRIBUTE
Expand Down Expand Up @@ -468,6 +505,7 @@ public class GraphBuilder<T> {
private final Class<T> nodeType;
private final ImmutableEnumSet<GraphCharacteristic> characteristics;
private final boolean isUndirected;
private final Map<T, Integer> indexByNode;
private final List<T> nodeList;
private final IntList fromNode = new IntList(4); // best guess initial edge capacity
private final IntList toNode = new IntList(4); // best guess initial edge capacity
Expand All @@ -484,8 +522,9 @@ public static <T> GraphBuilder<T> undirected(final Class<T> nodeType) {
}

/**
* Adds a new node to the graph.
* @apiNote nodes are not required to be unique with respect to {@link Objects#equals}.
* Adds a new node to the graph, respecting node equality, that is,
* no duplicates are added.
* @apiNote duplicates with respect to {@link Objects#equals} are not added
*/
public GraphBuilder<T> addNode(final @NonNull T node) {
addNodeHonoringIndexMap(node);
Expand Down Expand Up @@ -559,9 +598,8 @@ private GraphBuilder(final Class<T> nodeType, final ImmutableEnumSet<GraphCharac
this.characteristics = characteristics;
this.isUndirected = characteristics.contains(GraphCharacteristic.UNDIRECTED);
this.nodeList = new ArrayList<>();
//XXX map implementation is not required to be ordered, could use a HashMap here as well.
// This is purely a performance question!
this.edgeAttributeByPackedEdgeIndex = new TreeMap<>();
this.indexByNode = new HashMap<>();
this.edgeAttributeByPackedEdgeIndex = new HashMap<>();
}

public Graph<T> build() {
Expand Down Expand Up @@ -604,7 +642,12 @@ private int indexOfWithAdd(final T node) {
}

private int addNodeHonoringIndexMap(final T node) {
final Integer nodeIndex = indexByNode.get(node);
// skip adding if the node is a duplicate
if(nodeIndex!=null) return nodeIndex;

final int nextIndex = nodeList.size();
indexByNode.put(node, nextIndex);
nodeList.add(node);
if(nodeIndexByNode!=null) {
nodeIndexByNode.put(node, nextIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertLinesMatch;

import org.springframework.util.StringUtils;

Expand Down Expand Up @@ -89,6 +90,38 @@ void builderUndirected() {
TextUtils.readLines(textForm).filter(StringUtils::hasLength));
}

@Test
void nodeEqualityDirected() {
var graph = GraphUtils.GraphBuilder.directed(Customer.class)
.addNode(new Customer("A"))
.addNode(new Customer("B"))
.addNode(new Customer("A"))
.addEdge(0, 1)
.build();

//debug
//System.err.println(graph.toString(Customer::getName));

assertEquals(2,
graph.nodes().size());
}

@Test
void nodeEqualityUndirected() {
var graph = GraphUtils.GraphBuilder.undirected(Customer.class)
.addNode(new Customer("A"))
.addNode(new Customer("B"))
.addNode(new Customer("A"))
.addEdge(0, 1)
.build();

//debug
//System.err.println(graph.toString(Customer::getName));

assertEquals(2,
graph.nodes().size());
}

@Test
void builderWithEdgeAttributes() {
var gBuilder = GraphUtils.GraphBuilder.undirected(Customer.class);
Expand Down Expand Up @@ -172,4 +205,54 @@ void kernelSubgraph() {
assertEquals(2, subgraph3.edgeCount());
}

@Test
void filterDirectedGraph() {
var graph = GraphUtils.GraphBuilder.directed(Customer.class)
.addNode(new Customer("A"))
.addNode(new Customer("B"))
.addNode(new Customer("C"))
.addNode(new Customer("D"))
.addEdge(0, 1, 0.1) // A -> B (weight=0.1)
.addEdge(1, 2) // B -> C
.addEdge(2, 0, 0.7) // C -> A (weight=0.7)
.build()
.filter(node->!node.getName().equals("C")); // now remove C

var textForm = graph.toString(Customer::getName);

//debug
//System.err.println(textForm);

assertLinesMatch(
Can.of("A -> B (0.1)", "B", "D").toList(),
TextUtils.readLines(textForm).filter(StringUtils::hasLength).toList());
}

@Test
void filterUndirectedGraph() {
var a = new Customer("A");
var b = new Customer("B");
var c = new Customer("C");
var d = new Customer("D");

var graph = GraphUtils.GraphBuilder.undirected(Customer.class)
.addEdge(a, b, 0.1) // A - B (weight=0.1)
.addEdge(c, a) // A - C
.addEdge(c, b, 0.7) // B - C (weight=0.7)
.addNode(d)
.build()
.filter(node->!node.getName().equals("C")) // now remove C
;

var textForm = graph.toString(
NodeFormatter.of(Customer::getName),
edgeAttr->String.format("(weight=%.1f)", (double)edgeAttr));
//debug
//System.err.println(textForm);

assertLinesMatch(
Can.of("A - B (weight=0.1)", "D").toList(),
TextUtils.readLines(textForm).filter(StringUtils::hasLength).toList());
}

}

0 comments on commit 2e76cef

Please sign in to comment.