Java tutorial
/* * Copyright (C) 2016 jcastro * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package delfos.rs.trustbased; import delfos.common.StringsOrderings; import delfos.dataset.util.DatasetPrinter; import dnl.utils.text.table.TextTable; import edu.princeton.cs.algs4.AdjMatrixEdgeWeightedDigraph; import edu.princeton.cs.algs4.DirectedEdge; import edu.princeton.cs.algs4.FloydWarshall; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.io.output.WriterOutputStream; /** * * @author jcastro-inf ( https://github.com/jcastro-inf ) * * @param <Node> */ public class WeightedGraph<Node> implements Serializable, Comparable<WeightedGraph<Node>> { public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#0.00"); private static final long serialVersionUID = 115L; protected final AdjMatrixEdgeWeightedDigraph adjMatrixEdgeWeightedDigraph; protected final Map<Node, Integer> nodesIndex; protected final Map<Integer, Node> nodesByIndex; private FloydWarshall floydWarshall; /** * Crea la red de confianza con los valores indicados. * * @param weightConnections Valores de las conexiones entre los elementos. * * @throws IllegalArgumentException Si la estructura de valores de confianza es nula. */ public WeightedGraph(Map<Node, Map<Node, Number>> weightConnections) { validateConnections(weightConnections); nodesIndex = makeIndex(weightConnections); nodesByIndex = makeNodesByIndex(nodesIndex); adjMatrixEdgeWeightedDigraph = makeWeightedDiGraph(weightConnections); floydWarshall = null; validateWeightsGraph(adjMatrixEdgeWeightedDigraph); } /** * Crea la red de confianza con los valores indicados. * * * @param matrix Connections * @param ordering ordering of both columns and rows of the matrix * @throws IllegalArgumentException Si la estructura de valores de confianza es nula. */ public WeightedGraph(double[][] matrix, List<Node> ordering) { validateWeightMatrix(matrix); nodesIndex = makeIndex(ordering); nodesByIndex = makeNodesByIndex(nodesIndex); adjMatrixEdgeWeightedDigraph = makeWeightedDiGraph(nodesIndex, matrix); validateWeightsGraph(adjMatrixEdgeWeightedDigraph); } public WeightedGraph(Collection<Node> nodes, Set<PathBetweenNodes<Node>> edges) { validateEdges(edges); nodesIndex = makeIndex(nodes.stream().sorted().collect(Collectors.toList())); nodesByIndex = makeNodesByIndex(nodesIndex); double[][] matrix = makeMatrixFromEdges(edges); adjMatrixEdgeWeightedDigraph = makeWeightedDiGraph(nodesIndex, matrix); validateWeightsGraph(adjMatrixEdgeWeightedDigraph); } private double[][] makeMatrixFromEdges(Set<PathBetweenNodes<Node>> edges) { double[][] matrix = new double[nodesIndex.size()][nodesIndex.size()]; edges.parallelStream().filter(edge -> Double.isFinite(edge.getLength())).forEach(edge -> { Integer fromIndex = nodesIndex.get(edge.from()); Integer toIndex = nodesIndex.get(edge.to()); double weight = Objects.equals(fromIndex, toIndex) ? 0 : edge.getFirstWeight(); matrix[fromIndex][toIndex] = weight; }); IntStream.range(0, matrix.length).boxed().parallel().forEach(index -> matrix[index][index] = 1); return matrix; } private void validateEdges(Set<PathBetweenNodes<Node>> edges) throws IllegalArgumentException { List<PathBetweenNodes<Node>> edgesWithMoreThanOneJump = edges.parallelStream() .filter(edge -> edge.numEdges() != 1).collect(Collectors.toList()); if (!edgesWithMoreThanOneJump.isEmpty()) { System.out.println("There are edges that are paths!"); edgesWithMoreThanOneJump.forEach(edge -> System.out.println(edge)); throw new IllegalArgumentException( "The edges specified have more than one jump: " + edgesWithMoreThanOneJump.toString()); } } public Optional<Double> connectionWeight(Node node1, Node node2) { Optional<Double> weight = getEdge(node1, node2).map(edge -> edge.weight()); return weight; } /** * Devuelve la intensidad de la conexin directa entre dos nodos. * * @param node1 * @param node2 * @return */ private Optional<DirectedEdge> getEdge(Node node1, Node node2) { int indexNode1 = nodesIndex.get(node1); int indexNode2 = nodesIndex.get(node2); List<DirectedEdge> edgesFromNode1 = new ArrayList<>(); final Iterable<DirectedEdge> directedEdges = adjMatrixEdgeWeightedDigraph.adj(indexNode1); for (DirectedEdge a : directedEdges) { if (a.weight() > 0) { edgesFromNode1.add(a); } } Optional<DirectedEdge> edgeNode1ToNode2 = edgesFromNode1.parallelStream() .filter(edge -> ((edge.from() == indexNode1) && (edge.to() == indexNode2))).findAny(); return edgeNode1ToNode2; } /** * Longitud mxima de un camino sin repetir nodos. * * @return */ public int maxK() { return nodesIndex.size() - 1; } /** * Devuelve todos los nodos del grafo. * * @return */ public Set<Node> allNodes() { return nodesIndex.keySet().stream().collect(Collectors.toSet()); } public double distance(Node node1, Node node2) { initFloydWarshall(); int indexNode1 = nodesIndex.get(node1); int indexNode2 = nodesIndex.get(node2); double distance = floydWarshall.dist(indexNode1, indexNode2); return distance; } private synchronized void initFloydWarshall() { if (floydWarshall == null) { AdjMatrixEdgeWeightedDigraph distanceGaph = inverseOfEdgeValue(adjMatrixEdgeWeightedDigraph); floydWarshall = new FloydWarshall(distanceGaph); } } public Optional<PathBetweenNodes<Node>> shortestPath(Node node1, Node node2) { initFloydWarshall(); int indexNode1 = nodesIndex.get(node1); int indexNode2 = nodesIndex.get(node2); ArrayList<Node> pathNodesIncludingStartAndEnd = new ArrayList<>(); pathNodesIncludingStartAndEnd.add(node1); if (floydWarshall.hasPath(indexNode1, indexNode2)) { List<DirectedEdge> path = new ArrayList<>(); double dist = floydWarshall.dist(indexNode1, indexNode2); final Iterable<DirectedEdge> edgeIterator = floydWarshall.path(indexNode1, indexNode2); for (DirectedEdge edge : edgeIterator) { path.add(edge); } path.stream().sequential().map(edge -> edge.to()).forEach(indexNodeIntermediate -> { Node nodeIntermediate = nodesByIndex.get(indexNodeIntermediate); pathNodesIncludingStartAndEnd.add(nodeIntermediate); }); PathBetweenNodes<Node> pathBetweenNodes = new PathBetweenNodes<>(this, pathNodesIncludingStartAndEnd); return Optional.of(pathBetweenNodes); } else { return Optional.empty(); } } @Override public String toString() { String printWeightedGraph = DatasetPrinter.printWeightedGraph(this); return printWeightedGraph; } public Double[][] asMatrix() { final List<Node> nodesSorted = nodesSortingForMatrix(); Double[][] matrix = new Double[nodesSorted.size()][nodesSorted.size()]; IntStream.range(0, nodesSorted.size()).parallel().boxed().forEach(indexRow -> { Node node1 = nodesSorted.get(indexRow); IntStream.range(0, nodesSorted.size()).parallel().boxed().forEach(indexColumn -> { Node node2 = nodesSorted.get(indexColumn); double value = connectionWeight(node1, node2).orElse(0.0); matrix[indexRow][indexColumn] = value; }); }); return matrix; } public double[][] asMatrixUnboxed() { final List<Node> nodesSorted = nodesSortingForMatrix(); double[][] matrix = new double[nodesSorted.size()][nodesSorted.size()]; IntStream.range(0, nodesSorted.size()).parallel().boxed().forEach(indexRow -> { Node node1 = nodesSorted.get(indexRow); IntStream.range(0, nodesSorted.size()).parallel().boxed().forEach(indexColumn -> { Node node2 = nodesSorted.get(indexColumn); double value = connectionWeight(node1, node2).orElse(0.0); matrix[indexRow][indexColumn] = value; }); }); validateWeightMatrix(matrix); return matrix; } public List<Node> nodesSortingForMatrix() { List<Node> nodesSorted = allNodes().stream().sorted().collect(Collectors.toList()); return Collections.unmodifiableList(nodesSorted); } public void printTable(PrintStream outputStream) { TextTable textTable = getTextTable(); textTable.printTable(outputStream, 0); } public TextTable getTextTable() { return getTextTable(this.allNodes().stream().collect(Collectors.toSet())); } public TextTable getTextTable(Set<Node> nodes) { validateParameters(nodes); List<String> columnNames = new ArrayList<>(); columnNames.add("node\\node"); final List<Node> sortedNodes = this.allNodes().stream().sorted().filter(node -> nodes.contains(node)) .collect(Collectors.toList()); Object[][] data = new Object[sortedNodes.size()][sortedNodes.size() + 1]; columnNames.addAll(sortedNodes.stream().map(node -> node.toString() + " ").collect(Collectors.toList())); for (int node1index = 0; node1index < sortedNodes.size(); node1index++) { Node node1 = sortedNodes.get(node1index); int row = node1index; data[row][0] = node1.toString(); for (int node2index = 0; node2index < sortedNodes.size(); node2index++) { Node node2 = sortedNodes.get(node2index); int column = node2index + 1; String cellValue = getEdge(node1, node2).map(edge -> edge.weight()).filter(weight -> weight > 0) .map(weight -> DECIMAL_FORMAT.format(weight)).orElse("0"); data[row][column] = cellValue + " "; } } TextTable textTable = new TextTable(columnNames.toArray(new String[0]), data); return textTable; } void validateParameters(Collection<Node> nodes) throws IllegalArgumentException { boolean allMatch = nodes.parallelStream().allMatch(node -> this.allNodes().contains(node)); if (!allMatch) { throw new IllegalArgumentException("Specified nodes are not present in the weighted graph"); } } public void printTable(WriterOutputStream outputStream) { printTable(new PrintStream(outputStream)); } public String toStringTable() { TextTable textTable = getTextTable(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream recordingStream = new PrintStream(baos); textTable.printTable(recordingStream, 0); return baos.toString(); } public String toStringTable(Set<Node> nodes) { TextTable textTable = getTextTable(nodes); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream recordingStream = new PrintStream(baos); textTable.printTable(recordingStream, 0); return baos.toString(); } public AdjMatrixEdgeWeightedDigraph getWeightedDiGraph() { return cloneAdjMatrixEdgeWeightedDigraph(adjMatrixEdgeWeightedDigraph); } public static final AdjMatrixEdgeWeightedDigraph cloneAdjMatrixEdgeWeightedDigraph( AdjMatrixEdgeWeightedDigraph adjMatrixEdgeWeightedDigraph) { AdjMatrixEdgeWeightedDigraph copy = new AdjMatrixEdgeWeightedDigraph(adjMatrixEdgeWeightedDigraph.V()); IntStream.range(0, adjMatrixEdgeWeightedDigraph.V()).sequential().forEach(vertex -> { for (DirectedEdge directedEdge : adjMatrixEdgeWeightedDigraph.adj(vertex)) { copy.addEdge(directedEdge); } }); return copy; } protected final Map<Node, Integer> makeIndex(Map<Node, Map<Node, Number>> connections) { validateConnections(connections); List<Node> sortedNodes = connections.keySet().stream().sorted().collect(Collectors.toList()); return makeIndex(sortedNodes); } protected final Map<Node, Integer> makeIndex(List<Node> nodes) { return IntStream.range(0, nodes.size()).boxed() .collect(Collectors.toMap(indexNode -> nodes.get(indexNode), indexNode -> indexNode)); } protected final AdjMatrixEdgeWeightedDigraph makeWeightedDiGraph(Map<Node, Map<Node, Number>> connections) { validateConnections(connections); AdjMatrixEdgeWeightedDigraph adjMatrixEdgeWeightedDigraph1 = new AdjMatrixEdgeWeightedDigraph( nodesIndex.size()); connections.entrySet().forEach(entry -> { Node node1 = entry.getKey(); int indexNode1 = nodesIndex.get(node1); Map<Node, Number> thisNodeConnections = entry.getValue(); thisNodeConnections.entrySet().forEach(entry2 -> { Node node2 = entry2.getKey(); int indexNode2 = nodesIndex.get(node2); double weight = entry2.getValue().doubleValue(); final DirectedEdge directedEdge = new DirectedEdge(indexNode1, indexNode2, weight); if (directedEdge.weight() != weight) { System.out.println("What happen here, nobody come."); } adjMatrixEdgeWeightedDigraph1.addEdge(directedEdge); }); }); return adjMatrixEdgeWeightedDigraph1; } private static <Node> AdjMatrixEdgeWeightedDigraph makeWeightedDiGraph(Map<Node, Integer> nodesIndex, double[][] weightMatrix) { validateWeightMatrix(weightMatrix); AdjMatrixEdgeWeightedDigraph adjMatrixEdgeWeightedDigraph = new AdjMatrixEdgeWeightedDigraph( nodesIndex.size()); nodesIndex.keySet().stream().forEach(node1 -> { int indexNode1 = nodesIndex.get(node1); nodesIndex.keySet().stream().forEach(node2 -> { int indexNode2 = nodesIndex.get(node2); double weight = weightMatrix[indexNode1][indexNode2]; adjMatrixEdgeWeightedDigraph.addEdge(new DirectedEdge(indexNode1, indexNode2, weight)); }); }); return adjMatrixEdgeWeightedDigraph; } private void validateConnections(Map<Node, Map<Node, Number>> connections) throws IllegalStateException { List<Double> wrongValues = connections.values().parallelStream() .flatMap(thisNodeConnections -> thisNodeConnections.values().stream()) .map(numberValue -> numberValue.doubleValue()).filter(value -> (value > 1) || (value < 0)) .collect(Collectors.toList()); if (!wrongValues.isEmpty()) { throw new IllegalStateException( "Value must be given in [0,1] interval and it was: " + wrongValues.toString()); } } protected final Map<Integer, Node> makeNodesByIndex(Map<Node, Integer> nodesIndex) { return nodesIndex.entrySet().parallelStream() .collect(Collectors.toMap(entry -> entry.getValue(), entry -> entry.getKey())); } public double distanceJumpLimited(Node node1, Node node2, int maxjumps) { final double distance = this.shortestPath(node1, node2).filter(path -> path.numJumps() > maxjumps) .map(path -> path.getLength()).orElse(Double.NaN); return distance; } private static void validateWeightMatrix(double[][] weightMatrix) { List<Double> wrongValues = IntStream.range(0, weightMatrix.length).boxed().map(row -> weightMatrix[row]) .flatMap(row -> IntStream.range(0, row.length).boxed().map(collumn -> row[collumn])) .filter(value -> (value > 1.0) || (value < 0.0)).collect(Collectors.toList()); if (!wrongValues.isEmpty()) { throw new IllegalStateException( "Value must be given in [0,1] interval and it was: " + wrongValues.toString()); } } private static void validateWeightsGraph(AdjMatrixEdgeWeightedDigraph adjMatrixEdgeWeightedDigraph) { List<DirectedEdge> allEdges = IntStream.range(0, adjMatrixEdgeWeightedDigraph.V()).boxed().parallel() .map(vertex -> { Iterable<DirectedEdge> iterator = adjMatrixEdgeWeightedDigraph.adj(vertex); ArrayList<DirectedEdge> listOfEdges = new ArrayList<>(); for (DirectedEdge edge : iterator) { listOfEdges.add(edge); } return listOfEdges; }).flatMap(listOfEdges -> listOfEdges.parallelStream()).collect(Collectors.toList()); List<DirectedEdge> badEdges = allEdges.parallelStream() .filter(edge -> (edge.weight() < 0) || (edge.weight() > 1)).collect(Collectors.toList()); if (!badEdges.isEmpty()) { System.out.println("List of bad edges:"); badEdges.forEach(edge -> System.out.println("\t" + edge)); throw new IllegalStateException("arg"); } } public static final AdjMatrixEdgeWeightedDigraph inverseOfEdgeValue( AdjMatrixEdgeWeightedDigraph distanceGraph) { AdjMatrixEdgeWeightedDigraph weightGraph = new AdjMatrixEdgeWeightedDigraph(distanceGraph.V()); List<DirectedEdge> allEdges = IntStream.range(0, distanceGraph.V()).boxed().map(vertex -> { Iterable<DirectedEdge> iterator = distanceGraph.adj(vertex); ArrayList<DirectedEdge> listOfEdges = new ArrayList<>(); for (DirectedEdge edge : iterator) { listOfEdges.add(edge); } return listOfEdges; }).flatMap(listOfEdges -> listOfEdges.stream()).collect(Collectors.toList()); List<DirectedEdge> allEdgesConverted = allEdges.stream().map(edge -> { final double weight = edge.weight(); double distance = 1 / weight; if (weight == 0) { distance = Double.POSITIVE_INFINITY; } return new DirectedEdge(edge.from(), edge.to(), distance); }).collect(Collectors.toList()); allEdgesConverted.forEach(edge -> weightGraph.addEdge(edge)); return weightGraph; } public String toPairwiseDistancesTable() { return toPairwiseDistancesTable(this.nodesIndex.keySet()); } public String toPairwiseDistancesTable(Set<Node> nodes) { TextTable textTable = getPairwiseDistancesTable(nodes); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream recordingStream = new PrintStream(baos); textTable.printTable(recordingStream, 0); return baos.toString(); } public TextTable getPairwiseDistancesTable(Collection<Node> nodes) { initFloydWarshall(); validateParameters(nodes); List<String> columnNames = new ArrayList<>(); columnNames.add("node\\node"); final List<Node> sortedNodes = this.allNodes().stream().sorted().filter(node -> nodes.contains(node)) .collect(Collectors.toList()); Object[][] data = new Object[sortedNodes.size()][sortedNodes.size() + 1]; columnNames.addAll(sortedNodes.stream().map(node -> node.toString()).collect(Collectors.toList())); for (int node1index = 0; node1index < sortedNodes.size(); node1index++) { Node node1 = sortedNodes.get(node1index); int row = node1index; data[row][0] = node1.toString(); for (int node2index = 0; node2index < sortedNodes.size(); node2index++) { Node node2 = sortedNodes.get(node2index); int column = node2index + 1; double dist = floydWarshall.dist(node1index, node2index); String cellValue = DECIMAL_FORMAT.format(dist) + " "; data[row][column] = cellValue; } } TextTable textTable = new TextTable(columnNames.toArray(new String[0]), data); return textTable; } public WeightedGraph<Node> getSubGraph(Collection<Node> nodes) { Map<Node, Map<Node, Number>> edgesOfSubGraph = getSubGraphEdges(nodes); WeightedGraph<Node> subGraph = new WeightedGraph<>(edgesOfSubGraph); return subGraph; } private Map<Node, Map<Node, Number>> getSubGraphEdges(Collection<Node> nodes) { Map<Node, Map<Node, Number>> edgesOfSubGraph = nodes.parallelStream() .collect(Collectors.toMap(node1 -> node1, node1 -> { Map<Node, Number> edgesFromThisVertex = nodes.parallelStream() .filter(node2 -> this.connectionWeight(node1, node2).isPresent()) .collect(Collectors.toMap(node2 -> node2, node2 -> { return this.connectionWeight(node1, node2).get(); })); return edgesFromThisVertex; })); return edgesOfSubGraph; } public Collection<PathBetweenNodes<Node>> getEdgesFromNode(Node node1) { List<PathBetweenNodes<Node>> edgesFromNode = this.allNodes().parallelStream() .map(node2 -> this.getEdge(node1, node2)).filter(edge -> edge.isPresent()).map(edge -> edge.get()) .filter(edge -> edge.from() != edge.to()).map(edge -> { Node node2 = nodesByIndex.get(edge.to()); return new PathBetweenNodes<>(this, Arrays.asList(node1, node2)); }).collect(Collectors.toList()); return edgesFromNode; } public Set<PathBetweenNodes<Node>> allEdges() { Set<PathBetweenNodes<Node>> allEdges = this.allNodes().parallelStream().flatMap(node1 -> { Collection<PathBetweenNodes<Node>> edgesFromNode = this.getEdgesFromNode(node1); return edgesFromNode.parallelStream(); }).filter(path -> path.isEdge()).filter(path -> !path.isSelf()).collect(Collectors.toSet()); return allEdges; } @Override public int compareTo(WeightedGraph<Node> o) { List<Node> allNodes = this.allNodes().stream().sorted().collect(Collectors.toList()); List<Node> allNodesOther = o.allNodes().stream().sorted().collect(Collectors.toList()); int compareNatural = StringsOrderings.compareNatural(allNodes.toString(), allNodesOther.toString()); if (compareNatural != 0) { return compareNatural; } else { return Integer.compare(this.hashCode(), o.hashCode()); } } public String printPairwiseDistancesTable() { TextTable textTable = getPairwiseDistancesTable(this.allNodes()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream recordingStream = new PrintStream(baos); textTable.printTable(recordingStream, 0); return baos.toString(); } public boolean hasAnyEdge() { Optional<PathBetweenNodes<Node>> longestPath = allNodes().parallelStream() .flatMap(user1 -> allNodes().parallelStream().map(user2 -> shortestPath(user1, user2))) .filter(optionalPath -> optionalPath.isPresent()).map(optionalPath -> optionalPath.get()) .filter(path -> !path.isSelf()).collect(Collectors.maxBy(PathBetweenNodes.FURTHEST_PATH())); return longestPath.isPresent(); } public boolean hasAnyEdgeMissing() { long count = allEdges().parallelStream().filter(edge -> !edge.isSelf()).count(); Optional<Optional<PathBetweenNodes<Node>>> missingEdge = allNodes().parallelStream() .flatMap(user1 -> allNodes().parallelStream().map(user2 -> shortestPath(user1, user2))) .filter(optionalPath -> !optionalPath.isPresent()).findAny(); return !missingEdge.isPresent(); } public Map<Node, Map<Node, Double>> getCompleteMapOfConnections() { Map<Node, Map<Node, Double>> edgesOfSubGraph = allNodes().parallelStream() .collect(Collectors.toMap(node1 -> node1, node1 -> { Map<Node, Double> edgesFromThisVertex = allNodes().parallelStream() .collect(Collectors.toMap(node2 -> node2, node2 -> { return this.connectionWeight(node1, node2).orElse(0.0); })); return edgesFromThisVertex; })); return edgesOfSubGraph; } }