From d930f7a6f5c8465832513e271cb0f9f9129d17a9 Mon Sep 17 00:00:00 2001 From: ulic-youthlic Date: Tue, 29 Apr 2025 19:12:55 +0800 Subject: [PATCH] finish GraphCLI --- app/src/main/java/fun/youthlic/Graph.java | 42 +-- app/src/main/java/fun/youthlic/GraphCLI.java | 330 ++++++++++++++++++- 2 files changed, 329 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/fun/youthlic/Graph.java b/app/src/main/java/fun/youthlic/Graph.java index 6695169..aa6d303 100644 --- a/app/src/main/java/fun/youthlic/Graph.java +++ b/app/src/main/java/fun/youthlic/Graph.java @@ -11,7 +11,6 @@ import java.util.Optional; import java.util.PriorityQueue; import java.util.Random; import java.util.function.Function; -import java.util.stream.IntStream; public class Graph { @@ -55,29 +54,6 @@ public class Graph { } } - public static void main(final String[] args) { - final var g = new Graph( - "To explore strange new worlds, To seek out new life and new civilizations"); - final var words = g.findBridgeWords("team", "so"); - System.out.println(words); - final var path = g.findShortestPath("the", "shared"); - System.out.println(path); - System.out.println( - g.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\"" - : ""))); - System.out.println(g.computePageRank("new", 0.85)); - System.out.println(g.randomWalk()); - } - private final Map nodes; private Map pageRanks; @@ -111,13 +87,13 @@ public class Graph { return Optional.of(bridgeWords); } - public List findShortestPath(final String word1, final String word2) { + 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 path; + return Optional.empty(); } final Node startNode = nodes.get(startWord); @@ -125,7 +101,7 @@ public class Graph { if (startNode == endNode) { path.add(startNode.id); - return path; + return Optional.of(path); } final var distances = new HashMap(); @@ -155,7 +131,7 @@ public class Graph { } if (distances.get(endNode) == Integer.MAX_VALUE) { - return path; + return Optional.of(path); } final var reversePath = new LinkedList(); @@ -165,16 +141,16 @@ public class Graph { current = previousNodes.get(current); } - return reversePath; + return Optional.of(reversePath); } - public double computePageRank(final String nodeId, final double d) { + public double computePageRank(final String nodeId) { final Node node = nodes.get(nodeId); if (node == null) { - return 0.0; + return -1.0; } - return pageRanks.getOrDefault(node, 0.0); + return pageRanks.getOrDefault(node, -1.0); } public List randomWalk() { @@ -248,7 +224,7 @@ public class Graph { sb.append("digraph G {\n"); for (final Node node : nodes.values()) { for (final String edge : node.getDOTEdges()) { - sb.append(" ").append(edge).append("\n"); + sb.append(" ").append(edge); } } sb.append("}"); diff --git a/app/src/main/java/fun/youthlic/GraphCLI.java b/app/src/main/java/fun/youthlic/GraphCLI.java index cc5b3c8..1c6c245 100644 --- a/app/src/main/java/fun/youthlic/GraphCLI.java +++ b/app/src/main/java/fun/youthlic/GraphCLI.java @@ -1,6 +1,20 @@ 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; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +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; @@ -9,20 +23,316 @@ import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; 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; + } public static void main(final String[] args) { + GraphCLIHelper helper = null; + Terminal terminal = null; try { - final Terminal terminal = TerminalBuilder.builder().system(true).build(); - final LineReader reader = LineReaderBuilder.builder().terminal(terminal).parser(new DefaultParser()) - .build(); - while (true) { - final String line = reader.readLine("graph> "); - if (line.trim().equalsIgnoreCase("exit")) - break; - System.out.println(line); - } + terminal = TerminalBuilder.builder().system(true).build(); } catch (final IOException e) { - } finally { + 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; + } } } } + +class GraphCLIHelper { + Graph graph; + + GraphCLIHelper(final String text) { + graph = new Graph(text); + } + + 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(); + } + } + + 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; + 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; + } + } + + 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; + } +}