Archived
0

implement multi-threading route search

This commit is contained in:
iMoHax
2014-09-12 15:23:35 +04:00
parent c4bdc3894e
commit ece5389f6f
12 changed files with 506 additions and 195 deletions

View File

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

View File

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

View File

@@ -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,23 +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);
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) {
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);
@@ -44,50 +57,20 @@ public class MarketAnalyzer {
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 {
if (order.getProfit() < lowProfit) {
LOG.trace("Is low profit, skip");
break;
}
} else {
top.add(order);
}
}
}
}
return top;
}
public Collection<Order> getOrders(Vendor vendor, double balance) {
Collection<Order> res = new ArrayList<>();
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());
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;
}

View File

@@ -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,46 +53,23 @@ 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){
return vertexes.containsKey(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());
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;
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();
Path<T> res = findFastPath(path, target, deep, nextLimit);
if (res != null) return res;
}
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();
}
}
}
}
}
}

View File

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

View File

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

View File

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

View 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);
}
}
}
}

View File

@@ -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,9 +26,11 @@ public class Vertex<T extends Connectable<T>> {
}
public void addEdge(Edge<T> edge){
synchronized (edges){
if (edges.contains(edge)) return;
edges.add(edge);
}
}
public Collection<Edge<T>> getEdges() {
return edges;

View File

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

View File

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

View 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));
}
}