diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6139a03..bec3688 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,6 +8,7 @@
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
+ checkstyle
id("com.gradleup.shadow") version "9.0.0-beta12"
}
@@ -16,6 +17,13 @@ repositories {
mavenCentral()
}
+sourceSets { main { java { srcDirs("src/main/java") } } }
+
+checkstyle {
+ toolVersion = "10.12.4"
+ configFile = file("./checkstyle.xml")
+}
+
dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
@@ -26,6 +34,8 @@ dependencies {
implementation(libs.guava)
implementation("org.jline:jline:3.29.0")
+
+ compileOnly("com.github.spotbugs:spotbugs-annotations:3.1.3")
}
// Apply a specific Java toolchain to ease working on different environments.
diff --git a/app/checkstyle.xml b/app/checkstyle.xml
new file mode 100644
index 0000000..97e8e9e
--- /dev/null
+++ b/app/checkstyle.xml
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/fun/youthlic/Graph.java b/app/src/main/java/fun/youthlic/Graph.java
index aa6d303..92df719 100644
--- a/app/src/main/java/fun/youthlic/Graph.java
+++ b/app/src/main/java/fun/youthlic/Graph.java
@@ -12,289 +12,353 @@ import java.util.PriorityQueue;
import java.util.Random;
import java.util.function.Function;
+/**
+ * Core class for impl graph.
+ */
public class Graph {
- public record EdgeRecord(String node1, String node2, int weight) {
+ private final Map nodes;
+ private Map pageRanks;
+
+ /**
+ * Initial func of Graph.
+ *
+ * @param input input text
+ * @param d d of page rank
+ */
+ public Graph(final String input, final double d) {
+ nodes = new HashMap<>();
+ parse(input);
+ initRageRanks(d);
+ }
+
+ /**
+ * Initial func of Graph with default d.
+ *
+ * @param input input text
+ */
+ public Graph(final String input) {
+ nodes = new HashMap<>();
+ parse(input);
+ initRageRanks(0.85);
+ }
+
+ /**
+ * Find bridge words.
+ *
+ * @param word1 first word
+ * @param word2 second word
+ * @return list of bridge words
+ */
+ public Optional> findBridgeWords(final String word1, final String word2) {
+ final Node node1 = nodes.get(word1.toLowerCase(Locale.ENGLISH));
+ final Node node2 = nodes.get(word2.toLowerCase(Locale.ENGLISH));
+
+ if (node1 == null || node2 == null) {
+ return Optional.empty();
}
- private static class Node {
+ final var bridgeWords = new ArrayList();
+ for (final var node : node1.edges.keySet()) {
+ if (node.edges.containsKey(node2)) {
+ bridgeWords.add(node.id);
+ }
+ }
+ return Optional.of(bridgeWords);
+ }
- String id;
- Map edges;
+ /**
+ * Find the shortest path between two words.
+ *
+ * @param word1 first word
+ * @param word2 second word
+ * @return list of the words in the path
+ */
+ public Optional> findShortestPath(final String word1, final String word2) {
+ final var startWord = word1.toLowerCase(Locale.ENGLISH);
+ final var endWord = word2.toLowerCase(Locale.ENGLISH);
+ final var path = new ArrayList();
- Node(final String id) {
- this.id = id;
- this.edges = new HashMap<>();
- }
-
- void addEdge(final Node target) {
- edges.put(target, edges.getOrDefault(target, 0) + 1);
- }
-
- List getDOTEdges(final Function callback) {
- final var dotEdges = new ArrayList();
- for (final var entry : edges.entrySet()) {
- final Node target = entry.getKey();
- final int weight = entry.getValue();
- dotEdges.add(
- callback.apply(new EdgeRecord(id, target.id, weight)) + ";\n");
- }
- return dotEdges;
- }
-
- List getDOTEdges() {
- final var dotEdges = new ArrayList();
- for (final var entry : edges.entrySet()) {
- final Node target = entry.getKey();
- final int weight = entry.getValue();
- dotEdges.add(
- String.format("%s -> %s [label=\"%d\"];\n", id, target.id, weight));
- }
- return dotEdges;
- }
+ if (!nodes.containsKey(startWord) || !nodes.containsKey(endWord)) {
+ return Optional.empty();
}
- private final Map nodes;
+ final Node startNode = nodes.get(startWord);
+ final Node endNode = nodes.get(endWord);
- private Map pageRanks;
-
- public Graph(final String input, final double d) {
- nodes = new HashMap<>();
- parse(input);
- initRageRanks(d);
+ if (startNode == endNode) {
+ path.add(startNode.id);
+ return Optional.of(path);
}
- public Graph(final String input) {
- nodes = new HashMap<>();
- parse(input);
- initRageRanks(0.85);
+ final var distances = new HashMap();
+ final var previousNodes = new HashMap();
+ final var queue = new PriorityQueue(
+ Comparator.comparingInt(node -> distances.getOrDefault(node, Integer.MAX_VALUE)));
+
+ for (final var node : nodes.values()) {
+ distances.put(node, Integer.MAX_VALUE);
+ }
+ distances.put(startNode, 0);
+ queue.add(startNode);
+
+ while (!queue.isEmpty()) {
+ final Node current = queue.poll();
+ for (final var edge : current.edges.entrySet()) {
+ final Node neighbor = edge.getKey();
+ final int weight = edge.getValue();
+
+ final int newDist = distances.get(current) + weight;
+ if (newDist < distances.get(neighbor)) {
+ distances.put(neighbor, newDist);
+ previousNodes.put(neighbor, current);
+ queue.add(neighbor);
+ }
+ }
}
- public Optional> findBridgeWords(final String word1, final String word2) {
- final Node node1 = nodes.get(word1.toLowerCase(Locale.ENGLISH));
- final Node node2 = nodes.get(word2.toLowerCase(Locale.ENGLISH));
-
- if (node1 == null || node2 == null) {
- return Optional.empty();
- }
-
- final var bridgeWords = new ArrayList();
- for (final var node : node1.edges.keySet()) {
- if (node.edges.containsKey(node2)) {
- bridgeWords.add(node.id);
- }
- }
- return Optional.of(bridgeWords);
+ if (distances.get(endNode) == Integer.MAX_VALUE) {
+ return Optional.of(path);
}
- public Optional> findShortestPath(final String word1, final String word2) {
- final var startWord = word1.toLowerCase(Locale.ENGLISH);
- final var endWord = word2.toLowerCase(Locale.ENGLISH);
- final var path = new ArrayList();
-
- if (!nodes.containsKey(startWord) || !nodes.containsKey(endWord)) {
- return Optional.empty();
- }
-
- final Node startNode = nodes.get(startWord);
- final Node endNode = nodes.get(endWord);
-
- if (startNode == endNode) {
- path.add(startNode.id);
- return Optional.of(path);
- }
-
- final var distances = new HashMap();
- final var previousNodes = new HashMap();
- final var queue = new PriorityQueue(
- Comparator.comparingInt(node -> distances.getOrDefault(node, Integer.MAX_VALUE)));
-
- for (final var node : nodes.values()) {
- distances.put(node, Integer.MAX_VALUE);
- }
- distances.put(startNode, 0);
- queue.add(startNode);
-
- while (!queue.isEmpty()) {
- final Node current = queue.poll();
- for (final var edge : current.edges.entrySet()) {
- final Node neighbor = edge.getKey();
- final int weight = edge.getValue();
-
- final int newDist = distances.get(current) + weight;
- if (newDist < distances.get(neighbor)) {
- distances.put(neighbor, newDist);
- previousNodes.put(neighbor, current);
- queue.add(neighbor);
- }
- }
- }
-
- if (distances.get(endNode) == Integer.MAX_VALUE) {
- return Optional.of(path);
- }
-
- final var reversePath = new LinkedList();
- Node current = endNode;
- while (current != null) {
- reversePath.addFirst(current.id);
- current = previousNodes.get(current);
- }
-
- return Optional.of(reversePath);
+ final var reversePath = new LinkedList();
+ Node current = endNode;
+ while (current != null) {
+ reversePath.addFirst(current.id);
+ current = previousNodes.get(current);
}
- public double computePageRank(final String nodeId) {
- final Node node = nodes.get(nodeId);
- if (node == null) {
- return -1.0;
- }
+ return Optional.of(reversePath);
+ }
- return pageRanks.getOrDefault(node, -1.0);
+ /**
+ * Calculate rage rank.
+ *
+ * @param nodeId id of node
+ * @return page rank
+ */
+ public double computePageRank(final String nodeId) {
+ final Node node = nodes.get(nodeId);
+ if (node == null) {
+ return -1.0;
}
- public List randomWalk() {
- final var path = new ArrayList();
- if (nodes.isEmpty()) {
- return path;
- }
+ return pageRanks.getOrDefault(node, -1.0);
+ }
- final var random = new Random();
- final var nodeIds = new ArrayList<>(nodes.keySet());
- final String startNodeId = nodeIds.get(random.nextInt(nodeIds.size()));
- path.add(startNodeId);
-
- final var usedEdges = new HashMap();
- var currentNode = nodes.get(startNodeId);
- while (true) {
- final var availableEdge = new ArrayList>();
- int totalWeight = 0;
- for (final var entry : currentNode.edges.entrySet()) {
- final String edgeKey = currentNode.id + " -> " + entry.getKey().id;
- if (usedEdges.getOrDefault(edgeKey, 0) < 2) {
- availableEdge.add(entry);
- totalWeight += entry.getValue();
- }
- }
-
- if (availableEdge.isEmpty()) {
- break;
- }
-
- final double randomValue = random.nextDouble() * totalWeight;
- double cumulative = 0.0;
- Node nextNode = null;
-
- for (final var entry : availableEdge) {
- cumulative += entry.getValue();
- if (randomValue < cumulative) {
- nextNode = entry.getKey();
- break;
- }
- }
-
- assert nextNode != null;
- final String edgeKey = currentNode.id + " -> " + nextNode.id;
- final int edgeTimes = usedEdges.getOrDefault(edgeKey, 0) + 1;
- usedEdges.put(edgeKey, edgeTimes);
- path.add(nextNode.id);
- currentNode = nextNode;
-
- if (edgeTimes >= 2) {
- break;
- }
- }
- return path;
+ /**
+ * Random walk.
+ *
+ * @return list of words in the walk path
+ */
+ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("PREDICTABLE_RANDOM")
+ public List randomWalk() {
+ final var path = new ArrayList();
+ if (nodes.isEmpty()) {
+ return path;
}
- public String toDOT(final Function callback) {
- final var sb = new StringBuilder();
- sb.append("digraph G {\n");
- for (final Node node : nodes.values()) {
- for (final String edge : node.getDOTEdges(callback)) {
- sb.append(" ").append(edge);
- }
+ final var random = new Random();
+ final var nodeIds = new ArrayList<>(nodes.keySet());
+ final String startNodeId = nodeIds.get(random.nextInt(nodeIds.size()));
+ path.add(startNodeId);
+
+ final var usedEdges = new HashMap();
+ var currentNode = nodes.get(startNodeId);
+ while (true) {
+ final var availableEdge = new ArrayList>();
+ int totalWeight = 0;
+ for (final var entry : currentNode.edges.entrySet()) {
+ final String edgeKey = currentNode.id + " -> " + entry.getKey().id;
+ if (usedEdges.getOrDefault(edgeKey, 0) < 2) {
+ availableEdge.add(entry);
+ totalWeight += entry.getValue();
}
- sb.append("}");
- return sb.toString();
+ }
+
+ if (availableEdge.isEmpty()) {
+ break;
+ }
+
+ final double randomValue = random.nextDouble() * totalWeight;
+ double cumulative = 0.0;
+ Node nextNode = null;
+
+ for (final var entry : availableEdge) {
+ cumulative += entry.getValue();
+ if (randomValue < cumulative) {
+ nextNode = entry.getKey();
+ break;
+ }
+ }
+
+ assert nextNode != null;
+ final String edgeKey = currentNode.id + " -> " + nextNode.id;
+ final int edgeTimes = usedEdges.getOrDefault(edgeKey, 0) + 1;
+ usedEdges.put(edgeKey, edgeTimes);
+ path.add(nextNode.id);
+ currentNode = nextNode;
+
+ if (edgeTimes >= 2) {
+ break;
+ }
+ }
+ return path;
+ }
+
+ /**
+ * Generate DOT lang to describe the graph.
+ *
+ * @param callback function to modify the option of the node
+ * @return string of DOT lang
+ */
+ @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+ public String toDOT(final Function callback) {
+ final var sb = new StringBuilder();
+ sb.append("digraph G {\n");
+ for (final Node node : nodes.values()) {
+ for (final String edge : node.getDOTEdges(callback)) {
+ sb.append(" ").append(edge);
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Generate DOT lang to describe the graph without callback lambda function.
+ *
+ * @return string of DOT lang
+ */
+ @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+ public String toDOT() {
+ final var sb = new StringBuilder();
+ sb.append("digraph G {\n");
+ for (final Node node : nodes.values()) {
+ for (final String edge : node.getDOTEdges()) {
+ sb.append(" ").append(edge);
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private void parse(final String input) {
+ final String[] tokens = input.toLowerCase(Locale.ENGLISH).split("[ \n\r]+");
+ final var words = new ArrayList();
+
+ for (final var token : tokens) {
+ final String word = token.replaceAll("[^a-zA-Z]", "");
+ if (!word.isEmpty()) {
+ words.add(word);
+ }
}
- public String toDOT() {
- final var sb = new StringBuilder();
- sb.append("digraph G {\n");
- for (final Node node : nodes.values()) {
- for (final String edge : node.getDOTEdges()) {
- sb.append(" ").append(edge);
- }
- }
- sb.append("}");
- return sb.toString();
+ for (int i = 0; i < words.size() - 1; i++) {
+ final String currentWord = words.get(i);
+ final String nextWord = words.get(i + 1);
+
+ final Node currentNode = getOrCreateNode(currentWord);
+ final Node nextNode = getOrCreateNode(nextWord);
+
+ currentNode.addEdge(nextNode);
+ }
+ }
+
+ private Node getOrCreateNode(final String word) {
+ return nodes.computeIfAbsent(word, Node::new);
+ }
+
+ private void initRageRanks(final double d) {
+ final int totalNodes = nodes.size();
+ if (totalNodes == 0) {
+ pageRanks = new HashMap<>();
+ return;
+ }
+ var myPageRanks = new HashMap();
+ final double initRank = 1.0 / totalNodes;
+ for (final var n : nodes.values()) {
+ myPageRanks.put(n, initRank);
}
- private void parse(final String input) {
- final String[] tokens = input.toLowerCase(Locale.ENGLISH).split("[ \n\r]+");
- final var words = new ArrayList();
+ final int iterations = 100;
+ for (int i = 0; i < iterations; i++) {
+ final var newPageRanks = new HashMap();
+ nodes.values().forEach(n -> newPageRanks.put(n, 0.0));
+ for (final var curr : nodes.values()) {
+ final int totalEdges = curr.edges.size();
- for (final var token : tokens) {
- final String word = token.replaceAll("[^a-zA-Z]", "");
- if (!word.isEmpty()) {
- words.add(word);
- }
+ for (final var entry : curr.edges.entrySet()) {
+ final Node neighbor = entry.getKey();
+ final double contribution = myPageRanks.get(curr) / totalEdges;
+ newPageRanks.put(neighbor, newPageRanks.get(neighbor) + contribution);
}
+ }
- for (int i = 0; i < words.size() - 1; i++) {
- final String currentWord = words.get(i);
- final String nextWord = words.get(i + 1);
-
- final Node currentNode = getOrCreateNode(currentWord);
- final Node nextNode = getOrCreateNode(nextWord);
-
- currentNode.addEdge(nextNode);
- }
+ final double damplingTerm = (1.0 - d) / totalNodes;
+ nodes.values().forEach(n -> newPageRanks.put(n, damplingTerm + d * newPageRanks.get(n)));
+ myPageRanks = newPageRanks;
}
- private Node getOrCreateNode(final String word) {
- return nodes.computeIfAbsent(word, Node::new);
+ pageRanks = myPageRanks;
+ for (final var node : nodes.values()) {
+ if (node.edges.isEmpty()) {
+ nodes.values().stream()
+ .filter(n -> !n.equals(node))
+ .forEach(n -> pageRanks
+ .put(n, pageRanks.get(n) + pageRanks.get(node) / (totalNodes - 1)));
+ pageRanks.put(node, 0.0);
+ }
+ }
+ }
+
+ /**
+ * Record of edge.
+ *
+ * @param node1 fist node
+ * @param node2 second node
+ * @param weight weight of edge
+ *
+ */
+ public record EdgeRecord(String node1, String node2, int weight) {
+ }
+
+ private static class Node {
+
+ String id;
+ Map edges;
+
+ Node(final String id) {
+ this.id = id;
+ this.edges = new HashMap<>();
}
- private void initRageRanks(final double d) {
- final int totalNodes = nodes.size();
- if (totalNodes == 0) {
- pageRanks = new HashMap<>();
- return;
- }
- var myPageRanks = new HashMap();
- final double initRank = 1.0 / totalNodes;
- for (final var n : nodes.values()) {
- myPageRanks.put(n, initRank);
- }
-
- final int iterations = 100;
- for (int i = 0; i < iterations; i++) {
- final var newPageRanks = new HashMap();
- nodes.values().forEach(n -> newPageRanks.put(n, 0.0));
- for (final var curr : nodes.values()) {
- final int totalEdges = curr.edges.size();
-
- for (final var entry : curr.edges.entrySet()) {
- final Node neighbor = entry.getKey();
- final double contribution = myPageRanks.get(curr) / totalEdges;
- newPageRanks.put(neighbor, newPageRanks.get(neighbor) + contribution);
- }
- }
-
- final double damplingTerm = (1.0 - d) / totalNodes;
- nodes.values().forEach(n -> newPageRanks.put(n, damplingTerm + d * newPageRanks.get(n)));
- myPageRanks = newPageRanks;
- }
-
- pageRanks = myPageRanks;
- for (final var node : nodes.values()) {
- if (node.edges.isEmpty()) {
- nodes.values().stream().filter(n -> !n.equals(node))
- .forEach(n -> pageRanks.put(n, pageRanks.get(n) + pageRanks.get(node) / (totalNodes - 1)));
- pageRanks.put(node, 0.0);
- }
- }
+ void addEdge(final Node target) {
+ edges.put(target, edges.getOrDefault(target, 0) + 1);
}
+
+ @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+ List getDOTEdges(final Function callback) {
+ final var dotEdges = new ArrayList();
+ for (final var entry : edges.entrySet()) {
+ final Node target = entry.getKey();
+ final int weight = entry.getValue();
+ dotEdges.add(
+ callback.apply(new EdgeRecord(id, target.id, weight)) + ";\n");
+ }
+ return dotEdges;
+ }
+
+ @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+ List getDOTEdges() {
+ final var dotEdges = new ArrayList();
+ for (final var entry : edges.entrySet()) {
+ final Node target = entry.getKey();
+ final int weight = entry.getValue();
+ dotEdges.add(
+ String.format("%s -> %s [label=\"%d\"];%n", id, target.id, weight));
+ }
+ return dotEdges;
+ }
+ }
}
diff --git a/app/src/main/java/fun/youthlic/GraphCLI.java b/app/src/main/java/fun/youthlic/GraphCLI.java
index 1c6c245..73a16c5 100644
--- a/app/src/main/java/fun/youthlic/GraphCLI.java
+++ b/app/src/main/java/fun/youthlic/GraphCLI.java
@@ -2,7 +2,6 @@ package fun.youthlic;
import java.io.BufferedReader;
import java.io.BufferedWriter;
-import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -15,324 +14,330 @@ import java.util.Arrays;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
+/**
+ * Class for running in Commandline.
+ */
+@SuppressWarnings("ALL")
public class GraphCLI {
- static String readFile(final String filename) {
- String text = null;
- try (BufferedReader inputReader = new BufferedReader(new FileReader(new File(filename)))) {
- text = inputReader.lines().collect(Collectors.joining(System.lineSeparator()));
- } catch (final IOException e) {
- System.out.println("Error reading file: " + e.getMessage());
- }
- return text;
+ static String readFile(final String filename) {
+ String text = null;
+ try (BufferedReader inputReader = new BufferedReader(new FileReader(filename))) {
+ text = inputReader.lines().collect(Collectors.joining(System.lineSeparator()));
+ } catch (final IOException e) {
+ System.out.println("Error reading file: " + e.getMessage());
}
+ return text;
+ }
- public static void main(final String[] args) {
- GraphCLIHelper helper = null;
- Terminal terminal = null;
- try {
- terminal = TerminalBuilder.builder().system(true).build();
- } catch (final IOException e) {
- e.printStackTrace();
- System.exit(-1);
- }
- final LineReader reader = LineReaderBuilder.builder().terminal(terminal).parser(new DefaultParser())
- .build();
- while (true) {
- final String line = reader.readLine("graph> ");
- final var commands = line.split("[ \\t]+");
- if (commands.length == 0) {
- continue;
- }
- switch (commands[0]) {
- case "load":
- if (commands.length != 2) {
- System.out.println("Usage: load ");
- } else {
- final var text = readFile(commands[1]);
- if (text == null) {
- System.out.println("Load file failed; " + commands[1]);
- continue;
- }
- helper = new GraphCLIHelper(text);
- }
- break;
- case "help":
- System.out.println("Available commands:");
- System.out.println(" show");
- System.out.println(" dot");
- System.out.println(" help");
- System.out.println(" exit");
- System.out.println(" random-walk");
- System.out.println(" load ");
- System.out.println(" page-rank ");
- System.out.println(" bridge-words ");
- System.out.println(" new-text ");
- System.out.println(" shortest-path ");
- break;
- case "shortest-path":
- if (commands.length != 3) {
- System.out.println("Usage: shortest-path ");
- } else if (helper == null) {
- System.out.println("No graph loaded");
- break;
- } else {
- try {
- System.out.println(helper.calcShortestPath(commands[1], commands[2]));
- } catch (IOException | InterruptedException e) {
- System.out.println(e.getMessage());
- }
- }
- break;
- case "exit":
- return;
- case "random-walk":
- if (commands.length != 1) {
- System.out.println("Usage: random-walk");
- } else {
- if (helper == null) {
- System.out.println("No graph loaded");
- break;
- }
- System.out.println(helper.randomWalk());
- }
- break;
- case "new-text":
- if (commands.length < 2) {
- System.out.println("Usage: new-text ");
- } else if (helper == null) {
- System.out.println("No graph loaded");
- break;
- } else {
- System.out.println(helper.generateNewText(
- String.join(" ", Arrays.asList(commands).subList(1, commands.length))));
- }
- break;
- case "show":
- if (commands.length != 1) {
- System.out.println("Usage: show");
- } else {
- if (helper == null) {
- System.out.println("No graph loaded");
- break;
- }
- GraphCLIHelper.showDirectedGraph(helper.graph);
- }
- break;
- case "dot":
- if (commands.length != 1) {
- System.out.println("Usage: dot");
- } else {
- if (helper == null) {
- System.out.println("No graph loaded");
- break;
- }
- System.out.println(helper.graph.toDOT());
- }
- case "page-rank":
- if (commands.length != 2) {
- System.out.println("Usage: page-rank ");
- } else if (helper == null) {
- System.out.println("No graph loaded");
- } else {
- final var pageRank = helper.calPageRank(commands[1]);
- if (pageRank < 0.0) {
- System.out.println("Invalid word");
- } else {
- System.out.println("PageRank of " + commands[1] + ": " + pageRank);
- }
- }
- break;
- case "bridge-words":
- if (commands.length != 3) {
- System.out.println("Usage: bridge-words ");
- } else if (helper == null) {
- System.out.println("No graph loaded");
- } else {
- System.out.println(helper.queryBridgeWords(commands[1], commands[2]));
- }
- break;
- default:
- System.out.println("Unknown command: " + commands[0]);
- break;
- }
- }
+ /**
+ * CommandLine entry point.
+ *
+ * @param args launch args
+ */
+ public static void main(final String[] args) {
+ GraphCLIHelper helper = null;
+ Terminal terminal = null;
+ try {
+ terminal = TerminalBuilder.builder().system(true).build();
+ } catch (final IOException e) {
+ e.printStackTrace();
+ System.exit(-1);
}
+ final LineReader reader = LineReaderBuilder.builder().terminal(terminal)
+ .parser(new DefaultParser())
+ .build();
+ while (true) {
+ final String line = reader.readLine("graph> ");
+ final var commands = line.split("[ \\t]+");
+ if (commands.length == 0) {
+ continue;
+ }
+ switch (commands[0]) {
+ case "load":
+ if (commands.length != 2) {
+ System.out.println("Usage: load ");
+ } else {
+ final var text = readFile(commands[1]);
+ if (text == null) {
+ System.out.println("Load file failed; " + commands[1]);
+ continue;
+ }
+ helper = new GraphCLIHelper(text);
+ }
+ break;
+ case "help":
+ System.out.println("Available commands:");
+ System.out.println(" show");
+ System.out.println(" dot");
+ System.out.println(" help");
+ System.out.println(" exit");
+ System.out.println(" random-walk");
+ System.out.println(" load ");
+ System.out.println(" page-rank ");
+ System.out.println(" bridge-words ");
+ System.out.println(" new-text ");
+ System.out.println(" shortest-path ");
+ break;
+ case "shortest-path":
+ if (commands.length != 3) {
+ System.out.println("Usage: shortest-path ");
+ } else if (helper == null) {
+ System.out.println("No graph loaded");
+ break;
+ } else {
+ try {
+ System.out.println(helper.calcShortestPath(commands[1], commands[2]));
+ } catch (IOException | InterruptedException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ break;
+ case "exit":
+ return;
+ case "random-walk":
+ if (commands.length != 1) {
+ System.out.println("Usage: random-walk");
+ } else {
+ if (helper == null) {
+ System.out.println("No graph loaded");
+ break;
+ }
+ System.out.println(helper.randomWalk());
+ }
+ break;
+ case "new-text":
+ if (commands.length < 2) {
+ System.out.println("Usage: new-text ");
+ } else if (helper == null) {
+ System.out.println("No graph loaded");
+ break;
+ } else {
+ System.out.println(helper.generateNewText(
+ String.join(" ", Arrays.asList(commands).subList(1, commands.length))));
+ }
+ break;
+ case "show":
+ if (commands.length != 1) {
+ System.out.println("Usage: show");
+ } else {
+ if (helper == null) {
+ System.out.println("No graph loaded");
+ break;
+ }
+ GraphCLIHelper.showDirectedGraph(helper.graph);
+ }
+ break;
+ case "dot":
+ if (commands.length != 1) {
+ System.out.println("Usage: dot");
+ } else {
+ if (helper == null) {
+ System.out.println("No graph loaded");
+ break;
+ }
+ System.out.println(helper.graph.toDOT());
+ }
+ break;
+ case "page-rank":
+ if (commands.length != 2) {
+ System.out.println("Usage: page-rank ");
+ } else if (helper == null) {
+ System.out.println("No graph loaded");
+ } else {
+ final var pageRank = helper.calPageRank(commands[1]);
+ if (pageRank < 0.0) {
+ System.out.println("Invalid word");
+ } else {
+ System.out.println("PageRank of " + commands[1] + ": " + pageRank);
+ }
+ }
+ break;
+ case "bridge-words":
+ if (commands.length != 3) {
+ System.out.println("Usage: bridge-words ");
+ } else if (helper == null) {
+ System.out.println("No graph loaded");
+ } else {
+ System.out.println(helper.queryBridgeWords(commands[1], commands[2]));
+ }
+ break;
+ default:
+ System.out.println("Unknown command: " + commands[0]);
+ break;
+ }
+ }
+ }
}
+/**
+ * Helper class for CommandLine.
+ */
+@SuppressWarnings("ALL")
class GraphCLIHelper {
- Graph graph;
+ Graph graph;
- GraphCLIHelper(final String text) {
- graph = new Graph(text);
+ GraphCLIHelper(final String text) {
+ graph = new Graph(text);
+ }
+
+ static void showDirectedGraph(final Graph graph) {
+ final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii");
+ processBuilder.redirectErrorStream(true);
+ Process process;
+ try {
+ process = processBuilder.start();
+ } catch (final IOException e) {
+ System.err.println("Failed to start process");
+ e.printStackTrace();
+ return;
}
-
- static void showDirectedGraph(final Graph G) {
- final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii");
- processBuilder.redirectErrorStream(true);
- Process process = null;
- try {
- process = processBuilder.start();
- } catch (final IOException e) {
- System.err.println("Failed to start process");
- e.printStackTrace();
- }
- try (BufferedWriter writer = new BufferedWriter(
- new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) {
- writer.write(G.toDOT());
- writer.flush();
- } catch (final IOException e) {
- System.err.println("Failed to write to process");
- e.printStackTrace();
- }
- final StringBuilder output = new StringBuilder();
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.append(line).append(System.lineSeparator());
- }
- } catch (final IOException e) {
- System.err.println("Failed to read from process");
- e.printStackTrace();
- }
- try {
- final int exitCode = process.waitFor();
- if (exitCode != 0) {
- System.err.println("Process exited with code " + exitCode);
- } else {
- System.out.println(output.toString());
- }
- } catch (final InterruptedException e) {
- System.err.println("Process interrupted");
- e.printStackTrace();
- }
+ try (BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) {
+ writer.write(graph.toDOT());
+ writer.flush();
+ } catch (final IOException e) {
+ System.err.println("Failed to write to process");
+ e.printStackTrace();
}
+ final StringBuilder output = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ output.append(line).append(System.lineSeparator());
+ }
+ } catch (final IOException e) {
+ System.err.println("Failed to read from process");
+ e.printStackTrace();
+ }
+ try {
+ final int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ System.err.println("Process exited with code " + exitCode);
+ } else {
+ System.out.println(output.toString());
+ }
+ } catch (final InterruptedException e) {
+ System.err.println("Process interrupted");
+ e.printStackTrace();
+ }
+ }
- String queryBridgeWords(final String word1, final String word2) {
- final var option = graph.findBridgeWords(word1, word2);
- if (option.isEmpty()) {
- return "No " + word1 + " or " + word2 + " in the graph!";
- }
- final var bridgeWords = option.get();
- if (bridgeWords.isEmpty()) {
- return "No bridge words from " + word1 + " to " + word2 + "!";
- }
- final var output = new StringBuilder();
- output.append("The bridge words from ").append(word1).append(" to ").append(word2).append(" are: ");
- for (int i = 0; i < bridgeWords.size(); i++) {
- output.append(bridgeWords.get(i));
- if (i < bridgeWords.size() - 2) {
- output.append(", ");
- } else if (i == bridgeWords.size() - 2) {
- output.append(", and ");
- } else {
- output.append(".");
- }
+ String queryBridgeWords(final String word1, final String word2) {
+ final var option = graph.findBridgeWords(word1, word2);
+ if (option.isEmpty()) {
+ return "No " + word1 + " or " + word2 + " in the graph!";
+ }
+ final var bridgeWords = option.get();
+ if (bridgeWords.isEmpty()) {
+ return "No bridge words from " + word1 + " to " + word2 + "!";
+ }
+ final var output = new StringBuilder();
+ output.append("The bridge words from ").append(word1).append(" to ").append(word2).append(" are: ");
+ for (int i = 0; i < bridgeWords.size(); i++) {
+ output.append(bridgeWords.get(i));
+ if (i < bridgeWords.size() - 2) {
+ output.append(", ");
+ } else if (i == bridgeWords.size() - 2) {
+ output.append(", and ");
+ } else {
+ output.append(".");
+ }
+ }
+ return output.toString();
+ }
+
+ String generateNewText(final String inputText) {
+ final var output = new StringBuilder();
+ final var words = inputText.split("\\s+");
+ for (int i = 0; i < words.length; i++) {
+ output.append(words[i]);
+ if (i < words.length - 1) {
+ output.append(" ");
+ final var option = graph.findBridgeWords(words[i], words[i + 1]);
+ if (option.isPresent() && !option.get().isEmpty()) {
+ final var bridgeWords = option.get();
+ final Random random = new Random();
+ final int wordIndex = random.nextInt(bridgeWords.size());
+ output.append(option.get().get(wordIndex)).append(" ");
}
+ }
+ }
+ return output.toString();
+ }
+
+ String calcShortestPath(final String word1, final String word2)
+ throws IOException, InterruptedException {
+ final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii");
+ processBuilder.redirectErrorStream(true);
+ Process process = null;
+ process = processBuilder.start();
+ try (BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) {
+ writer.write(calcShortestPathToDOT(word1, word2));
+ writer.flush();
+ }
+ final StringBuilder output = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ output.append(line).append(System.lineSeparator());
+ }
+ }
+ try {
+ final int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new IOException("Process exited with code " + exitCode);
+ } else {
return output.toString();
+ }
+ } catch (final InterruptedException e) {
+ throw e;
}
+ }
- String generateNewText(final String inputText) {
- final var output = new StringBuilder();
- final var words = inputText.split("\\s+");
- for (int i = 0; i < words.length; i++) {
- output.append(words[i]);
- if (i < words.length - 1) {
- output.append(" ");
- final var option = graph.findBridgeWords(words[i], words[i + 1]);
- if (option.isPresent() && !option.get().isEmpty()) {
- final var bridgeWords = option.get();
- final Random random = new Random();
- final int wordIndex = random.nextInt(bridgeWords.size());
- output.append(option.get().get(wordIndex)).append(" ");
- }
- }
- }
- return output.toString();
+ String calcShortestPathToDOT(final String word1, final String word2) {
+ var option = graph.findShortestPath(word1, word2);
+ if (option.isEmpty()) {
+ return "No " + word1 + " or " + word2 + " in the graph";
+ } else {
+ final var path = option.get();
+ if (path.isEmpty()) {
+ return "No path from " + word1 + " to " + word2;
+ }
+ return graph.toDOT(r -> String.format(
+ "\"%s\" -> \"%s\" [label=\"%d\"%s]",
+ r.node1() + (path.contains(r.node1()) ? "~" : ""),
+ r.node2() + (path.contains(r.node2()) ? "~" : ""),
+ r.weight(),
+ (path.contains(r.node1()) && path.contains(r.node2())
+ && IntStream.range(0, path.size() - 1).anyMatch(
+ i -> path.get(i).equals(r.node1()) && path.get(i + 1).equals(r.node2())))
+ ? ",color=\"red\""
+ : ""));
}
+ }
- String calcShortestPath(final String word1, final String word2)
- throws IOException, InterruptedException {
- final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii");
- processBuilder.redirectErrorStream(true);
- Process process = null;
- try {
- process = processBuilder.start();
- } catch (final IOException e) {
- throw e;
- }
- try (BufferedWriter writer = new BufferedWriter(
- new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) {
- writer.write(calcShortestPathToDOT(word1, word2));
- writer.flush();
- } catch (final IOException e) {
- throw e;
- }
- final StringBuilder output = new StringBuilder();
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.append(line).append(System.lineSeparator());
- }
- } catch (final IOException e) {
- throw e;
- }
- try {
- final int exitCode = process.waitFor();
- if (exitCode != 0) {
- throw new IOException("Process exited with code " + exitCode);
- } else {
- return output.toString();
- }
- } catch (final InterruptedException e) {
- throw e;
- }
- }
+ Double calPageRank(final String word) {
+ return graph.computePageRank(word);
+ }
- String calcShortestPathToDOT(final String word1, final String word2) {
- var option = graph.findShortestPath(word1, word2);
- if (option.isEmpty()) {
- return "No " + word1 + " or " + word2 + " in the graph";
- } else {
- final var path = option.get();
- if (path.isEmpty()) {
- return "No path from " + word1 + " to " + word2;
- }
- return graph.toDOT(r -> String.format(
- "\"%s\" -> \"%s\" [label=\"%d\"%s]",
- r.node1() + (path.contains(r.node1()) ? "~" : ""),
- r.node2() + (path.contains(r.node2()) ? "~" : ""),
- r.weight(),
- (path.contains(r.node1()) && path.contains(r.node2()) &&
- IntStream.range(0, path.size() - 1).anyMatch(
- i -> path.get(i).equals(r.node1()) && path.get(i + 1).equals(r.node2())))
- ? ",color=\"red\""
- : ""));
- }
- }
-
- Double calPageRank(final String word) {
- final var pageRank = graph.computePageRank(word);
- return pageRank;
- }
-
- String randomWalk() {
- final var text = graph.randomWalk().stream().collect(Collectors.joining(" "));
- final Path path = Paths.get("output.txt");
- try {
- Files.write(path, text.getBytes());
- } catch (final IOException e) {
- System.err.println("Failed to write to file");
- e.printStackTrace();
- }
- return text;
+ String randomWalk() {
+ final var text = String.join(" ", graph.randomWalk());
+ final Path path = Paths.get("output.txt");
+ try {
+ Files.write(path, text.getBytes());
+ } catch (final IOException e) {
+ System.err.println("Failed to write to file");
+ e.printStackTrace();
}
+ return text;
+ }
}
diff --git a/app/src/test/java/fun/youthlic/GraphCLIHelperTest.java b/app/src/test/java/fun/youthlic/GraphCLIHelperTest.java
new file mode 100644
index 0000000..7b46438
--- /dev/null
+++ b/app/src/test/java/fun/youthlic/GraphCLIHelperTest.java
@@ -0,0 +1,80 @@
+package fun.youthlic;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class GraphCLIHelperTest {
+ private final GraphCLIHelper graph = new GraphCLIHelper("B D E H A C D H A F D H A B G H A C G H A D G H");
+
+ @Test
+ void testWord1NotInGraph() {
+ var result = graph.queryBridgeWords("x", "a");
+ assertEquals("No x or a in the graph!", result);
+ }
+ @Test
+ void testWord2NotInGraph() {
+ String result = graph.queryBridgeWords("a", "x");
+ assertEquals("No a or x in the graph!", result);
+ }
+
+ @Test
+ void testBothWordsNotInGraph() {
+ String result = graph.queryBridgeWords("x", "y");
+ assertEquals("No x or y in the graph!", result);
+ }
+
+ @Test
+ void testNoBridgeWords() {
+ String result = graph.queryBridgeWords("a", "b");
+ assertEquals("No bridge words from a to b!", result);
+ }
+
+ @Test
+ void testSingleBridgeWord() {
+ String result = graph.queryBridgeWords("b", "e");
+ assertEquals("The bridge words from b to e are: d.", result);
+ }
+
+ @Test
+ void testTwoBridgeWords() {
+ String result = graph.queryBridgeWords("c", "h");
+ assertEquals("The bridge words from c to h are: g, and d.", result);
+ }
+
+ @Test
+ void testThreeBridgeWords() {
+ String result = graph.queryBridgeWords("a", "g");
+ assertEquals("The bridge words from a to g are: c, b, and d.", result);
+ }
+
+ @Test
+ void testSameWordInput() {
+ String result = graph.queryBridgeWords("a", "a");
+ assertEquals("No bridge words from a to a!", result);
+ }
+
+ @Test
+ void testWord1Empty() {
+ String result = graph.queryBridgeWords("", "a");
+ assertEquals("No or a in the graph!", result);
+ }
+
+ @Test
+ void testWord2Empty() {
+ String result = graph.queryBridgeWords("a", "");
+ assertEquals("No a or in the graph!", result);
+ }
+
+ @Test
+ void testBothWordsEmpty() {
+ String result = graph.queryBridgeWords("", "");
+ assertEquals("No or in the graph!", result);
+ }
+
+ @Test
+ void testCaseMismatch() {
+ String result = graph.queryBridgeWords("B", "E");
+ assertEquals("The bridge words from B to E are: d.", result);
+ }
+}
diff --git a/flake.nix b/flake.nix
index 42c1f7c..679e187 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,6 +23,7 @@
kotlin-language-server
ktfmt
jdt-language-server
+ checkstyle
graph-easy
graphviz