change graph implementation
This commit is contained in:
23
core/src/main/java/ru/trader/analysis/AnalysisCallBack.java
Normal file
23
core/src/main/java/ru/trader/analysis/AnalysisCallBack.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
291
core/src/main/java/ru/trader/analysis/graph/Crawler.java
Normal file
291
core/src/main/java/ru/trader/analysis/graph/Crawler.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
60
core/src/main/java/ru/trader/analysis/graph/Edge.java
Normal file
60
core/src/main/java/ru/trader/analysis/graph/Edge.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
144
core/src/main/java/ru/trader/analysis/graph/Graph.java
Normal file
144
core/src/main/java/ru/trader/analysis/graph/Graph.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
core/src/main/java/ru/trader/analysis/graph/Vertex.java
Normal file
82
core/src/main/java/ru/trader/analysis/graph/Vertex.java
Normal 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 + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
281
core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java
Normal file
281
core/src/test/java/ru/trader/analysis/graph/CrawlerTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
202
core/src/test/java/ru/trader/analysis/graph/GraphTest.java
Normal file
202
core/src/test/java/ru/trader/analysis/graph/GraphTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
core/src/test/java/ru/trader/analysis/graph/PPath.java
Normal file
53
core/src/test/java/ru/trader/analysis/graph/PPath.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ru.trader.graph;
|
package ru.trader.analysis.graph;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import ru.trader.graph.Connectable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -4,4 +4,4 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
|||||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
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.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n
|
||||||
|
|
||||||
|
#log4j.logger.ru.trader.analysis.graph.Crawler = TRACE
|
||||||
|
|||||||
Reference in New Issue
Block a user