3 次代碼提交 19201c55e8 ... ccc4c74f06

作者 SHA1 備註 提交日期
  Dawson ccc4c74f06 Completed development. Now implementing tests to get the desired output. 1 月之前
  Dawson 07e0573755 Completed development. Now implementing tests to get the desired output. 1 月之前
  Dawson 00848d73ee Updating gitignore and finishing Query class method instance 1 月之前

+ 2 - 1
.gitignore

@@ -13,6 +13,7 @@ build/
 *.iml
 *.ipr
 out/
+/.idea/
 !**/src/main/**/out/
 !**/src/test/**/out/
 
@@ -39,4 +40,4 @@ bin/
 .vscode/
 
 ### Mac OS ###
-.DS_Store
+.DS_Store

+ 0 - 8
.idea/.gitignore

@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml

+ 0 - 17
.idea/gradle.xml

@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="GradleSettings">
-    <option name="linkedExternalProjectsSettings">
-      <GradleProjectSettings>
-        <option name="externalProjectPath" value="$PROJECT_DIR$" />
-        <option name="gradleHome" value="" />
-        <option name="gradleJvm" value="jbrsdk_jcef-17" />
-        <option name="modules">
-          <set>
-            <option value="$PROJECT_DIR$" />
-          </set>
-        </option>
-      </GradleProjectSettings>
-    </option>
-  </component>
-</project>

+ 0 - 7
.idea/misc.xml

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
-    <output url="file://$PROJECT_DIR$/out" />
-  </component>
-</project>

+ 0 - 6
.idea/vcs.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
-  </component>
-</project>

+ 92 - 3
src/main/java/me/dawson/teleport/Main.java

@@ -1,9 +1,98 @@
 package me.dawson.teleport;
 
-import java.util.TreeMap;
+import me.dawson.teleport.obj.Path;
+import me.dawson.teleport.obj.Response;
+import me.dawson.teleport.obj.Tuple;
+import me.dawson.teleport.query.Query;
+import me.dawson.teleport.query.QueryMatcher;
+
+import java.util.*;
 
 public class Main {
-    void main(String[] args) {
-        TreeMap
+    public static void main(String[] args) {
+
+        if(args.length == 0) {
+            System.out.println("No arguments provided! Shutting down...");
+            return;
+        }
+
+        Set<Path> paths = new HashSet<>();
+        List<Tuple<String, Query>> queriesToRun = new ArrayList<>();
+
+        boolean completedPathAddition = false;
+
+        for(String arg : args) {
+
+            QueryMatcher matcher = QueryMatcher.getQueryFromString(arg);
+
+            switch (matcher) {
+                case PATH_QUERY -> {
+                    if (completedPathAddition) {
+                        System.out.println("Ignored path: " + arg);
+                        continue;
+                    }
+                    var arguments = matcher.getArgumentsFromQuery(arg);
+                    if (arguments == null) {
+                        System.out.println("Invalid query: " + arg);
+                        continue;
+                    }
+                    String from = (String) arguments[0];
+                    String to = (String) arguments[1];
+
+                    Path path = new Path(from, to);
+                    paths.add(path);
+                    paths.add(path.inverse());
+                }
+                case CITY_LOOP -> {
+                    var arguments = matcher.getArgumentsFromQuery(arg);
+                    if (arguments == null) {
+                        System.out.println("Invalid query: " + arg);
+                        continue;
+                    }
+                    completedPathAddition = true;
+                    String city = (String) arguments[0];
+
+                    queriesToRun.add(new Tuple<>(arg, Query.of(paths).loopPossible(city)));
+                }
+                case CITY_TO_CITY -> {
+                    var arguments = matcher.getArgumentsFromQuery(arg);
+                    if (arguments == null) {
+                        System.out.println("Invalid query: " + arg);
+                        continue;
+                    }
+                    completedPathAddition = true;
+                    String from = (String) arguments[0];
+                    String to = (String) arguments[1];
+
+                    queriesToRun.add(new Tuple<>(arg, Query.of(paths).cityToCity(from, to)));
+                }
+                case JUMPS_FROM_CITY -> {
+                    var arguments = matcher.getArgumentsFromQuery(arg);
+                    if (arguments == null) {
+                        System.out.println("Invalid query: " + arg);
+                        continue;
+                    }
+                    completedPathAddition = true;
+                    String city = (String) arguments[0];
+                    int jumps = Integer.parseInt((String) arguments[1]);
+
+                    queriesToRun.add(new Tuple<>(arg, Query.of(paths).jumpCount(city, jumps)));
+                }
+                case UNKNOWN -> System.out.println("Unknown query: " + arg);
+            }
+        }
+        for (Tuple<String, Query> query : queriesToRun) {
+            String argument = query.key();
+            Response response = query.value().run();
+
+            if (response.isOkay()) {
+                System.out.println(argument + ": " + response.getResponse());
+            } else {
+                response.getException().ifPresentOrElse(
+                        exception -> System.out.println("Error running query: " + exception.getMessage()),
+                        () -> System.out.println("Error running query: Unknown error")
+                );
+            }
+        }
     }
 }

+ 3 - 0
src/main/java/me/dawson/teleport/obj/Path.java

@@ -2,4 +2,7 @@ package me.dawson.teleport.obj;
 
 public record Path(String from, String to) {
 
+    public Path inverse() {
+        return new Path(to, from);
+    }
 }

+ 5 - 0
src/main/java/me/dawson/teleport/obj/Tuple.java

@@ -0,0 +1,5 @@
+package me.dawson.teleport.obj;
+
+public record Tuple<K,V>(K key, V value) {
+
+}

+ 27 - 3
src/main/java/me/dawson/teleport/query/Query.java

@@ -2,10 +2,12 @@ package me.dawson.teleport.query;
 
 import me.dawson.teleport.obj.Path;
 import me.dawson.teleport.obj.Response;
+import me.dawson.teleport.query.impl.CityToCity;
+import me.dawson.teleport.query.impl.LoopPossible;
 import me.dawson.teleport.query.impl.PossibleInCount;
 
-import java.nio.file.Paths;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public abstract class Query {
 
@@ -18,12 +20,34 @@ public abstract class Query {
     public abstract Response run();
 
     public static IntermediateQuery of(Set<Path> paths) {
-        return this.IntermediateQuery;
+        return new IntermediateQuery(paths);
     }
 
-    class IntermediateQuery {
+    public static class IntermediateQuery {
+        private final Set<Path> paths;
+
+        protected IntermediateQuery(Set<Path> paths) {
+            this.paths = paths;
+        }
+
         public PossibleInCount jumpCount(String city, int jumps) {
             return new PossibleInCount(paths, city, jumps);
         }
+
+        public CityToCity cityToCity(String from, String to) {
+            return new CityToCity(paths, from, to);
+        }
+
+        public LoopPossible loopPossible(String city) {
+            return new LoopPossible(paths, city);
+        }
+    }
+
+    protected Set<String> getPossibleCities(String city) {
+        return paths.stream().filter(path -> path.from().equals(city)).map(Path::to).collect(Collectors.toSet());
+    }
+
+    protected Set<Path> getPossibleRoutesFromCity(String city) {
+        return paths.stream().filter(path -> path.from().equals(city)).collect(Collectors.toSet());
     }
 }

+ 52 - 0
src/main/java/me/dawson/teleport/query/QueryMatcher.java

@@ -0,0 +1,52 @@
+package me.dawson.teleport.query;
+
+import java.util.regex.Pattern;
+
+public enum QueryMatcher {
+
+    JUMPS_FROM_CITY(Pattern.compile("cities from (.+) in (\\d+) jumps")),
+    CITY_TO_CITY(Pattern.compile("can I teleport from (.+) to (.+)")),
+    PATH_QUERY(Pattern.compile("(.+) - (.+)")),
+    CITY_LOOP(Pattern.compile("loop possible from (.+)")),
+    UNKNOWN(null);
+
+    private final Pattern pattern;
+    private Object[] arguments;
+
+    QueryMatcher(Pattern pattern) {
+        this.pattern = pattern;
+    }
+
+    public boolean matches(String query) {
+        if(pattern == null) return false;
+        var matcher = pattern.matcher(query);
+        if (matcher.matches()) {
+            arguments = new Object[matcher.groupCount()];
+            for (int i = 0; i < matcher.groupCount(); i++) {
+                arguments[i] = matcher.group(i + 1);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public static QueryMatcher getQueryFromString(String query) {
+        for (var matcher : QueryMatcher.values()) {
+            if (matcher != null && matcher.matches(query)) {
+                return matcher;
+            }
+        }
+        return UNKNOWN;
+    }
+
+    public Object[] getArgumentsFromQuery(String query) {
+        if(this == UNKNOWN) {
+            return new Object[0];
+        }
+
+        if (matches(query)) {
+            return arguments;
+        }
+        return null;
+    }
+}

+ 56 - 0
src/main/java/me/dawson/teleport/query/impl/CityToCity.java

@@ -0,0 +1,56 @@
+package me.dawson.teleport.query.impl;
+
+import me.dawson.teleport.obj.Path;
+import me.dawson.teleport.obj.Response;
+import me.dawson.teleport.query.Query;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CityToCity extends Query {
+    private final String from, to;
+
+    public CityToCity(Set<Path> paths, String from, String to) {
+        super(paths);
+
+        this.from = from;
+        this.to = to;
+    }
+
+    @Override
+    public Response run() {
+
+        Set<String> toCheck = new HashSet<>();
+        toCheck.add(from);
+
+        int jumps = paths.size() + 1;
+
+        Set<String> reachableCities = new HashSet<>();
+
+        boolean canReach = false;
+
+        while(--jumps > 0) {
+            Set<String> nextJump = new HashSet<>();
+            for (String city : toCheck) {
+                var possibleCities = getPossibleCities(city);
+                nextJump.addAll(possibleCities);
+                reachableCities.addAll(possibleCities);
+
+                if(reachableCities.contains(to)) {
+                    canReach = true;
+                    break;
+                }
+            }
+            toCheck = nextJump;
+        }
+
+        reachableCities.clear();
+        toCheck.clear();
+
+        if(canReach) {
+            return new Response("yes");
+        } else {
+            return new Response("no");
+        }
+    }
+}

+ 47 - 0
src/main/java/me/dawson/teleport/query/impl/LoopPossible.java

@@ -0,0 +1,47 @@
+package me.dawson.teleport.query.impl;
+
+import me.dawson.teleport.obj.Path;
+import me.dawson.teleport.obj.Response;
+import me.dawson.teleport.query.Query;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class LoopPossible extends Query {
+
+    private final String cityToCheck;
+    public LoopPossible(Set<Path> paths, String city) {
+        super(paths);
+
+        this.cityToCheck = city;
+    }
+
+    @Override
+    public Response run() {
+        if(checkLoopInTree(cityToCheck, new HashSet<>(), "")) {
+            return new Response("yes");
+        } else {
+            return new Response("no");
+        }
+    }
+
+    private boolean checkLoopInTree(String check, Set<Path> usedRoutes, String previousCheck) {
+        Set<Path> possibleRoutes = getPossibleRoutesFromCity(check);
+        for (Path route : possibleRoutes) {
+            if (usedRoutes.contains(route) || usedRoutes.contains(route.inverse())) {
+                continue;
+            }
+
+            if(route.to().equals(cityToCheck) && !route.from().equals(previousCheck)) {
+                return true;
+            }
+
+            usedRoutes.add(route);
+            if (checkLoopInTree(route.to(), usedRoutes, check)) {
+                return true;
+            }
+            usedRoutes.remove(route);
+        }
+        return false;
+    }
+}

+ 4 - 8
src/main/java/me/dawson/teleport/query/impl/PossibleInCount.java

@@ -5,14 +5,12 @@ import me.dawson.teleport.obj.Response;
 import me.dawson.teleport.query.Query;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 public class PossibleInCount extends Query {
 
     private final String start;
-    private int jumps;
+    private final int jumps;
 
     public PossibleInCount(Set<Path> paths, String start, int jumps) {
         super(paths);
@@ -25,7 +23,7 @@ public class PossibleInCount extends Query {
         Set<String> toCheck = new HashSet<>();
         toCheck.add(start);
 
-        int jumps = this.jumps;
+        int jumps = this.jumps + 1;
 
         Set<String> reachableCities = new HashSet<>();
 
@@ -39,10 +37,8 @@ public class PossibleInCount extends Query {
             toCheck = nextJump;
         }
 
-        return new Response(String.format("cities from %s in %d jumps: %s", start, this.jumps, String.join(", ", reachableCities)));
-    }
+        reachableCities.remove(start);
 
-    private Set<String> getPossibleCities(String city) {
-        return paths.stream().filter(path -> path.from().equals(city)).map(Path::to).collect(Collectors.toSet());
+        return new Response(String.join(", ", reachableCities));
     }
 }

+ 27 - 0
src/test/java/me/dawson/tests/Tester.java

@@ -0,0 +1,27 @@
+package me.dawson.tests;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+public class Tester {
+
+    public String getCapturedOutput(Runnable run) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintStream originalOut = System.out;
+        System.setOut(new PrintStream(outputStream));
+
+        try {
+            run.run();
+
+            return outputStream.toString();
+        } finally {
+            System.setOut(originalOut);
+        }
+    }
+
+    public void assertEquals(Object expected, Object actual) {
+        Assertions.assertEquals(expected, actual);
+    }
+}

+ 38 - 0
src/test/java/me/dawson/tests/impl/RequirementsTest.java

@@ -0,0 +1,38 @@
+package me.dawson.tests.impl;
+
+import me.dawson.teleport.Main;
+import me.dawson.tests.Tester;
+import org.junit.jupiter.api.Test;
+
+public class RequirementsTest extends Tester {
+
+    @Test
+    void checkResults() {
+        String output = getCapturedOutput(() -> Main.main(new String[]{"Washington - Baltimore",
+                "Washington - Atlanta",
+                "Baltimore - Philadelphia",
+                "Philadelphia - New York",
+                "Los Angeles - San Fransisco",
+                "San Fransisco - Oakland",
+                "Los Angeles - Oakland",
+                "Seattle - New York",
+                "Seattle - Baltimore",
+                "cities from Seattle in 1 jumps",
+                "cities from Seattle in 2 jumps",
+                "can I teleport from New York to Atlanta",
+                "can I teleport from Oakland to Atlanta",
+                "loop possible from Oakland",
+                "loop possible from Washington"}));
+
+        final String expectedOutput = """
+            cities from Seattle in 1 jumps: New York, Baltimore
+            cities from Seattle in 2 jumps: New York, Baltimore, Philadelphia, Washington
+            can I teleport from New York to Atlanta: yes
+            can I teleport from Oakland to Atlanta: no
+            loop possible from Oakland: yes
+            loop possible from Washington: no
+            """;
+
+        assertEquals(expectedOutput, output);
+    }
+}

+ 91 - 0
src/test/java/me/dawson/tests/impl/UnexpectedBehaviorTest.java

@@ -0,0 +1,91 @@
+package me.dawson.tests.impl;
+
+import me.dawson.teleport.Main;
+import me.dawson.tests.Tester;
+import org.junit.jupiter.api.Test;
+
+public class UnexpectedBehaviorTest extends Tester {
+
+    @Test
+    void noArguments() {
+        String output = getCapturedOutput(() -> Main.main(new String[]{}));
+
+        assertEquals("No arguments provided! Shutting down...\n", output);
+    }
+
+    @Test
+    void invalidArguments() {
+        String output = getCapturedOutput(() -> {
+            String[] args = new String[]{"Washington - Baltimore",
+                    "Washington - Atlanta",
+                    "Baltimore - Philadelphia",
+                    "Philadelphia - New York",
+                    "Los Angeles - San Fransisco",
+                    "not a real argument",
+                    "San Fransisco - Oakland",
+                    "Los Angeles - Oakland",
+                    "Seattle - New York",
+                    "Seattle - Baltimore",
+                    "cities from Seattle in 1 jumps",
+                    "cities from Seattle in 2 jumps",
+                    "I did silly",
+                    "can I teleport from New York to Atlanta",
+                    "can I teleport from Oakland to Atlanta",
+                    "loop possible from Oakland",
+                    "loop possible from Washington"};
+
+            Main.main(args);
+        });
+
+        final String expectedOutput = """
+            WARNING: Unknown query: "not a real argument"
+            WARNING: Unknown query "I did silly"
+            cities from Seattle in 1 jumps: New York, Baltimore
+            cities from Seattle in 2 jumps: New York, Baltimore, Philadelphia, Washington
+            can I teleport from New York to Atlanta: yes
+            can I teleport from Oakland to Atlanta: no
+            loop possible from Oakland: yes
+            loop possible from Washington: no
+            """;
+
+        assertEquals(expectedOutput, output);
+    }
+
+    @Test
+    void invalidPath() {
+        String output = getCapturedOutput(() -> {
+            String[] args = new String[]{"Washington - Baltimore",
+                    "Washington - Atlanta",
+                    "Baltimore - Philadelphia",
+                    "Philadelphia - New York",
+                    "Los Angeles - San Fransisco",
+                    "San Fransisco - Oakland",
+                    "Los Angeles - Oakland",
+                    "Seattle - New York",
+                    "Seattle - Baltimore",
+                    "cities from Seattle in 1 jumps",
+                    "cities from Seattle in 2 jumps",
+                    "can I teleport from New York to Atlanta",
+                    "can I teleport from Oakland to Atlanta",
+                    "loop possible from Oakland",
+                    "loop possible from Washington",
+                    "Washington - Baltimore",
+                    "Chicago - Baltimore"};
+
+            Main.main(args);
+        });
+
+        final String expectedOutput = """
+            WARNING: Ignored path "Washington - Baltimore"
+            WARNING: Ignored path "Chicago - Baltimore"
+            cities from Seattle in 1 jumps: New York, Baltimore
+            cities from Seattle in 2 jumps: New York, Baltimore, Philadelphia, Washington
+            can I teleport from New York to Atlanta: yes
+            can I teleport from Oakland to Atlanta: no
+            loop possible from Oakland: yes
+            loop possible from Washington: no
+            """;
+
+        assertEquals(expectedOutput, output);
+    }
+}