diff --git a/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Edge.java b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Edge.java new file mode 100644 index 00000000..23181ef9 --- /dev/null +++ b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Edge.java @@ -0,0 +1,20 @@ +package hoa.can.code.classic.graph; + +public class Edge { + public final int f; + public final int t; + + public Edge(int f, int t) { + this.f = f; + this.t = t; + } + + public Edge rev(){ + return new Edge(t,f); + } + + @Override + public String toString() { + return String.format("[%d --> %d]", f, t); + } +} diff --git a/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Graph.java b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Graph.java new file mode 100644 index 00000000..a1ebb8c9 --- /dev/null +++ b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Graph.java @@ -0,0 +1,54 @@ +package hoa.can.code.classic.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class Graph { + private List vertices = new ArrayList<>(); + protected List> edges = new ArrayList<>(); + + public Graph(){} + + public Graph(List vertices){ + this.vertices.addAll(vertices); + vertices.forEach(whatever -> edges.add(new ArrayList<>())); + } + + public int edgeCount(){ + return edges.stream().mapToInt(List::size).sum(); + } + + public int add(V vertex){ + vertices.add(vertex); + edges.add(new ArrayList<>()); + return vertices.size()-1; + } + + public List neighbor(int idx){ + return edges.get(idx).stream() + .map(e -> vertices.get(e.t)) + .collect(Collectors.toList()); + } + + public List neighbor(V v){ + return neighbor(vertices.indexOf(v)); + } + + public int idx(V v){ + return vertices.indexOf(v); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for(int i=0; i"); + sb.append(Arrays.toString(neighbor(i).toArray())); + sb.append(System.lineSeparator()); + } + return sb.toString(); + } +} diff --git a/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Search.java b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Search.java new file mode 100644 index 00000000..e4160a68 --- /dev/null +++ b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/Search.java @@ -0,0 +1,173 @@ +package hoa.can.code.classic.graph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.Stack; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; + +public class Search { + public static > boolean linearContains(List list, T key) { + for (T item : list) { + if (item.compareTo(key) == 0) { + return true; + } + } + return false; + } + + // assumes *list* is already sorted + public static > boolean binaryContains(List list, T key) { + int low = 0; + int high = list.size() - 1; + while (low <= high) { + int middle = (low + high) / 2; + int comparison = list.get(middle).compareTo(key); + if (comparison < 0) { // middle codon is less than key + low = middle + 1; + } else if (comparison > 0) { // middle codon is greater than key + high = middle - 1; + } else { // middle codon is equal to key + return true; + } + } + return false; + } + + public static class Node implements Comparable> { + final T state; + Node parent; + double cost; + double heuristic; + + // for dfs and bfs we won't use cost and heuristic + Node(T state, Node parent) { + this.state = state; + this.parent = parent; + } + + // for astar we will use cost and heuristic + Node(T state, Node parent, double cost, double heuristic) { + this.state = state; + this.parent = parent; + this.cost = cost; + this.heuristic = heuristic; + } + + @Override + public int compareTo(Node other) { + Double mine = cost + heuristic; + Double theirs = other.cost + other.heuristic; + return mine.compareTo(theirs); + } + } + + public static Node dfs( + T initial, + Predicate goalTest, + Function> successors) { + // frontier is where we've yet to go + Stack> frontier = new Stack<>(); + frontier.push(new Node<>(initial, null)); + // explored is where we've been + Set explored = new HashSet<>(); + explored.add(initial); + + // keep going while there is more to explore + while (!frontier.isEmpty()) { + Node currentNode = frontier.pop(); + T currentState = currentNode.state; + if (goalTest.test(currentState)) { + return currentNode; + } + // check where we can go next and haven't explored + for (T child : successors.apply(currentState)) { + if (explored.contains(child)) { + continue; // skip children we already explored + } + explored.add(child); + frontier.push(new Node<>(child, currentNode)); + } + } + return null; // went through everything and never found goal + } + + public static List nodeToPath(Node node) { + List path = new ArrayList<>(); + path.add(node.state); + // work backwards from end to front + while (node.parent != null) { + node = node.parent; + path.add(0, node.state); // add to front + } + return path; + } + + public static Node bfs( + T initial, + Predicate goalTest, + Function> successors) { + // frontier is where we've yet to go + Queue> frontier = new LinkedList<>(); + frontier.offer(new Node<>(initial, null)); + // explored is where we've been + Set explored = new HashSet<>(); + explored.add(initial); + + // keep going while there is more to explore + while (!frontier.isEmpty()) { + Node currentNode = frontier.poll(); + T currentState = currentNode.state; + if (goalTest.test(currentState)) { + return currentNode; + } + // check where we can go next and haven't explored + for (T child : successors.apply(currentState)) { + if (explored.contains(child)) { + continue; // skip children we already explored + } + explored.add(child); + frontier.offer(new Node<>(child, currentNode)); + } + } + return null; // went through everything and never found goal + } + + public static Node astar( + T initial, + Predicate goalTest, + Function> successors, + ToDoubleFunction heuristic) { + // frontier is where we've yet to go + PriorityQueue> frontier = new PriorityQueue<>(); + frontier.offer(new Node<>(initial, null, 0.0, heuristic.applyAsDouble(initial))); + // explored is where we've been + Map explored = new HashMap<>(); + explored.put(initial, 0.0); + while (!frontier.isEmpty()) { + Node currentNode = frontier.poll(); + T currentState = currentNode.state; + if (goalTest.test(currentState)) { + return currentNode; + } + // check where we can go next and haven't explored + for (T child : successors.apply(currentState)) { + // 1 here assumes a grid, need a cost function for more sophisticated apps + double newCost = currentNode.cost + 1; + if (!explored.containsKey(child) || explored.get(child) > newCost) { + explored.put(child, newCost); + frontier.offer(new Node<>(child, currentNode, newCost, heuristic.applyAsDouble(child))); + } + } + } + return null; + } +} diff --git a/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/UndirectedGraph.java b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/UndirectedGraph.java new file mode 100644 index 00000000..56f519db --- /dev/null +++ b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/classic/graph/UndirectedGraph.java @@ -0,0 +1,25 @@ +package hoa.can.code.classic.graph; + +import java.util.List; + +public class UndirectedGraph extends Graph { + public UndirectedGraph(List vertices) { + super(vertices); + } + + public void add(Edge edge) { + Edge revEdge = edge.rev(); + edges.get(edge.f).add(edge); + edges.get(edge.t).add(revEdge); + } + + public void add(int f, int t) { + add(new Edge(f, t)); + } + + public void add(V f, V t) { + add( + new Edge(idx(f), idx(t)) + ); + } +} diff --git a/interview_prep/algorithm/java17/src/main/java/hoa/can/code/ez/DivisibleSumPair.java b/interview_prep/algorithm/java17/src/main/java/hoa/can/code/ez/DivisibleSumPair.java deleted file mode 100644 index e69de29b..00000000 diff --git a/interview_prep/algorithm/java17/src/test/java/hoa/can/code/UndirectedGraphTest.java b/interview_prep/algorithm/java17/src/test/java/hoa/can/code/UndirectedGraphTest.java new file mode 100644 index 00000000..ce4403cb --- /dev/null +++ b/interview_prep/algorithm/java17/src/test/java/hoa/can/code/UndirectedGraphTest.java @@ -0,0 +1,86 @@ +package hoa.can.code; + +import hoa.can.code.classic.graph.Search; +import hoa.can.code.classic.graph.UndirectedGraph; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UndirectedGraphTest { + @Test + @DisplayName("UndirectedGraphTest") + public void test1() { + UndirectedGraph g = cities(); + Search.Node bfsResult = Search.bfs( + "Seattle", + v -> v.equals("Chicago"), + g::neighbor); + List path = Search.nodeToPath(bfsResult); + assertEquals(List.of("Seattle","Chicago"), path); + + } + + @Test + @DisplayName("UndirectedGraphTest2") + public void test2() { + UndirectedGraph g = cities(); + Search.Node bfsResult = Search.bfs( + "Seattle", + v -> v.equals("Riverside"), + g::neighbor); + List path = Search.nodeToPath(bfsResult); + assertEquals("Seattle", path.get(0)); + assertEquals("Riverside", path.get(path.size()-1)); + + } + + @Test + @DisplayName("UndirectedGraphTest3") + public void test3() { + UndirectedGraph g = cities(); + Search.Node bfsResult = Search.bfs( + "Chicago", + v -> v.equals("Seattle"), + g::neighbor); + List path = Search.nodeToPath(bfsResult); + assertEquals(List.of("Chicago","Seattle"), path); + + } + + private UndirectedGraph cities(){ + UndirectedGraph cityGraph = new UndirectedGraph<>( + List.of("Seattle", "San Francisco", "Los Angeles", "Riverside", "Phoenix", "Chicago", "Boston", + "New York", "Atlanta", "Miami", "Dallas", "Houston", "Detroit", "Philadelphia", "Washington")); + + cityGraph.add("Seattle", "Chicago"); + cityGraph.add("Seattle", "San Francisco"); + cityGraph.add("San Francisco", "Riverside"); + cityGraph.add("San Francisco", "Los Angeles"); + cityGraph.add("Los Angeles", "Riverside"); + cityGraph.add("Los Angeles", "Phoenix"); + cityGraph.add("Riverside", "Phoenix"); + cityGraph.add("Riverside", "Chicago"); + cityGraph.add("Phoenix", "Dallas"); + cityGraph.add("Phoenix", "Houston"); + cityGraph.add("Dallas", "Chicago"); + cityGraph.add("Dallas", "Atlanta"); + cityGraph.add("Dallas", "Houston"); + cityGraph.add("Houston", "Atlanta"); + cityGraph.add("Houston", "Miami"); + cityGraph.add("Atlanta", "Chicago"); + cityGraph.add("Atlanta", "Washington"); + cityGraph.add("Atlanta", "Miami"); + cityGraph.add("Miami", "Washington"); + cityGraph.add("Chicago", "Detroit"); + cityGraph.add("Detroit", "Boston"); + cityGraph.add("Detroit", "Washington"); + cityGraph.add("Detroit", "New York"); + cityGraph.add("Boston", "New York"); + cityGraph.add("New York", "Philadelphia"); + cityGraph.add("Philadelphia", "Washington"); + return cityGraph; + } +}