diff --git a/client/src/main/java/ru/trader/controllers/RouterController.java b/client/src/main/java/ru/trader/controllers/RouterController.java index e0acb77..5891ba0 100644 --- a/client/src/main/java/ru/trader/controllers/RouterController.java +++ b/client/src/main/java/ru/trader/controllers/RouterController.java @@ -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); diff --git a/client/src/main/java/ru/trader/model/MarketModel.java b/client/src/main/java/ru/trader/model/MarketModel.java index 85e45bf..9e97a2d 100644 --- a/client/src/main/java/ru/trader/model/MarketModel.java +++ b/client/src/main/java/ru/trader/model/MarketModel.java @@ -202,8 +202,8 @@ public class MarketModel { return BindingsHelper.observableList(analyzer.getOrders(from.getVendor(), to.getVendor(), balance), this::asModel); } - public ObservableList getTop(int limit, double balance){ - return BindingsHelper.observableList(analyzer.getTop(limit, balance), this::asModel); + public ObservableList getTop(double balance){ + return BindingsHelper.observableList(analyzer.getTop(balance), this::asModel); } public ObservableList getRoutes(VendorModel from, double balance){ @@ -215,7 +215,7 @@ public class MarketModel { } public ObservableList 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) { diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzer.java b/core/src/main/java/ru/trader/core/MarketAnalyzer.java index c3a05fc..ae6a406 100644 --- a/core/src/main/java/ru/trader/core/MarketAnalyzer.java +++ b/core/src/main/java/ru/trader/core/MarketAnalyzer.java @@ -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 orderComparator = (o1, o2) -> o2.compareTo(o1); public MarketAnalyzer(Market market) { this.market = market; } - public Collection getTop(int limit, double balance){ - LOG.debug("Get top {}", limit); - TreeSet top = new TreeSet<>(); - for (Vendor vendor : market.get()) { + public Collection getTop(double balance){ + LOG.debug("Get top {}", count); + Collection vendors = market.get(); + List 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 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 orders = getOrders(vendor, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); + RouteGraph.addAllToTop(top, orders, count, orderComparator); } return top; } public Collection getOrders(Vendor vendor, double balance) { - Collection res = new ArrayList<>(); - setSource(vendor); + return getOrders(vendor, balance, 0); + } + + private Collection getOrders(Vendor vendor, double balance, double lowProfit) { + List res = new ArrayList<>(20); + Collection 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 getOrders(Vendor from, Vendor to, double balance) { Collection 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> 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 getPaths(Vendor from, double balance){ - setSource(from); - graph.setBalance(balance); - Collection vendors = market.get(); - List res = new ArrayList<>(vendors.size()*10); - for (Vendor vendor : vendors) { - Collection> paths = graph.getPathsTo(vendor, 10); - for (Path 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 getPaths(Vendor from, Vendor to, double balance){ - setSource(from); - graph.setBalance(balance); - Collection> paths = graph.getPathsTo(to); - Collection res = new ArrayList<>(paths.size()); - for (Path 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 getTopPaths(int limit, double balance){ - List top = new ArrayList<>(limit); - for (Vendor vendor : market.get()) { - setSource(vendor); - graph.setBalance(balance); - Collection> paths = graph.getPathsTo(vendor, 10); - for (Path path : paths) { - RouteGraph.addToTop(top, (PathRoute)path, limit, RouteGraph.comparator); - } + public Collection getTopPaths(double balance){ + List top = new ArrayList<>(count); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment); + Collection vendors = market.get(); + for (Vendor vendor : vendors) { + Collection 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; } diff --git a/core/src/main/java/ru/trader/graph/Graph.java b/core/src/main/java/ru/trader/graph/Graph.java index c9bb1dc..ad1a996 100644 --- a/core/src/main/java/ru/trader/graph/Graph.java +++ b/core/src/main/java/ru/trader/graph/Graph.java @@ -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> { + private final static ForkJoinPool POOL = new ForkJoinPool(); + private final static int THRESHOLD = 4; @FunctionalInterface public interface PathConstructor> { @@ -17,12 +21,13 @@ public class Graph> { private final static Logger LOG = LoggerFactory.getLogger(Graph.class); private final Vertex root; - private final HashMap> vertexes; + private final Map> vertexes; private final double stock; private final double maxDistance; private final boolean withRefill; private final PathConstructor pathFabric; + private int minJumps; public Graph(T start, Collection set, double stock, int maxDeep) { @@ -48,45 +53,22 @@ public class Graph> { 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 vertex, Collection 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 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 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 root, Collection 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 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> { return vertexes.get(entry); } - public Collection> getPathsTo(T entry){ + private void findPathsTo(Vertex target, int max, List> res){ + POOL.invoke(new PathFinder(res, max, pathFabric.build(root), target, root.getLevel() - 1, stock)); + } + + public List> getPathsTo(T entry){ return getPathsTo(entry, 200); } - public Collection> getPathsTo(T entry, int max){ + public List> getPathsTo(T entry, int max){ Vertex target = getVertex(entry); ArrayList> paths = new ArrayList<>(max); - findPaths(paths, max, pathFabric.build(root), target, root.getLevel()-1, stock); + findPathsTo(target, max, paths); + return paths; + } + + public List> getPaths(int count){ + ArrayList> paths = new ArrayList<>(vertexes.size()*count); + for (Vertex target : vertexes.values()) { + ArrayList> p = new ArrayList<>(count); + findPathsTo(target, count, p); + for (Path path : p) { + paths.add(path); + } + } return paths; } - private boolean findPaths(ArrayList> paths, int max, Path head, Vertex target, int deep, double limit){ - if (target == null) return true; - Vertex source = head.getTarget(); - LOG.trace("Find path to deep from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head); - Edge edge = source.getEdge(target); - if (edge != null ){ - if (!(withRefill && Math.min(limit, maxDistance) < edge.getLength() && !source.getEntry().canRefill())){ - Path 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 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 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> paths, int max, Path path){ + protected boolean onFindPath(List> paths, int max, Path path){ + if (paths.size() >= max) return true; paths.add(path); return paths.size() >= max; } @@ -156,30 +124,32 @@ public class Graph> { private Path findFastPath(Path head, Vertex target, int deep, double limit) { Vertex 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 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 path = head.connectTo(next.getTarget(), limit < next.getLength()); - path.finish(); - LOG.trace("Last edge find, path {}", path); - return path; - } + Optional> last = source.getEdges().parallelStream() + .filter(next -> next.isConnect(target) && distanceFilter.test(next.getLength()) && !head.isConnect(next.getTarget())) + .findFirst(); + if (last.isPresent()){ + Path 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 next : source.getEdges()) { - if (next.getTarget().getLevel() >= source.getLevel()) continue; - if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue; - Path path = head.connectTo(next.getTarget(), limit < next.getLength()); - double nextLimit = withRefill ? limit - next.getLength(): stock; - // refill - if (nextLimit < 0 ) nextLimit = stock - next.getLength(); - Path res = findFastPath(path, target, deep, nextLimit); - if (res != null) return res; - } + Optional> res = source.getEdges().parallelStream() + .filter(next -> next.getTarget().getLevel() < source.getLevel() && distanceFilter.test(next.getLength())) + .map((next) -> { + Path 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> { public T getRoot() { return root.getEntry(); } + + public int getMinJumps() { + return minJumps; + } + + private class DistanceFilter implements Predicate { + private final double limit; + private final T source; + + private DistanceFilter(double limit, T source) { + this.limit = limit; + this.source = source; + } + + @Override + public boolean test(Double distance) { + return distance <= Math.min(limit, maxDistance) || (withRefill && distance <= maxDistance && source.canRefill()); + } + } + + private class GraphBuilder extends RecursiveAction { + private final Vertex vertex; + private final Collection set; + private final int deep; + private final double limit; + private final DistanceFilter distanceFilter; + + private GraphBuilder(Vertex vertex, Collection 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 subTasks = new ArrayList<>(set.size()); + Iterator 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 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> paths; + private final int max; + private final Path head; + private final Vertex target; + private final int deep; + private final double limit; + private final DistanceFilter distanceFilter; + + private PathFinder(List> paths, int max, Path head, Vertex 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 source = head.getTarget(); + LOG.trace("Find path to deep from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head); + Edge edge = source.getEdge(target); + if (edge != null){ + if (distanceFilter.test(edge.getLength())){ + Path 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 subTasks = new ArrayList<>(source.getEdges().size()); + Iterator> iterator = source.getEdges().iterator(); + while (iterator.hasNext()) { + Edge 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 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(); + } + } + } + } + } + + } diff --git a/core/src/main/java/ru/trader/graph/Path.java b/core/src/main/java/ru/trader/graph/Path.java index 9a71b7a..148c2c7 100644 --- a/core/src/main/java/ru/trader/graph/Path.java +++ b/core/src/main/java/ru/trader/graph/Path.java @@ -67,6 +67,10 @@ public class Path> { 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); } diff --git a/core/src/main/java/ru/trader/graph/PathRoute.java b/core/src/main/java/ru/trader/graph/PathRoute.java index c6d6706..6433e12 100644 --- a/core/src/main/java/ru/trader/graph/PathRoute.java +++ b/core/src/main/java/ru/trader/graph/PathRoute.java @@ -128,7 +128,7 @@ public class PathRoute extends Path { 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 { } public Order getBest(){ + if (orders.isEmpty()) return null; return orders.get(0); } @@ -278,9 +279,8 @@ public class PathRoute extends Path { @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 { 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 { } 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())); + + } } diff --git a/core/src/main/java/ru/trader/graph/RouteGraph.java b/core/src/main/java/ru/trader/graph/RouteGraph.java index 6d5ef31..aa1ceb3 100644 --- a/core/src/main/java/ru/trader/graph/RouteGraph.java +++ b/core/src/main/java/ru/trader/graph/RouteGraph.java @@ -35,7 +35,7 @@ public class RouteGraph extends Graph { } @Override - protected boolean onFindPath(ArrayList> paths, int max, Path path) { + protected boolean onFindPath(List> paths, int max, Path 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 { } } } + + public static void addAllToTop(List list, Collection sortEntries, int limit, Comparator 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); + } + } + } + } + } diff --git a/core/src/main/java/ru/trader/graph/RouteSearcher.java b/core/src/main/java/ru/trader/graph/RouteSearcher.java new file mode 100644 index 0000000..d91e3c2 --- /dev/null +++ b/core/src/main/java/ru/trader/graph/RouteSearcher.java @@ -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 getPaths(Vendor from, Vendor to, Collection 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 getPaths(Vendor from, Collection 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> { + private final Vendor source; + private final Vendor target; + private final Collection vendors; + private final int jumps; + private final double balance; + private final int cargo; + private int limit; + + public SegmentSearcher(Vendor source, Vendor target, Collection 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 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 res = new ArrayList<>(limit); + if (jumps <= segmentJump){ + LOG.trace("Is last segment"); + List> paths; + if (target == null){ + paths = sGraph.getPaths(10); + } else { + paths = sGraph.getPathsTo(target, limit); + } + for (Path path : paths) { + res.add((PathRoute) path); + } + } else { + LOG.trace("Split to segments"); + List> paths = sGraph.getPaths(1); + int i = 0; + ArrayList 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 res){ + List 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); + } + } + + } + + +} diff --git a/core/src/main/java/ru/trader/graph/Vertex.java b/core/src/main/java/ru/trader/graph/Vertex.java index 519fe26..374b52b 100644 --- a/core/src/main/java/ru/trader/graph/Vertex.java +++ b/core/src/main/java/ru/trader/graph/Vertex.java @@ -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> { private final ArrayList> 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> { } public void addEdge(Edge edge){ - if (edges.contains(edge)) return; - edges.add(edge); + synchronized (edges){ + if (edges.contains(edge)) return; + edges.add(edge); + } } public Collection> getEdges() { diff --git a/core/src/test/java/ru/trader/TestUtil.java b/core/src/test/java/ru/trader/TestUtil.java index f61590c..29b3e97 100644 --- a/core/src/test/java/ru/trader/TestUtil.java +++ b/core/src/test/java/ru/trader/TestUtil.java @@ -84,6 +84,20 @@ public class TestUtil { checkContains(collection, false, items); } + @SafeVarargs + public static void assertCollectionContainAny(Collection 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 void assertCollectionContainAll(Collection collection, T... items){ checkContains(collection, true, items); diff --git a/core/src/test/java/ru/trader/graph/GraphTest.java b/core/src/test/java/ru/trader/graph/GraphTest.java index 14eb9b0..bb9ef80 100644 --- a/core/src/test/java/ru/trader/graph/GraphTest.java +++ b/core/src/test/java/ru/trader/graph/GraphTest.java @@ -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), diff --git a/core/src/test/java/ru/trader/graph/RouteSearcherTest.java b/core/src/test/java/ru/trader/graph/RouteSearcherTest.java new file mode 100644 index 0000000..fa16074 --- /dev/null +++ b/core/src/test/java/ru/trader/graph/RouteSearcherTest.java @@ -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> epaths = graph.getPathsTo(ithaca, 10); + PathRoute expect = epaths.stream().map(p -> (PathRoute) p).findFirst().get(); + + List 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)); + + } +}