Archived
0

change graph implementation

This commit is contained in:
iMoHax
2015-05-16 17:25:40 +03:00
parent 42a958ecb2
commit 0fd99b0ac2
12 changed files with 1275 additions and 2 deletions

View File

@@ -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;
}
}

View File

@@ -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<T extends Connectable<T>> extends Graph<T> {
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<T> vertex, Collection<T> set, int deep, double limit) {
return new ConnectibleGraphBuilder(vertex, set, deep, limit);
}
public void build(T start, Collection<T> set){
super.build(start, set, profile.getJumps(), profile.getShip().getTank());
}
private class DistanceFilter implements Predicate<Double> {
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<T> vertex, Collection<T> 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<T> target) {
return new ConnectibleEdge(vertex, target, refill);
}
}
protected class ConnectibleEdge extends Edge<T> {
private final boolean refill;
protected ConnectibleEdge(Vertex<T> source, Vertex<T> 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();
}
}
}

View File

@@ -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<T> {
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<T> graph;
private final Consumer<List<Edge<T>>> onFoundFunc;
public Crawler(Graph<T> graph, Consumer<List<Edge<T>>> onFoundFunc) {
this.graph = graph;
this.onFoundFunc = onFoundFunc;
}
private List<Edge<T>> getCopyList(List<Edge<T>> head, Edge<T> tail){
List<Edge<T>> 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> 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> 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<Edge<T>> head, Vertex<T> 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<Edge<T>> last = source.getEdges().parallelStream()
.filter(next -> next.isConnect(target))
.findFirst();
if (last.isPresent()){
List<Edge<T>> 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<T> 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<Edge<T>> head, Vertex<T> source, T target, int deep, int count) {
LOG.trace("BFS from {} to {}, deep {}, count {}", source, target, deep, count);
int found = 0;
LinkedList<TraversalEntry> 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<T> edge : source.getEdges()) {
if (edge.isConnect(target)){
List<Edge<T>> 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<Edge<T>> head, Vertex<T> source, T target, int deep, int count) {
LOG.trace("UCS from {} to {}, deep {}, count {}", source, target, deep, count);
int found = 0;
PriorityQueue<CostTraversalEntry> 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<T> edge = entry.edge;
if (edge != null) {
source = edge.getSource();
if (edge.isConnect(target)) {
List<Edge<T>> 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<Edge<T>> 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<Edge<T>> head, Vertex<T> source, T target, int deep, int count) {
LOG.trace("UCS2 from {} to {}, deep {}, count {}", source, target, deep, count);
int found = 0;
PriorityQueue<CostTraversalEntry> 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<Edge<T>> 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<T> edge = iterator.next();
LOG.trace("Check edge {}", edge);
if (edge.isConnect(target)) {
List<Edge<T>> 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<Edge<T>> head;
private final Vertex<T> vertex;
private TraversalEntry(List<Edge<T>> head, Vertex<T> vertex) {
this.head = head;
this.vertex = vertex;
}
}
private class CostTraversalEntry implements Comparable<CostTraversalEntry>{
private final List<Edge<T>> head;
private final Edge<T> edge;
private final Iterator<Edge<T>> iterator;
private final double cost;
private CostTraversalEntry(List<Edge<T>> head, Vertex<T> vertex) {
this.head = head;
this.iterator = vertex.getEdges().iterator();
this.edge = null;
this.cost = 0;
}
private CostTraversalEntry(List<Edge<T>> head, Edge<T> 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<Path<T>> paths;
private final Path<T> head;
private final Vertex<T> target;
private PathFinder(TopList<Path<T>> paths, Path<T> head, Vertex<T> target) {
this.paths = paths;
this.head = head;
this.target = target;
}
@Override
protected void compute() {
if (target == null || isCancelled()) return;
Vertex<T> source = head.getTarget();
LOG.trace("Find path to deep from {} to {}, head {}", source, target, head);
Edge<T> edge = source.getEdge(target);
if (edge != null){
Path<T> 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<PathFinder> subTasks = new ArrayList<>(source.getEdges().size());
Iterator<Edge<T>> iterator = source.getEdges().iterator();
while (iterator.hasNext()) {
Edge<T> next = iterator.next();
if (isDone() || callback.isCancel()) break;
// target already added if source consist edge
if (next.isConnect(target)) continue;
Path<T> 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();
}
}
}
}*/
}

View File

@@ -0,0 +1,60 @@
package ru.trader.analysis.graph;
import org.jetbrains.annotations.NotNull;
public abstract class Edge<T> implements Comparable<Edge>{
protected Double weight;
protected final Vertex<T> target;
protected final Vertex<T> source;
protected Edge(Vertex<T> source, Vertex<T> target) {
this.target = target;
this.source = source;
}
protected abstract double computeWeight();
public Vertex<T> getTarget(){
return target;
}
public Vertex<T> 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();
}
}

View File

@@ -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<T> {
private final static ForkJoinPool POOL = new ForkJoinPool();
private final static int THRESHOLD = 4;
private final static Logger LOG = LoggerFactory.getLogger(Graph.class);
protected Vertex<T> root;
protected final Map<T, Vertex<T>> 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<T> vertex, Collection<T> set, int deep, double limit);
public void build(T start, Collection<T> 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<T> vertex : vertexes.values()) {
int jumps = maxDeep - vertex.getLevel();
if (jumps > minJumps) minJumps = jumps;
}
}
callback.endBuild();
}
private Vertex<T> getInstance(T entry, int deep){
Vertex<T> vertex = new Vertex<>(entry);
vertex.setLevel(deep);
Vertex<T> 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<T> 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<T> vertex;
protected final Collection<T> set;
protected final int deep;
protected final double limit;
protected GraphBuilder(Vertex<T> vertex, Collection<T> 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<T> createEdge(Vertex<T> target);
@Override
protected void compute() {
LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep);
ArrayList<GraphBuilder> subTasks = new ArrayList<>(THRESHOLD);
Iterator<T> 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<T> 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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<T> {
private final ArrayList<Edge<T>> 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<T> edge){
assert this == edge.getSource();
synchronized (edges){
edges.add(edge);
}
}
public List<Edge<T>> getEdges() {
return edges;
}
public Optional<Edge<T>> getEdge(Vertex<T> target) {
return getEdge(target.entry);
}
public Optional<Edge<T>> getEdge(T target) {
return edges.stream().filter((e) -> e.isConnect(target)).findFirst();
}
public void sortEdges(){
edges.sort(Comparator.<Edge<T>>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 + '}';
}
}

View File

@@ -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<Point> 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<Edge<Point>> 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<Point> 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<List<Edge<Point>>> paths, PPath... points) {
assertPaths(false, paths, points);
}
private void assertPaths(boolean saveOrder, List<List<Edge<Point>>> paths, PPath... points){
Collection<PPath> 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<Point> graph = new ConnectibleGraph<>(profile);
graph.build(x5, entrys);
// x5 <-> x4, x5 <-> x6
SimpleCollector paths = new SimpleCollector();
Crawler<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<List<Edge<Point>>> paths = new ArrayList<>();
public void add(List<Edge<Point>> path){
paths.add(path);
}
public List<List<Edge<Point>>> get() {
return paths;
}
public List<Edge<Point>> get(int indx) {
if (indx >= paths.size()) return Collections.emptyList();
return paths.get(indx);
}
public void clear(){
paths.clear();
}
}
}

View File

@@ -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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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();
}
}

View File

@@ -0,0 +1,53 @@
package ru.trader.analysis.graph;
import java.util.Arrays;
import java.util.List;
public class PPath<T> {
private final T[] points;
private PPath(T[] points) {
this.points = points;
}
private PPath(List<Edge<T>> edges) {
//noinspection unchecked
points = (T[]) new Object[edges.size()+1];
for (int i = 0; i < edges.size(); i++) {
Edge<T> 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 <V> PPath<V> of(V... entries){
return new PPath<>(entries);
}
public static <V> PPath<V> of(List<Edge<V>> 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);
}
}

View File

@@ -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;

View File

@@ -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