implement multi-threading route search
This commit is contained in:
@@ -176,7 +176,7 @@ public class RouterController {
|
||||
|
||||
|
||||
public void showTopOrders(){
|
||||
OrderModel order = Screeners.showOrders(market.getTop(100, totalBalance.getValue().doubleValue()));
|
||||
OrderModel order = Screeners.showOrders(market.getTop(totalBalance.getValue().doubleValue()));
|
||||
if (order!=null){
|
||||
tblOrders.getItems().add(order);
|
||||
addOrderToPath(order);
|
||||
|
||||
@@ -202,8 +202,8 @@ public class MarketModel {
|
||||
return BindingsHelper.observableList(analyzer.getOrders(from.getVendor(), to.getVendor(), balance), this::asModel);
|
||||
}
|
||||
|
||||
public ObservableList<OrderModel> getTop(int limit, double balance){
|
||||
return BindingsHelper.observableList(analyzer.getTop(limit, balance), this::asModel);
|
||||
public ObservableList<OrderModel> getTop(double balance){
|
||||
return BindingsHelper.observableList(analyzer.getTop(balance), this::asModel);
|
||||
}
|
||||
|
||||
public ObservableList<PathRouteModel> getRoutes(VendorModel from, double balance){
|
||||
@@ -215,7 +215,7 @@ public class MarketModel {
|
||||
}
|
||||
|
||||
public ObservableList<PathRouteModel> getTopRoutes(double balance){
|
||||
return BindingsHelper.observableList(analyzer.getTopPaths(100, balance), this::asModel);
|
||||
return BindingsHelper.observableList(analyzer.getTopPaths(balance), this::asModel);
|
||||
}
|
||||
|
||||
PathRoute getPath(VendorModel from, VendorModel to) {
|
||||
|
||||
@@ -2,10 +2,7 @@ package ru.trader.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ru.trader.graph.Graph;
|
||||
import ru.trader.graph.Path;
|
||||
import ru.trader.graph.PathRoute;
|
||||
import ru.trader.graph.RouteGraph;
|
||||
import ru.trader.graph.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -13,59 +10,39 @@ public class MarketAnalyzer {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(MarketAnalyzer.class);
|
||||
|
||||
private Market market;
|
||||
private RouteGraph graph;
|
||||
private double tank;
|
||||
private double maxDistance;
|
||||
private double segment = 50;
|
||||
private int count = 100;
|
||||
private int jumps;
|
||||
private int cargo;
|
||||
|
||||
private final static Comparator<Order> orderComparator = (o1, o2) -> o2.compareTo(o1);
|
||||
|
||||
public MarketAnalyzer(Market market) {
|
||||
this.market = market;
|
||||
}
|
||||
|
||||
public Collection<Order> getTop(int limit, double balance){
|
||||
LOG.debug("Get top {}", limit);
|
||||
TreeSet<Order> top = new TreeSet<>();
|
||||
for (Vendor vendor : market.get()) {
|
||||
public Collection<Order> getTop(double balance){
|
||||
LOG.debug("Get top {}", count);
|
||||
Collection<Vendor> vendors = market.get();
|
||||
List<Order> top = new ArrayList<>(count);
|
||||
for (Vendor vendor : vendors) {
|
||||
LOG.trace("Check vendor {}", vendor);
|
||||
setSource(vendor);
|
||||
for (Offer sell : vendor.getAllSellOffers()) {
|
||||
long count = Math.min(cargo, (long) Math.floor(balance / sell.getPrice()));
|
||||
LOG.trace("Sell offer {}, count = {}", sell, count);
|
||||
if (count == 0) continue;
|
||||
Iterator<Offer> buyers = market.getStatBuy(sell.getItem()).getOffers().descendingIterator();
|
||||
while (buyers.hasNext()){
|
||||
Offer buy = buyers.next();
|
||||
if (!graph.isAccessible(buy.getVendor())){
|
||||
LOG.trace("Is inaccessible buyer, skip");
|
||||
continue;
|
||||
}
|
||||
Order order = new Order(sell, buy, count);
|
||||
LOG.trace("Buy offer {} profit = {}", buy, order.getProfit());
|
||||
if (order.getProfit() <= 0 ) break;
|
||||
if (top.size() == limit){
|
||||
LOG.trace("Min order {}", top.first());
|
||||
if (top.first().getProfit() < order.getProfit()) {
|
||||
LOG.trace("Add to top");
|
||||
top.add(order);
|
||||
top.pollFirst();
|
||||
} else {
|
||||
LOG.trace("Is low profit, skip");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
top.add(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
Collection<Order> orders = getOrders(vendor, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit());
|
||||
RouteGraph.addAllToTop(top, orders, count, orderComparator);
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
public Collection<Order> getOrders(Vendor vendor, double balance) {
|
||||
Collection<Order> res = new ArrayList<>();
|
||||
setSource(vendor);
|
||||
return getOrders(vendor, balance, 0);
|
||||
}
|
||||
|
||||
private Collection<Order> getOrders(Vendor vendor, double balance, double lowProfit) {
|
||||
List<Order> res = new ArrayList<>(20);
|
||||
Collection<Vendor> vendors = market.get();
|
||||
RouteGraph graph = new RouteGraph(vendor, vendors, tank, maxDistance, true, jumps);
|
||||
for (Offer sell : vendor.getAllSellOffers()) {
|
||||
long count = Math.min(cargo, (long) Math.floor(balance / sell.getPrice()));
|
||||
LOG.trace("Sell offer {}, count = {}", sell, count);
|
||||
@@ -79,15 +56,21 @@ public class MarketAnalyzer {
|
||||
}
|
||||
Order order = new Order(sell, buy, count);
|
||||
LOG.trace("Buy offer {} profit = {}", buy, order.getProfit());
|
||||
if (order.getProfit() <= 0 ) break;
|
||||
if (order.getProfit() < lowProfit) {
|
||||
LOG.trace("Is low profit, skip");
|
||||
break;
|
||||
}
|
||||
res.add(order);
|
||||
}
|
||||
}
|
||||
res.sort(orderComparator);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Collection<Order> getOrders(Vendor from, Vendor to, double balance) {
|
||||
Collection<Order> res = new ArrayList<>();
|
||||
setSource(from);
|
||||
RouteGraph graph = new RouteGraph(from, market.get(), tank, maxDistance, true, jumps);
|
||||
if (!graph.isAccessible(to)){
|
||||
LOG.trace("Is inaccessible buyer");
|
||||
return res;
|
||||
@@ -103,67 +86,37 @@ public class MarketAnalyzer {
|
||||
LOG.trace("Buy offer {} profit = {}", buy, order.getProfit());
|
||||
res.add(order);
|
||||
}
|
||||
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private void rebuild(Vendor source){
|
||||
graph = new RouteGraph(source, market.get(), tank, maxDistance, true, jumps);
|
||||
graph.setLimit(cargo);
|
||||
}
|
||||
|
||||
private void setSource(Vendor source){
|
||||
if (graph == null || !graph.getRoot().equals(source))
|
||||
rebuild(source);
|
||||
}
|
||||
|
||||
public Collection<Path<Vendor>> getPaths(Vendor from, Vendor to){
|
||||
setSource(from);
|
||||
RouteGraph graph = new RouteGraph(from, market.get(), tank, maxDistance, true, jumps);
|
||||
return graph.getPathsTo(to);
|
||||
}
|
||||
|
||||
public PathRoute getPath(Vendor from, Vendor to){
|
||||
setSource(from);
|
||||
RouteGraph graph = new RouteGraph(from, market.get(), tank, maxDistance, true, jumps);
|
||||
return (PathRoute) graph.getFastPathTo(to);
|
||||
}
|
||||
|
||||
public Collection<PathRoute> getPaths(Vendor from, double balance){
|
||||
setSource(from);
|
||||
graph.setBalance(balance);
|
||||
Collection<Vendor> vendors = market.get();
|
||||
List<PathRoute> res = new ArrayList<>(vendors.size()*10);
|
||||
for (Vendor vendor : vendors) {
|
||||
Collection<Path<Vendor>> paths = graph.getPathsTo(vendor, 10);
|
||||
for (Path<Vendor> path : paths) {
|
||||
res.add((PathRoute) path);
|
||||
}
|
||||
}
|
||||
Collections.sort(res, RouteGraph.comparator);
|
||||
return res;
|
||||
RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment);
|
||||
return searcher.getPaths(from, market.get(), jumps, balance, cargo, count);
|
||||
}
|
||||
|
||||
public Collection<PathRoute> getPaths(Vendor from, Vendor to, double balance){
|
||||
setSource(from);
|
||||
graph.setBalance(balance);
|
||||
Collection<Path<Vendor>> paths = graph.getPathsTo(to);
|
||||
Collection<PathRoute> res = new ArrayList<>(paths.size());
|
||||
for (Path<Vendor> path : paths) {
|
||||
res.add((PathRoute) path);
|
||||
}
|
||||
return res;
|
||||
RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment);
|
||||
return searcher.getPaths(from, to, market.get(), jumps, balance, cargo, count);
|
||||
}
|
||||
|
||||
public Collection<PathRoute> getTopPaths(int limit, double balance){
|
||||
List<PathRoute> top = new ArrayList<>(limit);
|
||||
for (Vendor vendor : market.get()) {
|
||||
setSource(vendor);
|
||||
graph.setBalance(balance);
|
||||
Collection<Path<Vendor>> paths = graph.getPathsTo(vendor, 10);
|
||||
for (Path<Vendor> path : paths) {
|
||||
RouteGraph.addToTop(top, (PathRoute)path, limit, RouteGraph.comparator);
|
||||
}
|
||||
public Collection<PathRoute> getTopPaths(double balance){
|
||||
List<PathRoute> top = new ArrayList<>(count);
|
||||
RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment);
|
||||
Collection<Vendor> vendors = market.get();
|
||||
for (Vendor vendor : vendors) {
|
||||
Collection<PathRoute> paths = searcher.getPaths(vendor, vendor, vendors, jumps, balance, cargo, 3);
|
||||
RouteGraph.addAllToTop(top, paths, count, RouteGraph.comparator);
|
||||
}
|
||||
return top;
|
||||
}
|
||||
@@ -171,21 +124,17 @@ public class MarketAnalyzer {
|
||||
|
||||
public void setTank(double tank) {
|
||||
this.tank = tank;
|
||||
this.graph = null;
|
||||
}
|
||||
|
||||
public void setMaxDistance(double maxDistance) {
|
||||
this.maxDistance = maxDistance;
|
||||
this.graph = null;
|
||||
}
|
||||
|
||||
public void setJumps(int jumps) {
|
||||
this.jumps = jumps;
|
||||
this.graph = null;
|
||||
}
|
||||
|
||||
public void setCargo(int cargo) {
|
||||
if (graph != null) graph.setLimit(cargo);
|
||||
this.cargo = cargo;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ package ru.trader.graph;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Graph<T extends Connectable<T>> {
|
||||
private final static ForkJoinPool POOL = new ForkJoinPool();
|
||||
private final static int THRESHOLD = 4;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PathConstructor<E extends Connectable<E>> {
|
||||
@@ -17,12 +21,13 @@ public class Graph<T extends Connectable<T>> {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(Graph.class);
|
||||
|
||||
private final Vertex<T> root;
|
||||
private final HashMap<T,Vertex<T>> vertexes;
|
||||
private final Map<T,Vertex<T>> vertexes;
|
||||
|
||||
private final double stock;
|
||||
private final double maxDistance;
|
||||
private final boolean withRefill;
|
||||
private final PathConstructor<T> pathFabric;
|
||||
private int minJumps;
|
||||
|
||||
|
||||
public Graph(T start, Collection<T> set, double stock, int maxDeep) {
|
||||
@@ -48,45 +53,22 @@ public class Graph<T extends Connectable<T>> {
|
||||
this.pathFabric = pathFabric;
|
||||
root = new Vertex<>(start);
|
||||
root.setLevel(maxDeep);
|
||||
vertexes = new HashMap<>();
|
||||
vertexes = new ConcurrentHashMap<>(50, 0.9f, THRESHOLD);
|
||||
vertexes.put(root.getEntry(), root);
|
||||
buildGraph(root, set, maxDeep-1, stock);
|
||||
build(root, set, maxDeep, stock);
|
||||
}
|
||||
|
||||
private void buildGraph(Vertex<T> vertex, Collection<T> set, int deep, double limit) {
|
||||
LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep);
|
||||
for (T entry : set) {
|
||||
if (entry == vertex.getEntry()) continue;
|
||||
double distance = vertex.getEntry().getDistance(entry);
|
||||
if (distance <= this.maxDistance){
|
||||
if (withRefill && distance > limit && !vertex.getEntry().canRefill()){
|
||||
LOG.trace("Vertex {} is far away, {}", entry, distance);
|
||||
continue;
|
||||
}
|
||||
Vertex<T> next = vertexes.get(entry);
|
||||
if (next == null){
|
||||
LOG.trace("Is new vertex");
|
||||
next = new Vertex<>(entry);
|
||||
vertexes.put(entry, next);
|
||||
}
|
||||
LOG.trace("Add edge from {} to {}", vertex, next);
|
||||
Edge<T> edge = new Edge<>(vertex, next);
|
||||
double nextLimit = withRefill ? limit - edge.getLength(): stock;
|
||||
if (nextLimit < 0) {
|
||||
LOG.trace("Refill");
|
||||
nextLimit = stock - edge.getLength();
|
||||
}
|
||||
vertex.addEdge(edge);
|
||||
// If level >= deep when vertex already added on upper deep
|
||||
if (next.getLevel() < deep){
|
||||
next.setLevel(vertex.getLevel()-1);
|
||||
if (deep > 0){
|
||||
buildGraph(next, set, deep-1, nextLimit);
|
||||
}
|
||||
}
|
||||
private void build(Vertex<T> root, Collection<T> set, int maxDeep, double stock) {
|
||||
POOL.invoke(new GraphBuilder(root, set, maxDeep - 1, stock));
|
||||
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;
|
||||
}
|
||||
}
|
||||
LOG.trace("End build graph from {} on deep {}", vertex, deep);
|
||||
}
|
||||
|
||||
public boolean isAccessible(T entry){
|
||||
@@ -97,51 +79,37 @@ public class Graph<T extends Connectable<T>> {
|
||||
return vertexes.get(entry);
|
||||
}
|
||||
|
||||
public Collection<Path<T>> getPathsTo(T entry){
|
||||
private void findPathsTo(Vertex<T> target, int max, List<Path<T>> res){
|
||||
POOL.invoke(new PathFinder(res, max, pathFabric.build(root), target, root.getLevel() - 1, stock));
|
||||
}
|
||||
|
||||
public List<Path<T>> getPathsTo(T entry){
|
||||
return getPathsTo(entry, 200);
|
||||
}
|
||||
|
||||
public Collection<Path<T>> getPathsTo(T entry, int max){
|
||||
public List<Path<T>> getPathsTo(T entry, int max){
|
||||
Vertex<T> target = getVertex(entry);
|
||||
ArrayList<Path<T>> paths = new ArrayList<>(max);
|
||||
findPaths(paths, max, pathFabric.build(root), target, root.getLevel()-1, stock);
|
||||
findPathsTo(target, max, paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
public List<Path<T>> getPaths(int count){
|
||||
ArrayList<Path<T>> paths = new ArrayList<>(vertexes.size()*count);
|
||||
for (Vertex<T> target : vertexes.values()) {
|
||||
ArrayList<Path<T>> p = new ArrayList<>(count);
|
||||
findPathsTo(target, count, p);
|
||||
for (Path<T> path : p) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
||||
private boolean findPaths(ArrayList<Path<T>> paths, int max, Path<T> head, Vertex<T> target, int deep, double limit){
|
||||
if (target == null) return true;
|
||||
Vertex<T> source = head.getTarget();
|
||||
LOG.trace("Find path to deep from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head);
|
||||
Edge<T> edge = source.getEdge(target);
|
||||
if (edge != null ){
|
||||
if (!(withRefill && Math.min(limit, maxDistance) < edge.getLength() && !source.getEntry().canRefill())){
|
||||
Path<T> path = head.connectTo(edge.getTarget(), limit < edge.getLength());
|
||||
path.finish();
|
||||
LOG.trace("Last edge find, add path {}", path);
|
||||
if (onFindPath(paths, max, path)) return true;
|
||||
}
|
||||
}
|
||||
if (deep > 0 ){
|
||||
if (source.getEdgesCount() > 0){
|
||||
LOG.trace("Search around");
|
||||
for (Edge<T> next : source.getEdges()) {
|
||||
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
|
||||
// target already added if source consist edge
|
||||
if (next.isConnect(target)) continue;
|
||||
Path<T> path = head.connectTo(next.getTarget(), limit < next.getLength());
|
||||
double nextLimit = withRefill ? limit - next.getLength(): stock;
|
||||
// refill
|
||||
if (nextLimit < 0 ) nextLimit = stock - next.getLength();
|
||||
if (findPaths(paths, max, path, target, deep - 1, nextLimit)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// if is true, then break search
|
||||
protected boolean onFindPath(ArrayList<Path<T>> paths, int max, Path<T> path){
|
||||
protected boolean onFindPath(List<Path<T>> paths, int max, Path<T> path){
|
||||
if (paths.size() >= max) return true;
|
||||
paths.add(path);
|
||||
return paths.size() >= max;
|
||||
}
|
||||
@@ -156,30 +124,32 @@ public class Graph<T extends Connectable<T>> {
|
||||
private Path<T> findFastPath(Path<T> head, Vertex<T> target, int deep, double limit) {
|
||||
Vertex<T> source = head.getTarget();
|
||||
LOG.trace("Find fast path from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head);
|
||||
DistanceFilter distanceFilter = new DistanceFilter(limit, source.getEntry());
|
||||
if (deep == source.getLevel()){
|
||||
for (Edge<T> next : source.getEdges()) {
|
||||
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
|
||||
if (head.isConnect(next.getTarget())) continue;
|
||||
if (next.isConnect(target)) {
|
||||
Path<T> path = head.connectTo(next.getTarget(), limit < next.getLength());
|
||||
path.finish();
|
||||
LOG.trace("Last edge find, path {}", path);
|
||||
return path;
|
||||
}
|
||||
Optional<Edge<T>> last = source.getEdges().parallelStream()
|
||||
.filter(next -> next.isConnect(target) && distanceFilter.test(next.getLength()) && !head.isConnect(next.getTarget()))
|
||||
.findFirst();
|
||||
if (last.isPresent()){
|
||||
Path<T> path = head.connectTo(last.get().getTarget(), limit < last.get().getLength());
|
||||
path.finish();
|
||||
LOG.trace("Last edge find, path {}", path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
if (deep < source.getLevel()){
|
||||
LOG.trace("Search around");
|
||||
for (Edge<T> next : source.getEdges()) {
|
||||
if (next.getTarget().getLevel() >= source.getLevel()) continue;
|
||||
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
|
||||
Path<T> path = head.connectTo(next.getTarget(), limit < next.getLength());
|
||||
double nextLimit = withRefill ? limit - next.getLength(): stock;
|
||||
// refill
|
||||
if (nextLimit < 0 ) nextLimit = stock - next.getLength();
|
||||
Path<T> res = findFastPath(path, target, deep, nextLimit);
|
||||
if (res != null) return res;
|
||||
}
|
||||
Optional<Path<T>> res = source.getEdges().parallelStream()
|
||||
.filter(next -> next.getTarget().getLevel() < source.getLevel() && distanceFilter.test(next.getLength()))
|
||||
.map((next) -> {
|
||||
Path<T> path = head.connectTo(next.getTarget(), limit < next.getLength());
|
||||
double nextLimit = withRefill ? limit - next.getLength(): stock;
|
||||
// refill
|
||||
if (nextLimit < 0 ) nextLimit = stock - next.getLength();
|
||||
return findFastPath(path, target, deep, nextLimit);
|
||||
})
|
||||
.filter(path -> path != null)
|
||||
.findFirst();
|
||||
if (res.isPresent()) return res.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -187,4 +157,173 @@ public class Graph<T extends Connectable<T>> {
|
||||
public T getRoot() {
|
||||
return root.getEntry();
|
||||
}
|
||||
|
||||
public int getMinJumps() {
|
||||
return minJumps;
|
||||
}
|
||||
|
||||
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 <= Math.min(limit, maxDistance) || (withRefill && distance <= maxDistance && source.canRefill());
|
||||
}
|
||||
}
|
||||
|
||||
private class GraphBuilder extends RecursiveAction {
|
||||
private final Vertex<T> vertex;
|
||||
private final Collection<T> set;
|
||||
private final int deep;
|
||||
private final double limit;
|
||||
private final DistanceFilter distanceFilter;
|
||||
|
||||
private GraphBuilder(Vertex<T> vertex, Collection<T> set, int deep, double limit) {
|
||||
this.vertex = vertex;
|
||||
this.set = set;
|
||||
this.deep = deep;
|
||||
this.limit = limit;
|
||||
distanceFilter = new DistanceFilter(limit, vertex.getEntry());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compute() {
|
||||
LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep);
|
||||
ArrayList<GraphBuilder> subTasks = new ArrayList<>(set.size());
|
||||
Iterator<T> iterator = set.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
T entry = iterator.next();
|
||||
if (entry == vertex.getEntry()) continue;
|
||||
double distance = vertex.getEntry().getDistance(entry);
|
||||
if (distanceFilter.test(distance)) {
|
||||
Vertex<T> next = vertexes.get(entry);
|
||||
if (next == null) {
|
||||
LOG.trace("Is new vertex");
|
||||
next = new Vertex<>(entry);
|
||||
vertexes.put(entry, next);
|
||||
}
|
||||
LOG.trace("Add edge from {} to {}", vertex, next);
|
||||
vertex.addEdge(new Edge<>(vertex, next));
|
||||
// If level >= deep when vertex already added on upper deep
|
||||
if (next.getLevel() < deep) {
|
||||
next.setLevel(vertex.getLevel() - 1);
|
||||
if (deep > 0) {
|
||||
double nextLimit = withRefill ? limit - distance : stock;
|
||||
if (nextLimit < 0) {
|
||||
LOG.trace("Refill");
|
||||
nextLimit = stock - distance;
|
||||
}
|
||||
//Recursive build
|
||||
GraphBuilder task = new GraphBuilder(next, set, deep - 1, nextLimit);
|
||||
task.fork();
|
||||
subTasks.add(task);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Vertex {} is far away, {}", entry, distance);
|
||||
}
|
||||
if (subTasks.size() == THRESHOLD || !iterator.hasNext()){
|
||||
for (GraphBuilder subTask : subTasks) {
|
||||
subTask.join();
|
||||
}
|
||||
subTasks.clear();
|
||||
}
|
||||
}
|
||||
if (!subTasks.isEmpty()){
|
||||
for (GraphBuilder subTask : subTasks) {
|
||||
subTask.join();
|
||||
}
|
||||
subTasks.clear();
|
||||
}
|
||||
LOG.trace("End build graph from {} on deep {}", vertex, deep);
|
||||
}
|
||||
}
|
||||
|
||||
private class PathFinder extends RecursiveAction {
|
||||
private final List<Path<T>> paths;
|
||||
private final int max;
|
||||
private final Path<T> head;
|
||||
private final Vertex<T> target;
|
||||
private final int deep;
|
||||
private final double limit;
|
||||
private final DistanceFilter distanceFilter;
|
||||
|
||||
private PathFinder(List<Path<T>> paths, int max, Path<T> head, Vertex<T> target, int deep, double limit) {
|
||||
this.paths = paths;
|
||||
this.max = max;
|
||||
this.head = head;
|
||||
this.target = target;
|
||||
this.deep = deep;
|
||||
this.limit = limit;
|
||||
distanceFilter = new DistanceFilter(limit, head.getTarget().getEntry());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compute() {
|
||||
if (target == null || isCancelled()) return;
|
||||
Vertex<T> source = head.getTarget();
|
||||
LOG.trace("Find path to deep from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head);
|
||||
Edge<T> edge = source.getEdge(target);
|
||||
if (edge != null){
|
||||
if (distanceFilter.test(edge.getLength())){
|
||||
Path<T> path = head.connectTo(edge.getTarget(), limit < edge.getLength());
|
||||
path.finish();
|
||||
LOG.trace("Last edge find, add path {}", path);
|
||||
synchronized (paths){
|
||||
if (onFindPath(paths, max, path)) complete(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deep > 0 ){
|
||||
if (source.getEdgesCount() > 0){
|
||||
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()) break;
|
||||
// target already added if source consist edge
|
||||
if (next.isConnect(target)) continue;
|
||||
if (!distanceFilter.test(next.getLength())) continue;
|
||||
Path<T> path = head.connectTo(next.getTarget(), limit < next.getLength());
|
||||
double nextLimit = withRefill ? limit - next.getLength() : stock;
|
||||
// refill
|
||||
if (nextLimit < 0) nextLimit = stock - next.getLength();
|
||||
//Recursive search
|
||||
PathFinder task = new PathFinder(paths, max, path, target, deep - 1, nextLimit);
|
||||
task.fork();
|
||||
subTasks.add(task);
|
||||
if (subTasks.size() == THRESHOLD || !iterator.hasNext()){
|
||||
for (PathFinder subTask : subTasks) {
|
||||
if (isDone()) {
|
||||
subTask.cancel(false);
|
||||
} else {
|
||||
subTask.join();
|
||||
}
|
||||
}
|
||||
subTasks.clear();
|
||||
}
|
||||
}
|
||||
if (!subTasks.isEmpty()){
|
||||
for (PathFinder subTask : subTasks) {
|
||||
if (isDone()) {
|
||||
subTask.cancel(false);
|
||||
} else {
|
||||
subTask.join();
|
||||
}
|
||||
}
|
||||
subTasks.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ public class Path<T extends Connectable<T>> {
|
||||
return target.equals(vertex) || (!isRoot() && head.isConnect(vertex));
|
||||
}
|
||||
|
||||
public boolean isConnect(T entry) {
|
||||
return target.getEntry().equals(entry) || (!isRoot() && head.isConnect(entry));
|
||||
}
|
||||
|
||||
public boolean isPathFrom(T entry) {
|
||||
return !isRoot() && head.target.getEntry().equals(entry);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class PathRoute extends Path<Vendor> {
|
||||
return tail != null;
|
||||
}
|
||||
|
||||
public void update(){
|
||||
private void update(){
|
||||
PathRoute p = this;
|
||||
p.updateBalance();
|
||||
while (p.hasNext()){
|
||||
@@ -259,6 +259,7 @@ public class PathRoute extends Path<Vendor> {
|
||||
}
|
||||
|
||||
public Order getBest(){
|
||||
if (orders.isEmpty()) return null;
|
||||
return orders.get(0);
|
||||
}
|
||||
|
||||
@@ -278,9 +279,8 @@ public class PathRoute extends Path<Vendor> {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Order order : orders) {
|
||||
if (order == TRANSIT) continue;
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
Order order = getBest();
|
||||
if (order != TRANSIT){
|
||||
sb.append(order.getBuy().getItem());
|
||||
sb.append(" (").append(order.getBuyer()).append(") ");
|
||||
}
|
||||
@@ -291,6 +291,7 @@ public class PathRoute extends Path<Vendor> {
|
||||
if (o.length()>0) sb.append(" (").append(o).append(") ");
|
||||
} else {
|
||||
sb.append(getPrevious().toString());
|
||||
sb.append(" ").append(balance).append(" ");
|
||||
if (isRefill()) sb.append("(R)");
|
||||
if (o.length()>0) sb.append(" (").append(o).append(") ");
|
||||
sb.append(" -> ").append(get());
|
||||
@@ -352,4 +353,13 @@ public class PathRoute extends Path<Vendor> {
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public boolean isRoute(PathRoute path){
|
||||
return this == path || (isRoot() ? path.isRoot() : !path.isRoot() && getPrevious().isRoute(path.getPrevious()))
|
||||
&& this.getTarget().equals(path.getTarget())
|
||||
&& this.profit == path.profit
|
||||
&& this.balance == path.balance
|
||||
&& (this.getBest() == null && path.getBest() == null || this.getBest().equals(path.getBest()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class RouteGraph extends Graph<Vendor> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onFindPath(ArrayList<Path<Vendor>> paths, int max, Path<Vendor> path) {
|
||||
protected boolean onFindPath(List<Path<Vendor>> paths, int max, Path<Vendor> path) {
|
||||
PathRoute route = (PathRoute) path;
|
||||
route.sort(balance, limit);
|
||||
addToTop(paths, route, max, (r1, r2) -> comparator.compare((PathRoute)r1, (PathRoute)r2));
|
||||
@@ -59,4 +59,24 @@ public class RouteGraph extends Graph<Vendor> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void addAllToTop(List<T> list, Collection<T> sortEntries, int limit, Comparator<T> comparator){
|
||||
for (T entry : sortEntries) {
|
||||
if (list.size() == limit){
|
||||
int index = Collections.binarySearch(list, entry, comparator);
|
||||
if (index < 0) index = -1 - index;
|
||||
if (index == limit) return;
|
||||
list.add(index, entry);
|
||||
list.remove(limit);
|
||||
} else {
|
||||
if (list.size() < limit-1){
|
||||
list.add(entry);
|
||||
} else {
|
||||
list.add(entry);
|
||||
list.sort(comparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
121
core/src/main/java/ru/trader/graph/RouteSearcher.java
Normal file
121
core/src/main/java/ru/trader/graph/RouteSearcher.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package ru.trader.graph;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ru.trader.core.Vendor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.RecursiveTask;
|
||||
|
||||
public class RouteSearcher {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(RouteSearcher.class);
|
||||
private final static ForkJoinPool POOL = new ForkJoinPool();
|
||||
private final static int THRESHOLD = (int) Math.ceil(Runtime.getRuntime().availableProcessors()/2.0);
|
||||
|
||||
private double maxDistance;
|
||||
private double stock;
|
||||
private int segmentJump;
|
||||
|
||||
public RouteSearcher(double maxDistance, double stock, double segment) {
|
||||
this.maxDistance = maxDistance;
|
||||
this.stock = stock;
|
||||
this.segmentJump = (int) Math.floor(segment/maxDistance);
|
||||
}
|
||||
|
||||
public List<PathRoute> getPaths(Vendor from, Vendor to, Collection<Vendor> vendors, int jumps, double balance, int cargo, int limit){
|
||||
if (segmentJump == 0){
|
||||
RouteGraph sGraph = new RouteGraph(from, vendors, stock, maxDistance, true, jumps);
|
||||
segmentJump = sGraph.getMinJumps() > 1 ? sGraph.getMinJumps()-1 : sGraph.getMinJumps();
|
||||
}
|
||||
return POOL.invoke(new SegmentSearcher(from, to, vendors, jumps, balance, cargo, limit));
|
||||
}
|
||||
|
||||
public List<PathRoute> getPaths(Vendor from, Collection<Vendor> vendors, int jumps, double balance, int cargo, int limit){
|
||||
if (segmentJump == 0){
|
||||
RouteGraph sGraph = new RouteGraph(from, vendors, stock, maxDistance, true, jumps);
|
||||
segmentJump = sGraph.getMinJumps() > 1 ? sGraph.getMinJumps()-1 : sGraph.getMinJumps();
|
||||
}
|
||||
return POOL.invoke(new SegmentSearcher(from, null, vendors, jumps, balance, cargo, limit));
|
||||
}
|
||||
|
||||
public class SegmentSearcher extends RecursiveTask<List<PathRoute>> {
|
||||
private final Vendor source;
|
||||
private final Vendor target;
|
||||
private final Collection<Vendor> vendors;
|
||||
private final int jumps;
|
||||
private final double balance;
|
||||
private final int cargo;
|
||||
private int limit;
|
||||
|
||||
public SegmentSearcher(Vendor source, Vendor target, Collection<Vendor> vendors, int jumps, double balance, int cargo, int limit) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.vendors = vendors;
|
||||
this.jumps = jumps;
|
||||
this.balance = balance;
|
||||
this.cargo = cargo;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<PathRoute> compute() {
|
||||
LOG.trace("Start search route to {} from {}, jumps {}", source, target, jumps);
|
||||
RouteGraph sGraph = new RouteGraph(source, vendors, stock, maxDistance, true, Math.min(jumps, segmentJump));
|
||||
sGraph.setLimit(cargo);
|
||||
sGraph.setBalance(balance);
|
||||
List<PathRoute> res = new ArrayList<>(limit);
|
||||
if (jumps <= segmentJump){
|
||||
LOG.trace("Is last segment");
|
||||
List<Path<Vendor>> paths;
|
||||
if (target == null){
|
||||
paths = sGraph.getPaths(10);
|
||||
} else {
|
||||
paths = sGraph.getPathsTo(target, limit);
|
||||
}
|
||||
for (Path<Vendor> path : paths) {
|
||||
res.add((PathRoute) path);
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Split to segments");
|
||||
List<Path<Vendor>> paths = sGraph.getPaths(1);
|
||||
int i = 0;
|
||||
ArrayList<SegmentSearcher> subTasks = new ArrayList<>(THRESHOLD);
|
||||
while (i < paths.size()) {
|
||||
subTasks.clear();
|
||||
for (int taskIndex = 0; taskIndex < THRESHOLD && i+taskIndex < paths.size(); taskIndex++) {
|
||||
PathRoute path = (PathRoute) paths.get(i+taskIndex);
|
||||
double newBalance = balance + path.getRoot().getProfit();
|
||||
SegmentSearcher task = new SegmentSearcher(path.get(), target, vendors, jumps - segmentJump, newBalance, cargo, (int) Math.ceil(limit / 2.0));
|
||||
task.fork();
|
||||
subTasks.add(task);
|
||||
}
|
||||
for (int taskIndex = 0; taskIndex < subTasks.size(); taskIndex++) {
|
||||
PathRoute path = (PathRoute) paths.get(i+taskIndex);
|
||||
add(subTasks.get(taskIndex), path, res);
|
||||
}
|
||||
i+=subTasks.size();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private void add(SegmentSearcher task, PathRoute path, List<PathRoute> res){
|
||||
List<PathRoute> tail = task.join();
|
||||
if (tail.isEmpty()){
|
||||
LOG.trace("Not found route from {} to {}, jumps {}", task.source, task.target, task.jumps);
|
||||
} else {
|
||||
path.add(tail.get(0), false);
|
||||
path.sort(balance, cargo);
|
||||
RouteGraph.addToTop(res, path.getEnd(), limit, RouteGraph.comparator);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,12 +3,11 @@ package ru.trader.graph;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Vertex<T extends Connectable<T>> {
|
||||
private final ArrayList<Edge<T>> edges = new ArrayList<>();
|
||||
private final T entry;
|
||||
private int level = -1;
|
||||
private volatile int level = -1;
|
||||
|
||||
public Vertex(T entry) {
|
||||
this.entry = entry;
|
||||
@@ -27,8 +26,10 @@ public class Vertex<T extends Connectable<T>> {
|
||||
}
|
||||
|
||||
public void addEdge(Edge<T> edge){
|
||||
if (edges.contains(edge)) return;
|
||||
edges.add(edge);
|
||||
synchronized (edges){
|
||||
if (edges.contains(edge)) return;
|
||||
edges.add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Edge<T>> getEdges() {
|
||||
|
||||
@@ -84,6 +84,20 @@ public class TestUtil {
|
||||
checkContains(collection, false, items);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> void assertCollectionContainAny(Collection<T> collection, T... items){
|
||||
boolean contain = false;
|
||||
for (T item : items) {
|
||||
if (collection.contains(item)){
|
||||
contain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contain){
|
||||
Assert.fail(String.format("Collection should include any item from %s", items));
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> void assertCollectionContainAll(Collection<T> collection, T... items){
|
||||
checkContains(collection, true, items);
|
||||
|
||||
@@ -233,7 +233,8 @@ public class GraphTest extends Assert {
|
||||
TestUtil.assertCollectionContainAll(paths, Path.toPath(x5, x6, x7), Path.toPath(x5, x4, x6, x7), Path.toPath(x5, x3, x6, x7));
|
||||
|
||||
paths = graph.getPathsTo(x7, 1);
|
||||
TestUtil.assertCollectionContainAll(paths, Path.toPath(x5, x3, x6, x7));
|
||||
assertEquals(1, paths.size());
|
||||
TestUtil.assertCollectionContainAny(paths, Path.toPath(x5, x6, x7), Path.toPath(x5, x4, x6, x7), Path.toPath(x5, x3, x6, x7));
|
||||
|
||||
paths = graph.getPathsTo(x4);
|
||||
TestUtil.assertCollectionContainAll(paths, Path.toPath(x5, x4), Path.toPath(x5, x6, x4), Path.toPath(x5, x3, x4),
|
||||
|
||||
52
core/src/test/java/ru/trader/graph/RouteSearcherTest.java
Normal file
52
core/src/test/java/ru/trader/graph/RouteSearcherTest.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package ru.trader.graph;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import ru.trader.core.Market;
|
||||
import ru.trader.core.Vendor;
|
||||
import ru.trader.store.Store;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class RouteSearcherTest extends Assert {
|
||||
private static Market market;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
InputStream is = getClass().getResourceAsStream("/world.xml");
|
||||
market = Store.loadFromFile(is);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoutes() throws Exception {
|
||||
// Balance: 6000000, cargo: 440, tank: 40, distance: 13.4, jumps: 6
|
||||
// Ithaca (Palladium to LHS 3262) -> Morgor -> LHS 3006 -> LHS 3262 (Consumer Technology to Ithaca) -> LHS 3006 -> Morgor -> Ithaca
|
||||
// Profit: 981200, avg: 490600, distance: 67.5, lands: 2
|
||||
Vendor ithaca = market.get().stream().filter((v)->v.getName().equals("Ithaca")).findFirst().get();
|
||||
Vendor lhs3262 = market.get().stream().filter((v)->v.getName().equals("LHS 3262")).findFirst().get();
|
||||
|
||||
RouteSearcher searcher = new RouteSearcher(13.4, 40, 50);
|
||||
RouteGraph graph = new RouteGraph(ithaca, market.get(), 40, 13.4, true, 6);
|
||||
graph.setLimit(440);
|
||||
graph.setBalance(6000000);
|
||||
|
||||
|
||||
List<Path<Vendor>> epaths = graph.getPathsTo(ithaca, 10);
|
||||
PathRoute expect = epaths.stream().map(p -> (PathRoute) p).findFirst().get();
|
||||
|
||||
List<PathRoute> apaths = searcher.getPaths(ithaca, ithaca, market.get(), 6, 6000000, 440, 10);
|
||||
PathRoute actual = apaths.stream().findFirst().get();
|
||||
assertTrue("Routes is different",expect.isRoute(actual));
|
||||
|
||||
graph = new RouteGraph(lhs3262, market.get(), 40, 13.4, true, 6);
|
||||
graph.setLimit(440);
|
||||
graph.setBalance(6000000);
|
||||
|
||||
expect = graph.getPathsTo(lhs3262, 10).stream().map(p -> (PathRoute)p).findFirst().get();
|
||||
actual = searcher.getPaths(lhs3262, lhs3262, market.get(), 6, 6000000, 440, 10).stream().findFirst().get();
|
||||
assertTrue("Routes is different",expect.isRoute(actual));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user