diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzer.java b/core/src/main/java/ru/trader/core/MarketAnalyzer.java index ae6a406..5f0b44b 100644 --- a/core/src/main/java/ru/trader/core/MarketAnalyzer.java +++ b/core/src/main/java/ru/trader/core/MarketAnalyzer.java @@ -12,8 +12,8 @@ public class MarketAnalyzer { private Market market; private double tank; private double maxDistance; - private double segment = 50; - private int count = 100; + private int segmentSize; + private int limit; private int jumps; private int cargo; @@ -21,16 +21,18 @@ public class MarketAnalyzer { public MarketAnalyzer(Market market) { this.market = market; + this.limit = 100; + this.segmentSize = 0; } public Collection getTop(double balance){ - LOG.debug("Get top {}", count); + LOG.debug("Get top {}", limit); Collection vendors = market.get(); - List top = new ArrayList<>(count); + List top = new ArrayList<>(limit); for (Vendor vendor : vendors) { LOG.trace("Check vendor {}", vendor); Collection orders = getOrders(vendor, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); - RouteGraph.addAllToTop(top, orders, count, orderComparator); + TopList.addAllToTop(top, orders, limit, orderComparator); } return top; } @@ -101,22 +103,22 @@ public class MarketAnalyzer { } public Collection getPaths(Vendor from, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment); - return searcher.getPaths(from, market.get(), jumps, balance, cargo, count); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + return searcher.getPaths(from, market.get(), jumps, balance, cargo, limit); } public Collection getPaths(Vendor from, Vendor to, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment); - return searcher.getPaths(from, to, market.get(), jumps, balance, cargo, count); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + return searcher.getPaths(from, to, market.get(), jumps, balance, cargo, limit); } public Collection getTopPaths(double balance){ - List top = new ArrayList<>(count); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segment); + List top = new ArrayList<>(limit); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); 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); + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); } return top; } @@ -138,5 +140,11 @@ public class MarketAnalyzer { this.cargo = cargo; } + public void setSegmentSize(int segmentSize) { + this.segmentSize = segmentSize; + } + public void setPathsCount(int count) { + this.limit = count; + } } diff --git a/core/src/main/java/ru/trader/graph/Graph.java b/core/src/main/java/ru/trader/graph/Graph.java index ad1a996..b608c86 100644 --- a/core/src/main/java/ru/trader/graph/Graph.java +++ b/core/src/main/java/ru/trader/graph/Graph.java @@ -12,6 +12,7 @@ import java.util.function.Predicate; public class Graph> { private final static ForkJoinPool POOL = new ForkJoinPool(); private final static int THRESHOLD = 4; + private final static int DEFAULT_COUNT = 200; @FunctionalInterface public interface PathConstructor> { @@ -20,14 +21,14 @@ public class Graph> { private final static Logger LOG = LoggerFactory.getLogger(Graph.class); - private final Vertex root; - private final Map> vertexes; + protected final Vertex root; + protected final Map> vertexes; - private final double stock; - private final double maxDistance; - private final boolean withRefill; + protected final double stock; + protected final double maxDistance; + protected final boolean withRefill; private final PathConstructor pathFabric; - private int minJumps; + protected int minJumps; public Graph(T start, Collection set, double stock, int maxDeep) { @@ -79,42 +80,45 @@ public class Graph> { return vertexes.get(entry); } - private void findPathsTo(Vertex target, int max, List> res){ - POOL.invoke(new PathFinder(res, max, pathFabric.build(root), target, root.getLevel() - 1, stock)); + private void findPathsTo(Vertex target, TopList> res, int deep){ + POOL.invoke(new PathFinder(res, pathFabric.build(root), target, deep-1, stock)); } public List> getPathsTo(T entry){ - return getPathsTo(entry, 200); + return getPathsTo(entry, DEFAULT_COUNT); } public List> getPathsTo(T entry, int max){ + return getPathsTo(entry, max, root.getLevel()).getList(); + } + + public TopList> getPathsTo(T entry, int max, int deep){ Vertex target = getVertex(entry); - ArrayList> paths = new ArrayList<>(max); - findPathsTo(target, max, paths); + TopList> paths = newTopList(max); + findPathsTo(target, paths, deep); return paths; } public List> getPaths(int count){ - ArrayList> paths = new ArrayList<>(vertexes.size()*count); + return getPaths(count, root.getLevel()).getList(); + } + + public TopList> getPaths(int count, int deep){ + TopList> paths = newTopList(count); for (Vertex target : vertexes.values()) { - ArrayList> p = new ArrayList<>(count); - findPathsTo(target, count, p); - for (Path path : p) { + TopList> p = newTopList(minJumps); + findPathsTo(target, p, deep); + for (Path path : p.getList()) { paths.add(path); } } return paths; } - - // if is true, then break search - protected boolean onFindPath(List> paths, int max, Path path){ - if (paths.size() >= max) return true; - paths.add(path); - return paths.size() >= max; + protected TopList> newTopList(int count){ + return new TopList<>(count); } - public Path getFastPathTo(T entry){ Vertex target = getVertex(entry); if (target == null) return null; @@ -246,17 +250,15 @@ public class Graph> { } private class PathFinder extends RecursiveAction { - private final List> paths; - private final int max; + private final TopList> paths; 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) { + private PathFinder(TopList> paths, Path head, Vertex target, int deep, double limit) { this.paths = paths; - this.max = max; this.head = head; this.target = target; this.deep = deep; @@ -276,7 +278,7 @@ public class Graph> { path.finish(); LOG.trace("Last edge find, add path {}", path); synchronized (paths){ - if (onFindPath(paths, max, path)) complete(null); + if (!paths.add(path)) complete(null); } } } @@ -296,7 +298,7 @@ public class Graph> { // refill if (nextLimit < 0) nextLimit = stock - next.getLength(); //Recursive search - PathFinder task = new PathFinder(paths, max, path, target, deep - 1, nextLimit); + PathFinder task = new PathFinder(paths, path, target, deep - 1, nextLimit); task.fork(); subTasks.add(task); if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ diff --git a/core/src/main/java/ru/trader/graph/Path.java b/core/src/main/java/ru/trader/graph/Path.java index 148c2c7..b2c1575 100644 --- a/core/src/main/java/ru/trader/graph/Path.java +++ b/core/src/main/java/ru/trader/graph/Path.java @@ -106,5 +106,9 @@ public class Path> { return head; } + public int getLength(){ + return isRoot() ? 0 : 1 + getPrevious().getLength(); + } + } diff --git a/core/src/main/java/ru/trader/graph/PathRoute.java b/core/src/main/java/ru/trader/graph/PathRoute.java index 6433e12..f457da7 100644 --- a/core/src/main/java/ru/trader/graph/PathRoute.java +++ b/core/src/main/java/ru/trader/graph/PathRoute.java @@ -12,19 +12,33 @@ public class PathRoute extends Path { private final static Logger LOG = LoggerFactory.getLogger(PathRoute.class); private final ArrayList orders = new ArrayList<>(); + private final boolean byAvg; private double profit = 0; private double balance = 0; + private double distance = 0; + private int landsCount = 0; private PathRoute tail; public final static Order TRANSIT = null; public PathRoute(Vertex source) { - super(source); + this(source, false); } + public static PathRoute buildAvg(Vertex source){ + return new PathRoute(source, true); + } + + private PathRoute(Vertex source, boolean byAvg) { + super(source); + this.byAvg = byAvg; + } + + private PathRoute(PathRoute head, Vertex vertex, boolean refill) { super(head, vertex, refill); assert head.tail == null; head.tail = this; + byAvg = head.byAvg; //transit orders.add(TRANSIT); } @@ -90,6 +104,22 @@ public class PathRoute extends Path { fillOrders(); getPrevious().finish(); } + updateDistance(); + } + + private void update(){ + PathRoute p = this; + p.updateBalance(); + while (p.hasNext()){ + p = p.getNext(); + p.updateBalance(); + } + while (p != this){ + p.updateProfit(); + p.updateLandsCount(); + p = p.getPrevious(); + } + getRoot().updateDistance(); } private void fillOrders(){ @@ -128,38 +158,26 @@ public class PathRoute extends Path { return tail != null; } - private void update(){ - PathRoute p = this; - p.updateBalance(); - while (p.hasNext()){ - p = p.getNext(); - p.updateBalance(); - } - while (p != this){ - p.updateProfit(); - p = p.getPrevious(); - } - } - - public void sort(double balance, long limit){ + public void sort(double balance, long cargo){ // start on root only if (isRoot()){ this.balance = balance; - if (hasNext()) - getNext().forwardSort(limit); + if (hasNext()){ + getNext().forwardSort(cargo); + } } else { - getPrevious().sort(balance, limit); + getPrevious().sort(balance, cargo); } } - private void forwardSort(long limit){ + private void forwardSort(long cargo){ updateBalance(); boolean needSort = false; for (Order order : orders) { if (order == TRANSIT) continue; - if (order.getCount() < limit){ + if (order.getCount() < cargo){ needSort = true; - order.setMax(balance, limit); + order.setMax(balance, cargo); } } if (needSort){ @@ -168,20 +186,22 @@ public class PathRoute extends Path { LOG.trace("New order of orders {}", orders); } if (hasNext()){ - getNext().forwardSort(limit); + getNext().forwardSort(cargo); } else { LOG.trace("Start back sort"); Order best = orders.get(0); profit = best == TRANSIT ? 0 : best.getProfit(); - LOG.trace("Max profit from {} = {}",getPrevious().get(), profit); + LOG.trace("Max profit from {} = {}", getPrevious().get(), profit); + updateLandsCount(); getPrevious().backwardSort(); } } private void backwardSort(){ - orders.sort(this::compareOrders); + orders.sort(byAvg ? this::compareByAvgProfit : this::compareOrders); LOG.trace("New order of orders {}", orders); updateProfit(); + updateLandsCount(); if (!isRoot()) getPrevious().backwardSort(); } @@ -222,6 +242,10 @@ public class PathRoute extends Path { return profit; } + public double getAvgProfit(){ + return isRoot()? profit/landsCount : getPrevious().getAvgProfit(); + } + public double getBalance() { return balance; } @@ -249,6 +273,17 @@ public class PathRoute extends Path { return Double.compare(profit2, profit1); } + private int compareByAvgProfit(Order o1, Order o2){ + if (o1 != TRANSIT && o2 != TRANSIT){ + if (!hasNext() || o1.isBuyer(o2.getBuyer())) + return o2.compareTo(o1); + } + double profit1 = getProfit(o1)/computeLandsCount(o1); + double profit2 = getProfit(o2)/computeLandsCount(o2); + return Double.compare(profit2, profit1); + } + + @Override public PathRoute getRoot() { return (PathRoute) super.getRoot(); @@ -263,19 +298,67 @@ public class PathRoute extends Path { return orders.get(0); } - public double getDistance(){ + private double computeDistance(){ if (isRoot()){ double res = 0; PathRoute p = this; while (p.hasNext()){ p = p.getNext(); - res += p.getDistance(); + res += p.computeDistance(); } return res; } else return getPrevious().get().getDistance(get()); } + private void updateDistance(){ + this.distance = computeDistance(); + } + + public double getDistance(){ + return distance; + } + + private int computeLandsCount(Order order){ + int res = 0; + PathRoute p = isRoot()? getNext() : this; + while (p.hasNext()){ + p = p.getNext(); + // lands for sell + if (order != null && p.isPathFrom(order.getBuyer())){ + LOG.trace("{} is lands for sell by order {}", p, order); + return res + p.getLandsCount() + 1; + } else { + if (order == null){ + order = p.getBest(); + if (order != null){ + LOG.trace("{} is lands for buy by order {}", p, order); + return res + p.getLandsCount() + 1; + } + } else { + if (p.isRefill()){ + LOG.trace("{} is lands for refill", p); + res++; + } + } + } + + } + LOG.trace("{} is end, landing", p); + res++; + return res; + } + + private void updateLandsCount(){ + Order best = isRoot() ? getNext().getBest() : getBest(); + landsCount = computeLandsCount(best); + LOG.trace("Lands count from {} = {}", isRoot() ? get() : getPrevious().get(), landsCount); + } + + public int getLandsCount() { + return landsCount; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -303,38 +386,6 @@ public class PathRoute extends Path { orders.set(0, order); } - public int getLandsCount(){ - int res = 0; - PathRoute p = this.isRoot() ? getNext() : this; - Order o = p.getBest(); - while (p.hasNext()){ - p = p.getNext(); - // lands for sell - if (o != null && p.isPathFrom(o.getBuyer())){ - LOG.trace("{} is lands for sell by order {}", p, o); - o = p.getBest(); - res++; - } else { - if (o == null){ - o = p.getBest(); - if (o!= null){ - LOG.trace("{} is lands for buy by order {}", p, o); - res++; - } - } else { - if (p.isRefill()){ - LOG.trace("{} is lands for refill", p); - res++; - } - } - } - - } - LOG.trace("{} is end, landing", p); - res++; - return res; - } - public PathRoute dropTo(Vendor vendor){ PathRoute p = getCopy(true).getEnd(); while (!p.isRoot() && !p.get().equals(vendor)){ diff --git a/core/src/main/java/ru/trader/graph/RouteGraph.java b/core/src/main/java/ru/trader/graph/RouteGraph.java index aa1ceb3..26e9cb2 100644 --- a/core/src/main/java/ru/trader/graph/RouteGraph.java +++ b/core/src/main/java/ru/trader/graph/RouteGraph.java @@ -7,12 +7,13 @@ import java.util.*; public class RouteGraph extends Graph { private double balance; - private int limit; + private int cargo; + private boolean groupRes; - public static Comparator comparator = (p1, p2) -> { + public static Comparator byProfitComparator = (p1, p2) -> { PathRoute r1 = p1.getRoot(); PathRoute r2 = p2.getRoot(); - int cmp = Double.compare(r2.getProfit()/r2.getLandsCount(), r1.getProfit()/r1.getLandsCount()); + int cmp = Double.compare(r2.getAvgProfit(), r1.getAvgProfit()); if (cmp != 0 ) return cmp; cmp = Double.compare(r1.getDistance(), r2.getDistance()); if (cmp != 0) return cmp; @@ -21,61 +22,65 @@ public class RouteGraph extends Graph { return cmp; }; + public static Comparator groupByLengthComparator = (p1, p2) -> { + int cmp = Integer.compare(p1.getLength(), p2.getLength()); + if (cmp != 0 ) return cmp; + return byProfitComparator.compare(p1, p2); + }; public RouteGraph(Vendor start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep) { - super(start, set, stock, maxDistance, withRefill, maxDeep, PathRoute::new); + this(start, set, stock, maxDistance, withRefill, maxDeep, false); + } + + public RouteGraph(Vendor start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep, boolean groupRes) { + super(start, set, stock, maxDistance, withRefill, maxDeep, groupRes ? PathRoute::buildAvg : PathRoute::new); + if (groupRes){ + this.groupRes = maxDeep > minJumps; + } } public void setBalance(double balance) { this.balance = balance; } - public void setLimit(int limit) { - this.limit = limit; + public void setCargo(int cargo) { + this.cargo = cargo; } @Override - 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)); - return false; - } - - public static void addToTop(List list, T entry, int limit, Comparator comparator){ - 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); - } + protected TopList> newTopList(int count) { + int groupSize = 0; + if (groupRes && getMinJumps() > 1){ + groupSize = Math.floorDiv(count, root.getLevel()); } + return new TopRoutes(count, groupSize); } - 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); + private class TopRoutes extends TopList> { + private final int groupSize; + + public TopRoutes(int limit, int groupSize) { + super(limit, (p1, p2) -> groupSize > 0 ? groupByLengthComparator.compare((PathRoute)p1, (PathRoute)p2) : RouteGraph.byProfitComparator.compare((PathRoute)p1, (PathRoute)p2)); + this.groupSize = groupSize; + } + + @Override + public boolean add(Path entry) { + if (comparator != null){ + ((PathRoute)entry).sort(balance, cargo); + } + if (groupSize>0){ + addToGroupTop(list, entry, limit, comparator, (e) -> e.getLength()-1, groupSize); } else { - if (list.size() < limit-1){ - list.add(entry); + if (comparator != null){ + addToTop(list, entry, limit, comparator); } else { + if (list.size() >= limit) return false; list.add(entry); - list.sort(comparator); + if (list.size() >= limit) return false; } } + return true; } } diff --git a/core/src/main/java/ru/trader/graph/RouteSearcher.java b/core/src/main/java/ru/trader/graph/RouteSearcher.java index d91e3c2..d81be8e 100644 --- a/core/src/main/java/ru/trader/graph/RouteSearcher.java +++ b/core/src/main/java/ru/trader/graph/RouteSearcher.java @@ -16,40 +16,35 @@ public class RouteSearcher { 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; + private final double maxDistance; + private final double stock; + private final int segmentSize; - public RouteSearcher(double maxDistance, double stock, double segment) { + public RouteSearcher(double maxDistance, double stock) { + this(maxDistance, stock, 0); + } + public RouteSearcher(double maxDistance, double stock, int segmentSize) { this.maxDistance = maxDistance; this.stock = stock; - this.segmentJump = (int) Math.floor(segment/maxDistance); + this.segmentSize = segmentSize; } 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; + protected final Vendor source; + protected final Vendor target; + protected final Collection vendors; + protected final int jumps; + protected final double balance; + protected final int cargo; + protected int limit; public SegmentSearcher(Vendor source, Vendor target, Collection vendors, int jumps, double balance, int cargo, int limit) { this.source = source; @@ -64,15 +59,17 @@ public class RouteSearcher { @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); + RouteGraph sGraph = new RouteGraph(source, vendors, stock, maxDistance, true, jumps, true); + int jumpsToAll = sGraph.getMinJumps(); + LOG.trace("Segment jumps {}", jumpsToAll); + sGraph.setCargo(cargo); sGraph.setBalance(balance); List res = new ArrayList<>(limit); - if (jumps <= segmentJump){ + if (jumps <= jumpsToAll){ LOG.trace("Is last segment"); List> paths; if (target == null){ - paths = sGraph.getPaths(10); + paths = sGraph.getPaths(limit); } else { paths = sGraph.getPathsTo(target, limit); } @@ -81,7 +78,7 @@ public class RouteSearcher { } } else { LOG.trace("Split to segments"); - List> paths = sGraph.getPaths(1); + List> paths = sGraph.getPaths(getPathsOnSegmentCount(sGraph), jumpsToAll-1).getList(); int i = 0; ArrayList subTasks = new ArrayList<>(THRESHOLD); while (i < paths.size()) { @@ -89,7 +86,7 @@ public class RouteSearcher { 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)); + SegmentSearcher task = new SegmentSearcher(path.get(), target, vendors, jumps - path.getLength(), newBalance, cargo, 1); task.fork(); subTasks.add(task); } @@ -100,9 +97,18 @@ public class RouteSearcher { i+=subTasks.size(); } } + finish(res); return res; } + private int getPathsOnSegmentCount(RouteGraph graph){ + if (segmentSize ==0){ + return graph.vertexes.size()*graph.minJumps; + } else { + return segmentSize; + } + } + private void add(SegmentSearcher task, PathRoute path, List res){ List tail = task.join(); @@ -111,10 +117,15 @@ public class RouteSearcher { } else { path.add(tail.get(0), false); path.sort(balance, cargo); - RouteGraph.addToTop(res, path.getEnd(), limit, RouteGraph.comparator); + TopList.addToTop(res, path.getEnd(), limit, RouteGraph.byProfitComparator); } } + private void finish(List res){ + if (res.size() < limit) + res.sort(RouteGraph.byProfitComparator); + } + } diff --git a/core/src/main/java/ru/trader/graph/TopList.java b/core/src/main/java/ru/trader/graph/TopList.java new file mode 100644 index 0000000..1a0759f --- /dev/null +++ b/core/src/main/java/ru/trader/graph/TopList.java @@ -0,0 +1,91 @@ +package ru.trader.graph; + +import java.util.*; +import java.util.function.Function; + +public class TopList { + protected final List list; + protected final int limit; + protected final Comparator comparator; + + public TopList(int limit) { + this(limit, null); + } + + + public TopList(int limit, Comparator comparator) { + this.list = new ArrayList<>(limit); + this.limit = limit; + this.comparator = comparator; + } + + //return true if is last entry or list is full + public boolean add(T entry){ + if (comparator != null){ + addToTop(list, entry, limit, comparator); + } else { + if (list.size() >= limit) return false; + list.add(entry); + if (list.size() >= limit) return false; + } + return true; + } + + public List getList() { + return list; + } + + public static void addToGroupTop(List list, T entry, int limit, Comparator comparator, Function getGroup, int groupSize) { + boolean isFull = list.size() >= limit; + int group = getGroup.apply(entry); + int groupStart = groupSize * group; + int groupEnd = groupSize * (group + 1); + if (!isFull){ + if (groupStart >= list.size()) groupStart = list.size(); + if (groupEnd >= list.size()) groupEnd = list.size(); + } + List groupList = list.subList(groupStart, groupEnd); + T removeEntry = addToTop(groupList, entry, groupSize, comparator); + if (!isFull && removeEntry != null && group != getGroup.apply(removeEntry)){ + addToGroupTop(list, removeEntry, limit, comparator, getGroup, groupSize); + } + } + + public static T addToTop(List list, T entry, int limit, Comparator comparator) { + if (list.size() == limit) { + int index = Collections.binarySearch(list, entry, comparator); + if (index < 0) index = -1 - index; + if (index == limit) return null; + list.add(index, entry); + return list.remove(limit); + + } else { + if (list.size() < limit - 1) { + list.add(entry); + } else { + list.add(entry); + list.sort(comparator); + } + } + return null; + } + + 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); + } + } + } + } +} \ No newline at end of file diff --git a/core/src/test/java/ru/trader/core/MarketAnalyzerTest2.java b/core/src/test/java/ru/trader/core/MarketAnalyzerTest2.java index eb135d3..8437869 100644 --- a/core/src/test/java/ru/trader/core/MarketAnalyzerTest2.java +++ b/core/src/test/java/ru/trader/core/MarketAnalyzerTest2.java @@ -8,6 +8,7 @@ import ru.trader.graph.PathRoute; import ru.trader.store.Store; import java.io.InputStream; import java.util.Collection; +import java.util.Optional; public class MarketAnalyzerTest2 extends Assert { private static MarketAnalyzer analyzer; @@ -32,11 +33,13 @@ public class MarketAnalyzerTest2 extends Assert { analyzer.setCargo(440);analyzer.setTank(40);analyzer.setMaxDistance(13.4);analyzer.setJumps(6); Collection paths = analyzer.getPaths(ithaca, ithaca, 6000000); PathRoute expect = PathRoute.toPathRoute(ithaca, morgor, lhs3006, lhs3262, lhs3006, morgor, ithaca); - PathRoute actual = paths.stream().filter((p)->p.equals(expect)).findFirst().get().getRoot(); + Optional path = paths.stream().filter((p)->p.equals(expect)).findFirst(); + assertTrue(path.isPresent()); + PathRoute actual = path.get().getRoot(); TestUtil.assertCollectionContain(paths, expect); assertEquals(981200, actual.getProfit(), 0.00001); assertEquals(72.42, actual.getDistance(), 0.01); assertEquals(2, actual.getLandsCount()); - assertEquals(490600, actual.getProfit()/actual.getLandsCount() , 0.00001); + assertEquals(490600, actual.getAvgProfit() , 0.00001); } } diff --git a/core/src/test/java/ru/trader/graph/RouteGraphTest.java b/core/src/test/java/ru/trader/graph/RouteGraphTest.java index 43be7d0..3b07a41 100644 --- a/core/src/test/java/ru/trader/graph/RouteGraphTest.java +++ b/core/src/test/java/ru/trader/graph/RouteGraphTest.java @@ -43,7 +43,7 @@ public class RouteGraphTest extends Assert { public void testRoutes() throws Exception { RouteGraph graph = new RouteGraph(v1, market.get(), 1, 1, true, 4); graph.setBalance(500); - graph.setLimit(5); + graph.setCargo(5); //Profit: 150 180 200 230 670 620 950 890 620 950 1015 1180 890 950 930 //Landings: 1 2 3 4 4 2 3 3 2 3 4 4 3 3 4 //Prof: 150 90 66.66 57.5 167.5 310 316.66 296.66 310 316.66 253.75 295 296.66 316.66 232.5 diff --git a/core/src/test/java/ru/trader/graph/RouteSearcherTest.java b/core/src/test/java/ru/trader/graph/RouteSearcherTest.java index fa16074..c8ec271 100644 --- a/core/src/test/java/ru/trader/graph/RouteSearcherTest.java +++ b/core/src/test/java/ru/trader/graph/RouteSearcherTest.java @@ -25,11 +25,10 @@ public class RouteSearcherTest extends Assert { // 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); + RouteSearcher searcher = new RouteSearcher(13.4, 40); RouteGraph graph = new RouteGraph(ithaca, market.get(), 40, 13.4, true, 6); - graph.setLimit(440); + graph.setCargo(440); graph.setBalance(6000000); @@ -40,13 +39,38 @@ public class RouteSearcherTest extends Assert { 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); + } + + @Test + public void testRoutes2() throws Exception { + // Balance: 6000000, cargo: 440, tank: 40, distance: 13.6, 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.6, 40); + RouteGraph graph = new RouteGraph(ithaca, market.get(), 40, 13.6, true, 6); + graph.setCargo(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.6, true, 6); + graph.setCargo(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)); + apaths = searcher.getPaths(lhs3262, lhs3262, market.get(), 6, 6000000, 440, 10); + actual = apaths.stream().findFirst().get(); + assertEquals("Routes is different",expect.getAvgProfit(), actual.getAvgProfit(), 0.00001); } + + } diff --git a/core/src/test/java/ru/trader/graph/TopListTest.java b/core/src/test/java/ru/trader/graph/TopListTest.java new file mode 100644 index 0000000..4c45ca7 --- /dev/null +++ b/core/src/test/java/ru/trader/graph/TopListTest.java @@ -0,0 +1,87 @@ +package ru.trader.graph; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +public class TopListTest extends Assert { + + private static final Function getGroup = (o1) -> Math.floorDiv(o1, 10); + + private static final Comparator groupcomp = (o1, o2) -> { + int cmp = Integer.compare(getGroup.apply(o1), getGroup.apply(o2)); + if (cmp !=0 ) return cmp; + return o1.compareTo(o2); + }; + + private void add(List list, Integer entry){ + TopList.addToGroupTop(list, entry, 10, groupcomp, getGroup, 1); + } + + @Test + public void testAddToGroup() throws Exception { + ArrayList top = new ArrayList<>(10); + add(top, 5); + add(top, 15); + add(top, 22); + add(top, 34); + add(top, 36); + add(top, 21); + add(top, 7); + add(top, 6); + add(top, 3); + + assertEquals(4, top.size()); + assertEquals(3, top.get(0).intValue()); + assertEquals(15, top.get(1).intValue()); + assertEquals(21, top.get(2).intValue()); + assertEquals(34, top.get(3).intValue()); + } + + @Test + public void testAddToGroup2() throws Exception { + ArrayList top = new ArrayList<>(10); + add(top, 36); + add(top, 15); + add(top, 22); + add(top, 6); + add(top, 34); + add(top, 5); + add(top, 21); + add(top, 7); + add(top, 3); + + assertEquals(4, top.size()); + assertEquals(3, top.get(0).intValue()); + assertEquals(15, top.get(1).intValue()); + assertEquals(21, top.get(2).intValue()); + assertEquals(34, top.get(3).intValue()); + } + + + @Test + public void testAddToGroup3() throws Exception { + ArrayList top = new ArrayList<>(10); + add(top, 21); + add(top, 34); + add(top, 36); + add(top, 3); + add(top, 15); + add(top, 22); + add(top, 6); + add(top, 34); + add(top, 5); + add(top, 7); + + assertEquals(4, top.size()); + assertEquals(3, top.get(0).intValue()); + assertEquals(15, top.get(1).intValue()); + assertEquals(21, top.get(2).intValue()); + assertEquals(34, top.get(3).intValue()); + } + +}