From 0fd99b0ac2fa818a79e73df0e051d7eab4caa9b1 Mon Sep 17 00:00:00 2001 From: iMoHax Date: Sat, 16 May 2015 17:25:40 +0300 Subject: [PATCH] change graph implementation --- .../ru/trader/analysis/AnalysisCallBack.java | 23 ++ .../analysis/graph/ConnectibleGraph.java | 111 +++++++ .../ru/trader/analysis/graph/Crawler.java | 291 ++++++++++++++++++ .../java/ru/trader/analysis/graph/Edge.java | 60 ++++ .../java/ru/trader/analysis/graph/Graph.java | 144 +++++++++ .../trader/analysis/graph/GraphCallBack.java | 25 ++ .../java/ru/trader/analysis/graph/Vertex.java | 82 +++++ .../ru/trader/analysis/graph/CrawlerTest.java | 281 +++++++++++++++++ .../ru/trader/analysis/graph/GraphTest.java | 202 ++++++++++++ .../java/ru/trader/analysis/graph/PPath.java | 53 ++++ .../ru/trader/{ => analysis}/graph/Point.java | 3 +- core/src/test/resources/log4j.properties | 2 +- 12 files changed, 1275 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/ru/trader/analysis/AnalysisCallBack.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/Crawler.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/Edge.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/Graph.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/GraphCallBack.java create mode 100644 core/src/main/java/ru/trader/analysis/graph/Vertex.java create mode 100644 core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java create mode 100644 core/src/test/java/ru/trader/analysis/graph/GraphTest.java create mode 100644 core/src/test/java/ru/trader/analysis/graph/PPath.java rename core/src/test/java/ru/trader/{ => analysis}/graph/Point.java (94%) diff --git a/core/src/main/java/ru/trader/analysis/AnalysisCallBack.java b/core/src/main/java/ru/trader/analysis/AnalysisCallBack.java new file mode 100644 index 0000000..dc1dba7 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/AnalysisCallBack.java @@ -0,0 +1,23 @@ +package ru.trader.analysis; + +public class AnalysisCallBack { + + private volatile boolean cancel = false; + + public String getMessage(String key){return "";} + + public void startStage(String id){} + public void setMax(long count){} + public void inc(){} + public void print(String message){} + public void endStage(String id){} + + public final boolean isCancel() { + return cancel; + } + + public final void cancel(){ + this.cancel = true; + } + +} diff --git a/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java b/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java new file mode 100644 index 0000000..6af9c3e --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java @@ -0,0 +1,111 @@ +package ru.trader.analysis.graph; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.analysis.AnalysisCallBack; +import ru.trader.core.Profile; +import ru.trader.graph.Connectable; + +import java.util.Collection; +import java.util.function.Predicate; + +public class ConnectibleGraph> extends Graph { + private final static Logger LOG = LoggerFactory.getLogger(ConnectibleGraph.class); + + private final Profile profile; + + public ConnectibleGraph(Profile profile) { + super(); + this.profile = profile; + } + + public ConnectibleGraph(Profile profile, AnalysisCallBack callback) { + super(callback); + this.profile = profile; + } + + @Override + protected GraphBuilder createGraphBuilder(Vertex vertex, Collection set, int deep, double limit) { + return new ConnectibleGraphBuilder(vertex, set, deep, limit); + } + + public void build(T start, Collection set){ + super.build(start, set, profile.getJumps(), profile.getShip().getTank()); + } + + private class DistanceFilter implements Predicate { + private final double limit; + private final T source; + + private DistanceFilter(double limit, T source) { + this.limit = limit; + this.source = source; + } + + @Override + public boolean test(Double distance) { + return distance <= profile.getShip().getJumpRange(limit) || (profile.withRefill() && distance <= profile.getShip().getJumpRange() && source.canRefill()); + } + } + + private class ConnectibleGraphBuilder extends GraphBuilder { + private final DistanceFilter distanceFilter; + protected boolean refill; + + private ConnectibleGraphBuilder(Vertex vertex, Collection set, int deep, double limit) { + super(vertex, set, deep, limit); + distanceFilter = new DistanceFilter(limit, vertex.getEntry()); + } + + @Override + protected double onConnect(T entry) { + double distance = vertex.getEntry().getDistance(entry); + if (!distanceFilter.test(distance)){ + LOG.trace("Vertex {} is far away, {}", entry, distance); + return -1; + } + double costFuel = profile.getShip().getFuelCost(limit, distance); + double nextLimit = profile.withRefill() ? limit - costFuel : profile.getShip().getTank(); + if (nextLimit < 0) { + LOG.trace("Refill"); + refill = true; + nextLimit = profile.getShip().getTank() - profile.getShip().getFuelCost(distance); + } else { + refill = false; + } + return nextLimit; + } + + @Override + protected ConnectibleEdge createEdge(Vertex target) { + return new ConnectibleEdge(vertex, target, refill); + } + } + + protected class ConnectibleEdge extends Edge { + private final boolean refill; + + protected ConnectibleEdge(Vertex source, Vertex target, boolean refill) { + super(source, target); + this.refill = refill; + } + + public boolean isRefill() { + return refill; + } + + @Override + protected double computeWeight() { + T s = source.getEntry(); + T t = target.getEntry(); + return s.getDistance(t); + } + + @Override + public String toString() { + return source.getEntry().toString() + " - "+ weight + + (refill ? "R" : "") + +" -> " + target.getEntry().toString(); + } + } +} diff --git a/core/src/main/java/ru/trader/analysis/graph/Crawler.java b/core/src/main/java/ru/trader/analysis/graph/Crawler.java new file mode 100644 index 0000000..1a963b9 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/Crawler.java @@ -0,0 +1,291 @@ +package ru.trader.analysis.graph; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ForkJoinPool; +import java.util.function.Consumer; + +public class Crawler { + private final static ForkJoinPool POOL = new ForkJoinPool(); + private final static int THRESHOLD = 4; + private final static Logger LOG = LoggerFactory.getLogger(Crawler.class); + + private final Graph graph; + private final Consumer>> onFoundFunc; + + public Crawler(Graph graph, Consumer>> onFoundFunc) { + this.graph = graph; + this.onFoundFunc = onFoundFunc; + } + + private List> getCopyList(List> head, Edge tail){ + List> res = new ArrayList<>(20); + res.addAll(head); + res.add(tail); + return res; + } + + public void findFast(T target){ + findFast(target, 1); + } + + public void findFast(T target, int count){ + Vertex t = graph.getVertex(target); + int found = 0; + if (t != null) { + if (count > 1) { + found = bfs(new ArrayList<>(), graph.root, target, 0, count); + } else { + found = dfs(new ArrayList<>(), graph.root, target, t.getLevel() + 1, count); + } + } + LOG.debug("Found {} paths", found); + } + + public void findMin(T target){ + findMin(target, 1); + } + + public void findMin(T target, int count){ + Vertex t = graph.getVertex(target); + int found = 0; + if (t != null) { + found = ucs(new ArrayList<>(), graph.root, target, 0, count); + } + LOG.debug("Found {} paths", found); + } + + private int dfs(List> head, Vertex source, T target, int deep, int count) { + LOG.trace("DFS from {} to {}, deep {}, count {}, head {}", source, target, deep, count, head); + int found = 0; + if (deep == source.getLevel()){ + Optional> last = source.getEdges().parallelStream() + .filter(next -> next.isConnect(target)) + .findFirst(); + if (last.isPresent()){ + List> res = getCopyList(head, last.get()); + LOG.debug("Last edge find, path {}", res); + onFoundFunc.accept(res); + found++; + } + } + if (found < count){ + if (deep < source.getLevel()) { + LOG.trace("Search around"); + for (Edge edge : source.getEdges()) { + if (edge.getTarget().isSingle()) continue; + found += dfs(getCopyList(head, edge), edge.getTarget(), target, deep, count-found); + if (found >= count) break; + } + } + } + return found; + } + + private int bfs(List> head, Vertex source, T target, int deep, int count) { + LOG.trace("BFS from {} to {}, deep {}, count {}", source, target, deep, count); + int found = 0; + LinkedList queue = new LinkedList<>(); + queue.add(new TraversalEntry(head, source)); + while (!queue.isEmpty() && count > found){ + TraversalEntry entry = queue.poll(); + head = entry.head; + source = entry.vertex; + LOG.trace("Search from {} to {}, head {}", source, target, head); + source.sortEdges(); + for (Edge edge : source.getEdges()) { + if (edge.isConnect(target)){ + List> res = getCopyList(head, edge); + LOG.debug("Last edge find, path {}", res); + onFoundFunc.accept(res); + found++; + } + if (found >= count) break; + if (edge.getTarget().isSingle()) continue; + if (deep < source.getLevel()) { + queue.add(new TraversalEntry(getCopyList(head, edge), edge.getTarget())); + } + } + } + return found; + } + + private int ucs(List> head, Vertex source, T target, int deep, int count) { + LOG.trace("UCS from {} to {}, deep {}, count {}", source, target, deep, count); + int found = 0; + PriorityQueue queue = new PriorityQueue<>(); + queue.add(new CostTraversalEntry(head, source)); + while (!queue.isEmpty() && count > found){ + CostTraversalEntry entry = queue.poll(); + LOG.trace("Check path head {}, edge {}, cost {}", entry.head, entry.edge, entry.cost); + head = entry.head; + Edge edge = entry.edge; + if (edge != null) { + source = edge.getSource(); + if (edge.isConnect(target)) { + List> res = getCopyList(head, edge); + LOG.debug("Path found {}", res); + onFoundFunc.accept(res); + found++; + if (found >= count) break; + } + if (edge.getTarget().isSingle() || deep >= source.getLevel()){ + continue; + } + head = getCopyList(entry.head, edge); + } + Iterator> iterator = entry.iterator; + //put only 2 entry for iterate + while (iterator.hasNext()){ + edge = iterator.next(); + if (deep < source.getLevel() && !edge.getTarget().isSingle() || edge.isConnect(target)) { + LOG.trace("Add edge {} to queue", edge); + queue.add(new CostTraversalEntry(head, edge, entry.cost)); + } + } + } + return found; + } + + //last edge don't compare + private int ucs2(List> head, Vertex source, T target, int deep, int count) { + LOG.trace("UCS2 from {} to {}, deep {}, count {}", source, target, deep, count); + int found = 0; + PriorityQueue queue = new PriorityQueue<>(); + source.sortEdges(); + queue.add(new CostTraversalEntry(head, source)); + while (!queue.isEmpty() && count > found){ + CostTraversalEntry entry = queue.peek(); + head = entry.edge != null ? getCopyList(entry.head, entry.edge) : entry.head; + Iterator> iterator = entry.iterator; + LOG.trace("Check path head {}, cost {}", head, entry.cost); + int i = 0; + //put only 2 entry for iterate + while (iterator.hasNext() && i < 2){ + Edge edge = iterator.next(); + LOG.trace("Check edge {}", edge); + if (edge.isConnect(target)) { + List> res = getCopyList(head, edge); + LOG.debug("Last edge find, path {}", res); + onFoundFunc.accept(res); + found++; + } + if (found >= count) break; + if (edge.getTarget().isSingle()) continue; + if (deep < source.getLevel()) { + edge.getTarget().sortEdges(); + queue.add(new CostTraversalEntry(head, edge, entry.cost)); + i++; + } + } + if (!iterator.hasNext()) queue.poll(); + } + return found; + } + + private class TraversalEntry { + private final List> head; + private final Vertex vertex; + + private TraversalEntry(List> head, Vertex vertex) { + this.head = head; + this.vertex = vertex; + } + } + + private class CostTraversalEntry implements Comparable{ + private final List> head; + private final Edge edge; + private final Iterator> iterator; + private final double cost; + + private CostTraversalEntry(List> head, Vertex vertex) { + this.head = head; + this.iterator = vertex.getEdges().iterator(); + this.edge = null; + this.cost = 0; + } + + private CostTraversalEntry(List> head, Edge edge, double cost) { + this.head = head; + this.edge = edge; + this.iterator = edge.getTarget().getEdges().iterator(); + this.cost = cost + edge.getWeight(); + } + + @Override + public int compareTo(@NotNull CostTraversalEntry other) { + int cmp = Double.compare(cost, other.cost); + if (cmp != 0) return cmp; + return Integer.compare(head.size(), other.head.size()); + } + } +/* + private class PathFinder extends RecursiveAction { + private final TopList> paths; + private final Path head; + private final Vertex target; + + private PathFinder(TopList> paths, Path head, Vertex target) { + this.paths = paths; + this.head = head; + this.target = target; + } + + @Override + protected void compute() { + if (target == null || isCancelled()) return; + Vertex source = head.getTarget(); + LOG.trace("Find path to deep from {} to {}, head {}", source, target, head); + Edge edge = source.getEdge(target); + if (edge != null){ + Path path = head.connectTo(edge.getTarget(), limit < edge.getLength()); + path.finish(); + LOG.trace("Last edge find, add path {}", path); + synchronized (paths){ + if (!paths.add(path)) complete(null); + } + callback.onFound(); + } + if (!source.isSingle()){ + LOG.trace("Search around"); + ArrayList subTasks = new ArrayList<>(source.getEdges().size()); + Iterator> iterator = source.getEdges().iterator(); + while (iterator.hasNext()) { + Edge next = iterator.next(); + if (isDone() || callback.isCancel()) break; + // target already added if source consist edge + if (next.isConnect(target)) continue; + Path path = head.connectTo(next.getTarget(), limit < next.getLength()); + //Recursive search + PathFinder task = new PathFinder(paths, path, target); + task.fork(); + subTasks.add(task); + if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ + for (PathFinder subTask : subTasks) { + if (isDone() || callback.isCancel()) { + subTask.cancel(callback.isCancel()); + } else { + subTask.join(); + } + } + subTasks.clear(); + } + } + if (!subTasks.isEmpty()){ + for (PathFinder subTask : subTasks) { + if (isDone() || callback.isCancel()) { + subTask.cancel(callback.isCancel()); + } else { + subTask.join(); + } + } + subTasks.clear(); + } + } + } + }*/ +} diff --git a/core/src/main/java/ru/trader/analysis/graph/Edge.java b/core/src/main/java/ru/trader/analysis/graph/Edge.java new file mode 100644 index 0000000..ca1030d --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/Edge.java @@ -0,0 +1,60 @@ +package ru.trader.analysis.graph; + +import org.jetbrains.annotations.NotNull; + +public abstract class Edge implements Comparable{ + protected Double weight; + protected final Vertex target; + protected final Vertex source; + + protected Edge(Vertex source, Vertex target) { + this.target = target; + this.source = source; + } + + protected abstract double computeWeight(); + + public Vertex getTarget(){ + return target; + } + + public Vertex getSource() { + return source; + } + + public double getWeight(){ + if (weight == null){ + weight = computeWeight(); + } + return weight; + } + + public boolean isConnect(T other){ + return target.getEntry().equals(other); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Edge edge = (Edge) o; + return source.equals(edge.source) && target.equals(edge.target); + } + + @Override + public int hashCode() { + int result = target.hashCode(); + result = 31 * result + source.hashCode(); + return result; + } + + @Override + public int compareTo(@NotNull Edge other) { + return Double.compare(getWeight(), other.getWeight()); + } + + @Override + public String toString() { + return source.getEntry().toString() + " - "+ weight +" -> " + target.getEntry().toString(); + } +} diff --git a/core/src/main/java/ru/trader/analysis/graph/Graph.java b/core/src/main/java/ru/trader/analysis/graph/Graph.java new file mode 100644 index 0000000..6666bd0 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/Graph.java @@ -0,0 +1,144 @@ +package ru.trader.analysis.graph; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.analysis.AnalysisCallBack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.RecursiveAction; + +public abstract class Graph { + private final static ForkJoinPool POOL = new ForkJoinPool(); + private final static int THRESHOLD = 4; + + private final static Logger LOG = LoggerFactory.getLogger(Graph.class); + + protected Vertex root; + protected final Map> vertexes; + private final GraphCallBack callback; + + protected int minJumps; + + protected Graph() { + this(new AnalysisCallBack()); + } + + protected Graph(AnalysisCallBack callback) { + this.callback = new GraphCallBack(callback); + vertexes = new ConcurrentHashMap<>(50, 0.9f, THRESHOLD); + } + + protected abstract GraphBuilder createGraphBuilder(Vertex vertex, Collection set, int deep, double limit); + + public void build(T start, Collection set, int maxDeep, double limit) { + callback.startBuild(start); + root = getInstance(start, maxDeep); + POOL.invoke(createGraphBuilder(root, set, maxDeep - 1, limit)); + if (set.size() > vertexes.size()){ + minJumps = maxDeep; + } else { + minJumps = 1; + for (Vertex vertex : vertexes.values()) { + int jumps = maxDeep - vertex.getLevel(); + if (jumps > minJumps) minJumps = jumps; + } + } + callback.endBuild(); + } + + private Vertex getInstance(T entry, int deep){ + Vertex vertex = new Vertex<>(entry); + vertex.setLevel(deep); + Vertex old = vertexes.get(entry); + if (old == null || old.getLevel() < deep) { + LOG.trace("Is top vertex"); + vertexes.put(entry, vertex); + } + return vertex; + } + + public boolean isAccessible(T entry){ + return vertexes.containsKey(entry); + } + + public Vertex getVertex(T entry){ + return vertexes.get(entry); + } + + public T getRoot() { + return root.getEntry(); + } + + public int getMinJumps() { + return minJumps; + } + + protected abstract class GraphBuilder extends RecursiveAction { + protected final Vertex vertex; + protected final Collection set; + protected final int deep; + protected final double limit; + + protected GraphBuilder(Vertex vertex, Collection set, int deep, double limit) { + this.vertex = vertex; + this.set = set; + this.deep = deep; + this.limit = limit; + } + + protected abstract double onConnect(T entry); + protected abstract Edge createEdge(Vertex target); + + @Override + protected void compute() { + LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep); + ArrayList subTasks = new ArrayList<>(THRESHOLD); + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + if (callback.isCancel()) break; + T entry = iterator.next(); + if (entry == vertex.getEntry()) continue; + double nextLimit = onConnect(entry); + if (nextLimit >= 0) { + LOG.trace("Connect {} to {}", vertex, entry); + Vertex next = getInstance(entry, vertex.getLevel() - 1); + vertex.connect(createEdge(next)); + if (deep > 0) { + //Recursive build + GraphBuilder task = createGraphBuilder(next, set, deep - 1, nextLimit); + task.fork(); + subTasks.add(task); + } + } else { + LOG.trace("Vertex {} is far away", entry); + } + if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ + for (GraphBuilder subTask : subTasks) { + if (callback.isCancel()){ + subTask.cancel(true); + } else { + subTask.join(); + } + } + subTasks.clear(); + } + } + if (!subTasks.isEmpty()){ + for (GraphBuilder subTask : subTasks) { + if (callback.isCancel()){ + subTask.cancel(true); + } else { + subTask.join(); + } + } + subTasks.clear(); + } + LOG.trace("End build graph from {} on deep {}", vertex, deep); + } + } +} diff --git a/core/src/main/java/ru/trader/analysis/graph/GraphCallBack.java b/core/src/main/java/ru/trader/analysis/graph/GraphCallBack.java new file mode 100644 index 0000000..4e0849b --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/GraphCallBack.java @@ -0,0 +1,25 @@ +package ru.trader.analysis.graph; + +import ru.trader.analysis.AnalysisCallBack; + +public class GraphCallBack { + private final AnalysisCallBack parent; + public final String BUILD_STAGE = "graph.stage.build"; + + public GraphCallBack(AnalysisCallBack parent) { + this.parent = parent; + } + + public void startBuild(Object entry){ + parent.startStage(BUILD_STAGE); + parent.print(String.format(parent.getMessage(BUILD_STAGE), entry.toString())); + } + + public void endBuild(){ + parent.endStage(BUILD_STAGE); + } + + public boolean isCancel(){ + return parent.isCancel(); + } +} diff --git a/core/src/main/java/ru/trader/analysis/graph/Vertex.java b/core/src/main/java/ru/trader/analysis/graph/Vertex.java new file mode 100644 index 0000000..9a566d9 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/graph/Vertex.java @@ -0,0 +1,82 @@ +package ru.trader.analysis.graph; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +public class Vertex { + private final ArrayList> edges = new ArrayList<>(); + private final T entry; + private volatile int level = -1; + + public Vertex(T entry) { + this.entry = entry; + } + + public T getEntry() { + return entry; + } + + public boolean isEntry(T entry){ + return this.entry.equals(entry); + } + + void setLevel(int level) { + this.level = level; + } + + public int getLevel() { + return level; + } + + public void connect(Edge edge){ + assert this == edge.getSource(); + synchronized (edges){ + edges.add(edge); + } + } + + public List> getEdges() { + return edges; + } + + public Optional> getEdge(Vertex target) { + return getEdge(target.entry); + } + + public Optional> getEdge(T target) { + return edges.stream().filter((e) -> e.isConnect(target)).findFirst(); + } + + public void sortEdges(){ + edges.sort(Comparator.>naturalOrder()); + } + + public boolean isConnected(T other){ + return getEdge(other).isPresent(); + } + + public boolean isSingle(){ + return edges.size() == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Vertex vertex = (Vertex) o; + return entry.equals(vertex.entry); + } + + @Override + public int hashCode() { + return entry.hashCode(); + } + + @Override + public String toString() { + return "Vertex{" + entry + ", lvl=" + level + '}'; + } + +} diff --git a/core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java b/core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java new file mode 100644 index 0000000..84bd093 --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java @@ -0,0 +1,281 @@ +package ru.trader.analysis.graph; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.TestUtil; +import ru.trader.core.Profile; +import ru.trader.core.Ship; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class CrawlerTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(CrawlerTest.class); + + private final static ArrayList entrys = new ArrayList<>(); + private final static Point x1 = new Point("x1", -40); + private final static Point x2 = new Point("x2", -20); + private final static Point x3 = new Point("x3", -10, true); + private final static Point x4 = new Point("x4", -5, true); + private final static Point x5 = new Point("x5", 0); + private final static Point x6 = new Point("x6", 5); + private final static Point x7 = new Point("x7", 20); + private final static Point x8 = new Point("x8", 30); + private final static Point x9 = new Point("x9", 40); + private final static Point x10 = new Point("x10", 50); + + @Before + public void setUp() throws Exception { + entrys.add(x1); + entrys.add(x2); + entrys.add(x3); + entrys.add(x4); + entrys.add(x5); + entrys.add(x6); + entrys.add(x7); + entrys.add(x8); + entrys.add(x9); + entrys.add(x10); + } + + private void assertEdges(List> edges, Point ... points){ + for (int i = 1; i < points.length; i++) { + if (i > edges.size()){ + Assert.fail(String.format("Wrong edges count. Expected: %s Actual: %s", points.length-1, edges.size())); + } + Edge edge = edges.get(i-1); + Point expSource = points[i-1]; + Point expTarget = points[i]; + if (!edge.getSource().isEntry(expSource)){ + Assert.fail(String.format("Edge start differed. Expected: %s Actual: %s", expSource, edge.getSource().getEntry())); + } + if (!edge.getTarget().isEntry(expTarget)){ + Assert.fail(String.format("Edge end differed. Expected: %s Actual: %s", expTarget, edge.getTarget().getEntry())); + } + } + } + + private void assertPaths(List>> paths, PPath... points) { + assertPaths(false, paths, points); + } + + private void assertPaths(boolean saveOrder, List>> paths, PPath... points){ + Collection actual = new ArrayList<>(paths.size()); + paths.forEach(p -> actual.add(PPath.of(p))); + if (saveOrder) { + TestUtil.assertCollectionEquals(actual, points); + } else { + TestUtil.assertCollectionContainAll(actual, points); + } + } + + @Test + public void testGetPaths() throws Exception { + LOG.info("Start get paths test"); + //max distance 5.126, 1 jump tank + Ship ship = new Ship(); + ship.setMass(64);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(2); profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4, x5 <-> x6 + + SimpleCollector paths = new SimpleCollector(); + Crawler crawler = new Crawler<>(graph, paths::add); + crawler.findMin(x4, 10); + assertPaths(paths.get(), PPath.of(x5, x4)); + paths.clear(); + + crawler.findMin(x6, 10); + assertPaths(paths.get(), PPath.of(x5, x6)); + paths.clear(); + + crawler.findMin(x7, 10); + assertEquals(paths.get().size(), 0); + paths.clear(); + + } + + @Test + public void testGetPaths2() throws Exception { + LOG.info("Start get paths test2"); + //max distance 15.6, 1 jump tank + Ship ship = new Ship(); + ship.setMass(18);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(3); profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <-> x3 <-> x2, x5 <-> x6 <-> x7 <-> x8 + // x5 <-> x3, x4 <-> x2, x3 <-> x6, x4 <-> x6 + SimpleCollector paths = new SimpleCollector(); + Crawler crawler = new Crawler<>(graph, paths::add); + + crawler.findMin(x8, 10); + assertPaths(paths.get(), PPath.of(x5, x6, x7, x8)); + paths.clear(); + + crawler.findMin(x7, 10); + assertPaths(paths.get(), PPath.of(x5, x6, x7), PPath.of(x5, x4, x6, x7), PPath.of(x5, x3, x6, x7)); + paths.clear(); + + crawler.findMin(x7); + assertEquals(1, paths.get().size()); + assertPaths(paths.get(), PPath.of(x5, x6, x7)); + paths.clear(); + + crawler.findMin(x4, 20); + assertPaths(true, paths.get(), PPath.of(x5, x4), PPath.of(x5, x3, x4), PPath.of(x5, x6, x4), + PPath.of(x5, x6, x5, x4), PPath.of(x5, x4, x3, x4), PPath.of(x5, x4, x5, x4), + PPath.of(x5, x6, x3, x4), PPath.of(x5, x4, x6, x4), + PPath.of(x5, x3, x5, x4), PPath.of(x5, x3, x2, x4), + PPath.of(x5, x4, x2, x4), PPath.of(x5, x3, x6, x4) + ); + paths.clear(); + + crawler.findMin(x5, 20); + assertPaths(paths.get(), PPath.of(x5, x4, x5), PPath.of(x5, x4, x6, x5), PPath.of(x5, x4, x3, x5), + PPath.of(x5, x6, x5), PPath.of(x5, x6, x4, x5), PPath.of(x5, x6, x3, x5), PPath.of(x5, x3, x5), + PPath.of(x5, x3, x4, x5), PPath.of(x5, x3, x6, x5)); + paths.clear(); + + crawler.findFast(x8); + assertEdges(paths.get(0), x5, x6, x7, x8); + paths.clear(); + + crawler.findFast(x7, 10); + assertEdges(paths.get(0), x5, x6, x7); + assertPaths(paths.get(), PPath.of(x5, x6, x7), PPath.of(x5, x4, x6, x7), PPath.of(x5, x3, x6, x7)); + paths.clear(); + + crawler.findFast(x4); + assertEdges(paths.get(0), x5, x4); + paths.clear(); + + } + + @Test + public void testGetRefillPaths() throws Exception { + LOG.info("Start get refill paths"); + //max distance 10.1, 1 jump tank + Ship ship = new Ship(); + ship.setMass(30.3);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(3); profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <- refill -> x3 <- refill -> x2, x5 <-> x6 + // x5 <-> x3 <- refill -> x2, x5 <-> x4 <- refill -> x6 + SimpleCollector paths = new SimpleCollector(); + Crawler crawler = new Crawler<>(graph, paths::add); + + crawler.findMin(x1, 10); + assertTrue(paths.get().isEmpty()); + paths.clear(); + + crawler.findMin(x2, 10); + assertPaths(paths.get(), PPath.of(x5, x4, x3, x2), PPath.of(x5, x3, x2)); + paths.clear(); + + crawler.findMin(x6, 10); + assertPaths(true, paths.get(), PPath.of(x5, x6), PPath.of(x5, x4, x6), + PPath.of(x5, x6, x5, x6), PPath.of(x5, x4, x5, x6), + PPath.of(x5, x3, x5, x6), PPath.of(x5, x3, x4, x6), + PPath.of(x5, x6, x4, x6)); + paths.clear(); + + crawler.findFast(x2); + assertPaths(paths.get(), PPath.of(x5, x3, x2)); + paths.clear(); + + } + + @Test + public void testGetRefillPaths2() throws Exception { + LOG.info("Start get refill paths 2 "); + //max distance 15.6, 1 jump tank + Ship ship = new Ship(); + ship.setMass(18);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(4); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <-> x3 - refill -> x2, + // x5 <-> x6 <-> x4 <-refill -> x2 + // x5 <-> x3 <- refill -> x2 + // x5 <-> x4 <- refill -> x6 + SimpleCollector paths = new SimpleCollector(); + Crawler crawler = new Crawler<>(graph, paths::add); + + crawler.findMin(x1, 10); + assertTrue(paths.get().isEmpty()); + paths.clear(); + + crawler.findMin(x2, 20); + assertPaths(paths.get(), PPath.of(x5, x3, x4, x2), PPath.of(x5, x3, x2), + PPath.of(x5, x4, x3, x2), PPath.of(x5, x4, x2), PPath.of(x5, x3, x5, x4, x2), + PPath.of(x5, x6, x4, x2), PPath.of(x5, x6, x4, x3, x2), PPath.of(x5, x4, x3, x4, x2), + PPath.of(x5, x4, x5, x4, x2), PPath.of(x5, x6, x5, x4, x2), PPath.of(x5, x3, x4, x3, x2), + PPath.of(x5, x3, x4, x3, x2), PPath.of(x5, x3, x4, x3, x2), PPath.of(x5, x3, x4, x3, x2)); + paths.clear(); + + crawler.findMin(x6, 30); + assertPaths(paths.get(), PPath.of(x5, x6), PPath.of(x5, x4, x6), + PPath.of(x5, x3, x4, x6), PPath.of(x5, x3, x6), PPath.of(x5, x4, x3, x6), + PPath.of(x5, x3, x4, x3, x6), PPath.of(x5, x3, x4, x5, x6), PPath.of(x5, x3, x5, x6), + PPath.of(x5, x3, x5, x4, x6), PPath.of(x5, x4, x3, x4, x6), PPath.of(x5, x4, x3, x5, x6), + PPath.of(x5, x4, x5, x6), PPath.of(x5, x4, x5, x4, x6), PPath.of(x5, x4, x5, x3, x6), + PPath.of(x5, x6, x5, x6), PPath.of(x5, x6, x4, x6), PPath.of(x5, x4, x6, x5, x6), + PPath.of(x5, x6, x4, x5, x6), PPath.of(x5, x6, x5, x4, x6), PPath.of(x5, x6, x5, x3, x6), + PPath.of(x5, x4, x6, x4, x6), PPath.of(x5, x6, x4, x3, x6) + ); + paths.clear(); + + crawler.findMin(x7, 10); + assertTrue(paths.get().isEmpty()); + paths.clear(); + + crawler.findFast(x2); + assertPaths(paths.get(), PPath.of(x5, x3, x2)); + paths.clear(); + + } + + + @After + public void tearDown() throws Exception { + entrys.clear(); + } + + private class SimpleCollector { + private List>> paths = new ArrayList<>(); + + public void add(List> path){ + paths.add(path); + } + + public List>> get() { + return paths; + } + + public List> get(int indx) { + if (indx >= paths.size()) return Collections.emptyList(); + return paths.get(indx); + } + public void clear(){ + paths.clear(); + } + } + +} \ No newline at end of file diff --git a/core/src/test/java/ru/trader/analysis/graph/GraphTest.java b/core/src/test/java/ru/trader/analysis/graph/GraphTest.java new file mode 100644 index 0000000..74156a2 --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/graph/GraphTest.java @@ -0,0 +1,202 @@ +package ru.trader.analysis.graph; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Profile; +import ru.trader.core.Ship; + +import java.util.ArrayList; + +public class GraphTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(GraphTest.class); + + private final static ArrayList entrys = new ArrayList<>(); + private final static Point x1 = new Point("x1",-40); + private final static Point x2 = new Point("x2",-20); + private final static Point x3 = new Point("x3",-10, true); + private final static Point x4 = new Point("x4",-5, true); + private final static Point x5 = new Point("x5",0); + private final static Point x6 = new Point("x6",5); + private final static Point x7 = new Point("x7",20); + private final static Point x8 = new Point("x8",30); + private final static Point x9 = new Point("x9",40); + private final static Point x10 = new Point("x10",50); + + @Before + public void setUp() throws Exception { + entrys.add(x1); + entrys.add(x2); + entrys.add(x3); + entrys.add(x4); + entrys.add(x5); + entrys.add(x6); + entrys.add(x7); + entrys.add(x8); + entrys.add(x9); + entrys.add(x10); + } + + @Test + public void testBuild0() throws Exception { + LOG.info("Start graph build test0"); + //max distance 4.95, 1 jump tank + Ship ship = new Ship(); + ship.setMass(67);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(10); + profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 + assertFalse(graph.isAccessible(x1)); + assertFalse(graph.isAccessible(x2)); + assertFalse(graph.isAccessible(x3)); + assertFalse(graph.isAccessible(x4)); + assertTrue(graph.isAccessible(x5)); + assertFalse(graph.isAccessible(x6)); + assertFalse(graph.isAccessible(x7)); + assertFalse(graph.isAccessible(x8)); + assertFalse(graph.isAccessible(x9)); + assertFalse(graph.isAccessible(x10)); + } + + + @Test + public void testBuild1() throws Exception { + LOG.info("Start graph build test1"); + //max distance 5.167, 1 jump tank + Ship ship = new Ship(); + ship.setMass(64);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(2); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <-refill-> x3, x5 -> x6 + assertFalse(graph.isAccessible(x1)); + assertFalse(graph.isAccessible(x2)); + assertTrue(graph.isAccessible(x3)); + assertTrue(graph.isAccessible(x4)); + assertTrue(graph.isAccessible(x5)); + assertTrue(graph.isAccessible(x6)); + assertFalse(graph.isAccessible(x7)); + assertFalse(graph.isAccessible(x8)); + assertFalse(graph.isAccessible(x9)); + assertFalse(graph.isAccessible(x10)); + + Vertex x = graph.getVertex(x5); + // x5 -> x4, x5 -> x6 + checkEdges(x, new Point[]{x4, x6}, new Point[]{x1, x2, x3, x7, x8, x9, x10}); + // x4 -> x5 + x = graph.getVertex(x4); + checkEdges(x, new Point[]{x5, x3}, new Point[]{x1, x2, x6, x7, x8, x9, x10}); + // x6 <- x5 + x = graph.getVertex(x6); + checkEdges(x, new Point[]{}, new Point[]{x1, x2, x3, x4, x5, x7, x8, x9, x10}); + + } + + private void checkEdges(Vertex vertex, Point[] trueEdge, Point[] falseEdge){ + for (Point point : trueEdge) { + assertTrue(String.format("%s must have edge to %s", vertex, point), vertex.isConnected(point)); + } + for (Point point : falseEdge) { + assertFalse(String.format("%s must not have edge to %s", vertex, point), vertex.isConnected(point)); + } + } + + @Test + public void testBuild2() throws Exception { + LOG.info("Start graph build test2"); + //max distance 5.167, 1 jump tank + Ship ship = new Ship(); + ship.setMass(64);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(3);profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <-> x3, x5 <-> x6 + assertFalse(graph.isAccessible(x1)); + assertFalse(graph.isAccessible(x2)); + assertTrue(graph.isAccessible(x3)); + assertTrue(graph.isAccessible(x4)); + assertTrue(graph.isAccessible(x5)); + assertTrue(graph.isAccessible(x6)); + assertFalse(graph.isAccessible(x7)); + assertFalse(graph.isAccessible(x8)); + assertFalse(graph.isAccessible(x9)); + assertFalse(graph.isAccessible(x10)); + + Vertex x = graph.getVertex(x5); + // x5 -> x4, x5 -> x6 + checkEdges(x, new Point[]{x4, x6}, new Point[]{x1, x2, x3, x7, x8, x9, x10}); + // x3 -> x4 + x = graph.getVertex(x3); + checkEdges(x, new Point[]{x4}, new Point[]{x1, x2, x5, x6, x7, x8, x9, x10}); + // x4 -> x5, x4 -> x3 + x = graph.getVertex(x4); + checkEdges(x, new Point[]{x3, x5}, new Point[]{x1, x2, x6, x7, x8, x9, x10}); + // x6 -> x5 + x = graph.getVertex(x6); + checkEdges(x, new Point[]{x5}, new Point[]{x1, x2, x3, x4, x7, x8, x9, x10}); + } + + @Test + public void testBuild4() throws Exception { + LOG.info("Start graph build test4"); + //max distance 15.6, 1 jump tank + Ship ship = new Ship(); + ship.setMass(18);ship.setTank(0.6); + Profile profile = new Profile(ship); + profile.setJumps(3); profile.setRefill(false); + LOG.info("Ship = {}, Jumps = {}", profile.getShip(), profile.getJumps()); + ConnectibleGraph graph = new ConnectibleGraph<>(profile); + graph.build(x5, entrys); + // x5 <-> x4 <-> x3 -> x2, x5 <-> x6 <-> x7 -> x8 + // x5 <-> x3, x5 <-> x4 <-> x2, x3 <-> x6, x4 <-> x6 + assertFalse(graph.isAccessible(x1)); + assertTrue(graph.isAccessible(x2)); + assertTrue(graph.isAccessible(x3)); + assertTrue(graph.isAccessible(x4)); + assertTrue(graph.isAccessible(x5)); + assertTrue(graph.isAccessible(x6)); + assertTrue(graph.isAccessible(x7)); + assertTrue(graph.isAccessible(x8)); + assertFalse(graph.isAccessible(x9)); + assertFalse(graph.isAccessible(x10)); + + Vertex x = graph.getVertex(x5); + // x5 -> x4, x5 -> x3, x5 -> x6 + checkEdges(x, new Point[]{x3, x4, x6}, new Point[]{x1, x2, x7, x8, x9, x10}); + // x2 -> x3, x2 -> x4 + x = graph.getVertex(x2); + checkEdges(x, new Point[]{x3, x4}, new Point[]{x1, x5, x6, x7, x8, x9, x10}); + // x3 -> x4, x3 -> x2, x3 -> x5, x3 -> x6 + x = graph.getVertex(x3); + checkEdges(x, new Point[]{x2, x4, x5, x6}, new Point[]{x1, x7, x8, x9, x10}); + // x4 -> x5, x4 -> x3, x4 -> x2, x4 -> x6 + x = graph.getVertex(x4); + checkEdges(x, new Point[]{x2, x3, x5, x6}, new Point[]{x1, x7, x8, x9, x10}); + // x6 -> x5, x6 -> x7, x6 -> x3, x6 -> x4 + x = graph.getVertex(x6); + checkEdges(x, new Point[]{x5, x7, x3, x4}, new Point[]{x1, x2, x8, x9, x10}); + // x7 -> x6, x7 -> x8 + x = graph.getVertex(x7); + checkEdges(x, new Point[]{x6, x8}, new Point[]{x1, x2, x3, x4, x5, x9, x10}); + // x8 <- x7 + x = graph.getVertex(x8); + checkEdges(x, new Point[]{}, new Point[]{x1, x2, x3, x4, x5, x6, x7, x9, x10}); + + } + + @After + public void tearDown() throws Exception { + entrys.clear(); + } +} diff --git a/core/src/test/java/ru/trader/analysis/graph/PPath.java b/core/src/test/java/ru/trader/analysis/graph/PPath.java new file mode 100644 index 0000000..1ea404f --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/graph/PPath.java @@ -0,0 +1,53 @@ +package ru.trader.analysis.graph; + +import java.util.Arrays; +import java.util.List; + +public class PPath { + private final T[] points; + + private PPath(T[] points) { + this.points = points; + } + + private PPath(List> edges) { + //noinspection unchecked + points = (T[]) new Object[edges.size()+1]; + for (int i = 0; i < edges.size(); i++) { + Edge edge = edges.get(i); + if (i > 0 && !points[i].equals(edge.getSource().getEntry())){ + throw new IllegalArgumentException(String.format("Edges by index %d and %d is not linked", i-1, i)); + } else { + points[i] = edge.getSource().getEntry(); + } + points[i+1] = edge.getTarget().getEntry(); + } + } + + @SafeVarargs + public static PPath of(V... entries){ + return new PPath<>(entries); + } + + public static PPath of(List> edges){ + return new PPath<>(edges); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PPath)) return false; + PPath pPath = (PPath) o; + return Arrays.equals(points, pPath.points); + } + + @Override + public int hashCode() { + return Arrays.hashCode(points); + } + + @Override + public String toString() { + return Arrays.toString(points); + } +} diff --git a/core/src/test/java/ru/trader/graph/Point.java b/core/src/test/java/ru/trader/analysis/graph/Point.java similarity index 94% rename from core/src/test/java/ru/trader/graph/Point.java rename to core/src/test/java/ru/trader/analysis/graph/Point.java index 63ac05f..7c279d9 100644 --- a/core/src/test/java/ru/trader/graph/Point.java +++ b/core/src/test/java/ru/trader/analysis/graph/Point.java @@ -1,6 +1,7 @@ -package ru.trader.graph; +package ru.trader.analysis.graph; import org.jetbrains.annotations.NotNull; +import ru.trader.graph.Connectable; import java.util.Objects; diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties index fe6a143..65da310 100644 --- a/core/src/test/resources/log4j.properties +++ b/core/src/test/resources/log4j.properties @@ -4,4 +4,4 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n - +#log4j.logger.ru.trader.analysis.graph.Crawler = TRACE