ソースを参照

Completed development. Now implementing tests to get the desired output.

Dawson 1 ヶ月 前
コミット
07e0573755

+ 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) {
+
+}

+ 20 - 2
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 {
 
@@ -21,7 +23,7 @@ public abstract class Query {
         return new IntermediateQuery(paths);
     }
 
-    static class IntermediateQuery {
+    public static class IntermediateQuery {
         private final Set<Path> paths;
 
         protected IntermediateQuery(Set<Path> paths) {
@@ -31,5 +33,21 @@ public abstract class Query {
         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);
+    }
+}

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

@@ -0,0 +1,93 @@
+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(() -> {
+            me.dawson.teleport.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);
+    }
+}