diff --git a/core/src/main/java/ru/trader/graph/Path.java b/core/src/main/java/ru/trader/graph/Path.java index 4ba923e..833da3a 100644 --- a/core/src/main/java/ru/trader/graph/Path.java +++ b/core/src/main/java/ru/trader/graph/Path.java @@ -22,14 +22,9 @@ public class Path> { return new Path<>(this, vertex, refill); } - public void finish(){ - finish(target); - } - - protected void finish(Vertex target){ + protected void finish(){ if (!isRoot()){ - head.finish(target); - if (target != head.target) head.finish(); + head.finish(); } } @@ -91,11 +86,15 @@ public class Path> { return target; } + public T get() { + return target.getEntry(); + } + public boolean isRefill(){ return refill; } - protected Path getHead(){ + protected Path getPrevious(){ return head; } diff --git a/core/src/main/java/ru/trader/graph/PathRoute.java b/core/src/main/java/ru/trader/graph/PathRoute.java index c39be2a..b1a4b0f 100644 --- a/core/src/main/java/ru/trader/graph/PathRoute.java +++ b/core/src/main/java/ru/trader/graph/PathRoute.java @@ -1,5 +1,7 @@ package ru.trader.graph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.trader.core.Offer; import ru.trader.core.Order; import ru.trader.core.Vendor; @@ -7,50 +9,107 @@ import ru.trader.core.Vendor; import java.util.*; public class PathRoute extends Path { - private final ArrayList orders = new ArrayList<>(); - private double profit = 0; - private int transitIndex = 0; - private PathRoute tail; + private final static Logger LOG = LoggerFactory.getLogger(PathRoute.class); + private final ArrayList orders = new ArrayList<>(); + private final boolean expand; + private double profit = 0; + private PathRoute tail; + private int ordersCount = 0; + public final static Order TRANSIT = null; + + public PathRoute(Vertex source, boolean expand) { + super(source); + this.expand = expand; + } public PathRoute(Vertex source) { super(source); + expand = false; } private PathRoute(PathRoute head, Vertex vertex, boolean refill) { super(head, vertex, refill); assert head.tail == null; head.tail = this; + expand = head.expand; + //transit + orders.add(ordersCount++, TRANSIT); } @Override public Path connectTo(Vertex vertex, boolean refill) { + LOG.trace("Connect path {} to {}", this, vertex); return new PathRoute(this.getCopy(), vertex, refill); } - @Override - protected void finish(Vertex target) { - if (!isRoot()) { - if (!contains(target.getEntry())){ - updateOrders(target.getEntry()); - getHead().finish(target); - if (getHead().getTarget() != target) getHead().finish(); + public PathRoute getCopy(){ + PathRoute path = (PathRoute) getRoot(); + PathRoute res = new PathRoute(path.getTarget()); + while (path.hasNext()){ + path = path.getNext(); + res = new PathRoute(res, path.getTarget(), path.isRefill()); + } + return res; + } + + private void addOrder(Order order){ + LOG.trace("Add order {} to path {}", order, this); + orders.add(ordersCount++, order); + if (!expand) return; + LOG.trace("Expand orders"); + if (hasNext()){ + PathRoute next = getNext(); + LOG.trace("Add {} clone of order", next.ordersCount - 1); + for (int i = 1; i < next.ordersCount; i++) { + orders.add(ordersCount++, new Order(order.getSell(), order.getBuy())); + addTransitsToHead(); } + cloneTailOrders(next.ordersCount); + } + addTransitsToHead(); + } + + private void addTransitsToHead(){ + PathRoute p = getPrevious(); + while (!p.isRoot()) { + LOG.trace("Add transit order to path {}", p); + p.orders.add(p.ordersCount++, TRANSIT); + p = p.getPrevious(); } } - private boolean contains(Vendor buyer){ - for (Order order : orders) { - if (order.isBuyer(buyer)) return true; + private void cloneTailOrders(int count){ + if (hasNext()) { + PathRoute p = getNext(); + LOG.trace("Duplicate {} orders of path {}", count, p); + for (int i = 0; i < count; i++) { + Order o = p.orders.get(i); + if (o == TRANSIT) p.orders.add(TRANSIT); + else p.orders.add(new Order(o.getSell(), o.getBuy())); + } + p.cloneTailOrders(count); } - return false; } - private void updateOrders(Vendor buyer){ - Vendor seller = getHead().getTarget().getEntry(); + @Override + protected void finish() { + if (!isRoot()){ + fillOrders(); + getPrevious().finish(); + } + } + + private void fillOrders(){ + LOG.trace("Fill orders of path {}", this); + Vendor seller = getPrevious().get(); for (Offer sell : seller.getAllSellOffers()) { - Offer buy = buyer.getBuy(sell.getItem()); - if (buy != null) orders.add(new Order(sell, buy)); + PathRoute p = this; + while (p != null) { + Offer buy = p.get().getBuy(sell.getItem()); + if (buy != null) addOrder(new Order(sell, buy)); + p = p.getNext(); + } } } @@ -58,40 +117,37 @@ public class PathRoute extends Path { return Collections.unmodifiableList(orders); } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (Order order : orders) { - if (sb.length() > 0) sb.append(", "); - sb.append(order.getBuy().getItem()); - sb.append(" (").append(order.getBuyer()).append(") "); - } - String o = sb.toString(); - sb = new StringBuilder(); - if (isRoot()){ - sb.append(getTarget().getEntry()); - if (o.length()>0) sb.append(" (").append(o).append(") "); - } else { - sb.append(getHead().toString()); - if (isRefill()) sb.append("(R)"); - if (o.length()>0) sb.append(" (").append(o).append(") "); - sb.append(" -> ").append(getTarget().getEntry()); - } - return sb.toString(); - } - public boolean isEmpty(){ - return orders.isEmpty(); + return ordersCount <= 1; } - public PathRoute getCopy(){ - PathRoute path = (PathRoute) getRoot(); - PathRoute res = new PathRoute(path.getTarget()); - while (path.tail != null){ - res = new PathRoute(res, path.tail.getTarget(), path.tail.isRefill()); - path = path.tail; + @Override + protected PathRoute getPrevious() { + return (PathRoute) super.getPrevious(); + } + + public PathRoute getNext() { + return tail; + } + + public boolean hasNext(){ + return tail != null; + } + + public void resort(double balance, long limit){ + if (isRoot()) return; + for (Order order : orders) { + if (order == TRANSIT) continue; + order.setMax(balance, limit); } - return res; + orders.sort(this::compareOrders); + + updateProfit(); + getPrevious().resort(balance, limit); + } + + private double getTransitProfit(){ + return hasNext() ? getNext().getProfit() : 0; } public double getProfit(){ @@ -99,54 +155,22 @@ public class PathRoute extends Path { } public double getProfit(Order order){ + if (order == TRANSIT) return getTransitProfit(); if (isPathFrom(order.getBuyer())) return order.getProfit() + profit; - return tail != null ? tail.getProfit(order) : order.getProfit(); - } - - @Override - protected PathRoute getHead() { - return (PathRoute) super.getHead(); - } - - public void resort(double balance, long limit){ - if (isRoot()) return; - for (Order order : orders) { - order.setMax(balance, limit); - } - orders.sort(this::compareOrders); - - updateProfit(); - updateTransitIndex(); - getHead().resort(balance, limit); - } - - private void updateTransitIndex() { - transitIndex = orders.size(); - if (isEmpty()) return; - double transitProfit = tail != null ? tail.getProfit() : 0; - ListIterator itr = orders.listIterator(orders.size()); - while (itr.hasPrevious()){ - Order o = itr.previous(); - if (getProfit(o) > transitProfit){ - return; - } - transitIndex--; - } + return hasNext() ? getNext().getProfit(order) : order.getProfit(); } private void updateProfit() { - if (isEmpty()){ - profit = tail != null ? tail.getProfit() : 0; - } else { - Order best = orders.get(0); - profit = getProfit(best); - } + Order best = orders.get(0); + if (best == TRANSIT) profit = getTransitProfit(); + else profit = getProfit(best); + } private int compareOrders(Order o1, Order o2){ - if (tail == null || o1.isBuyer(o2.getBuyer())){ - //reverse - return o2.compareTo(o1); + if (o1 != TRANSIT && o2 != TRANSIT){ + if (!hasNext() || o1.isBuyer(o2.getBuyer())) + return o2.compareTo(o1); } double profit1 = getProfit(o1); double profit2 = getProfit(o2); @@ -154,11 +178,30 @@ public class PathRoute extends Path { } - public PathRoute getTail() { - return tail; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int step = !hasNext() || tail.ordersCount == 0 ? 1 : tail.ordersCount; + for (int i = 0; i < ordersCount; i += step) { + Order order = orders.get(i); + if (order == TRANSIT) continue; + if (sb.length() > 0) sb.append(", "); + sb.append(order.getBuy().getItem()); + sb.append(" (").append(order.getBuyer()).append(") "); + } + String o = sb.toString(); + sb = new StringBuilder(); + if (isRoot()){ + sb.append(get()); + if (o.length()>0) sb.append(" (").append(o).append(") "); + } else { + sb.append(getPrevious().toString()); + if (isRefill()) sb.append("(R)"); + if (o.length()>0) sb.append(" (").append(o).append(") "); + sb.append(" -> ").append(get()); + } + return sb.toString(); } - public int getTransitIndex() { - return transitIndex; - } } diff --git a/core/src/test/java/ru/trader/TestUtil.java b/core/src/test/java/ru/trader/TestUtil.java index 789926a..f61590c 100644 --- a/core/src/test/java/ru/trader/TestUtil.java +++ b/core/src/test/java/ru/trader/TestUtil.java @@ -13,7 +13,7 @@ public class TestUtil { assertSize(collection, items); int curIndx=0; for (T actual : collection) { - if (!actual.equals(items[curIndx])){ + if ((actual == null && items[curIndx] != null) || (actual != null && !actual.equals(items[curIndx]))){ Assert.fail(String.format("Entry by index %d is different. Expected: %s Actual: %s", curIndx, items[curIndx], actual)); return; } diff --git a/core/src/test/java/ru/trader/graph/PathRouteTest.java b/core/src/test/java/ru/trader/graph/PathRouteTest.java index a953752..a1eef82 100644 --- a/core/src/test/java/ru/trader/graph/PathRouteTest.java +++ b/core/src/test/java/ru/trader/graph/PathRouteTest.java @@ -2,12 +2,15 @@ package ru.trader.graph; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.trader.TestUtil; import ru.trader.core.*; import java.util.Collection; public class PathRouteTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(PathRouteTest.class); private final static Item ITEM1 = new Item("ITEM1"); private final static Item ITEM2 = new Item("ITEM2"); @@ -19,6 +22,7 @@ public class PathRouteTest extends Assert { private static Vendor v5; private PathRoute initTest1(){ + LOG.info("Init test 1"); v1 = new SimpleVendor("v1"); v2 = new SimpleVendor("v2"); @@ -39,7 +43,8 @@ public class PathRouteTest extends Assert { @Test public void testPathRoute1() throws Exception { - PathRoute path = initTest1().getTail(); + LOG.info("Start path route test 1"); + PathRoute path = initTest1().getNext(); Collection orders = path.getOrders(); Order order1 = new Order(v1.getSell(ITEM1), v2.getBuy(ITEM1), 5); @@ -47,11 +52,11 @@ public class PathRouteTest extends Assert { Order order3 = new Order(v1.getSell(ITEM3), v2.getBuy(ITEM3), 5); assertEquals(1000, path.getProfit(), 0.0001); - assertEquals(3, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order1, order2, order3); + TestUtil.assertCollectionEquals(orders, order1, order2, order3, PathRoute.TRANSIT); } private PathRoute initTest2(){ + LOG.info("Init test 2"); v1 = new SimpleVendor("v1"); v2 = new SimpleVendor("v2"); v3 = new SimpleVendor("v3"); @@ -73,7 +78,8 @@ public class PathRouteTest extends Assert { @Test public void testPathRoute2() throws Exception { - PathRoute path = initTest2().getTail(); + LOG.info("Start path route test 2"); + PathRoute path = initTest2().getNext(); Collection orders = path.getOrders(); Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); @@ -81,18 +87,17 @@ public class PathRouteTest extends Assert { Order order3 = new Order(v1.getSell(ITEM3), v3.getBuy(ITEM3), 5); assertEquals(1000, path.getProfit(), 0.0001); - assertEquals(1, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order1, order3); + TestUtil.assertCollectionEquals(orders, order1, PathRoute.TRANSIT, order3); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(750, path.getProfit(), 0.0001); - assertEquals(1, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order2); + TestUtil.assertCollectionEquals(orders, order2, PathRoute.TRANSIT); } private PathRoute initTest3(){ + LOG.info("Init test 3"); v1 = new SimpleVendor("v1"); v2 = new SimpleVendor("v2"); v3 = new SimpleVendor("v3"); @@ -120,7 +125,8 @@ public class PathRouteTest extends Assert { @Test public void testPathRoute3() throws Exception { - PathRoute path = initTest3().getTail(); + LOG.info("Start path route test 3"); + PathRoute path = initTest3().getNext(); Collection orders = path.getOrders(); Order order1 = new Order(v1.getSell(ITEM1), v3.getBuy(ITEM1), 5); @@ -131,25 +137,23 @@ public class PathRouteTest extends Assert { Order order7 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 5); assertEquals(800, path.getProfit(), 0.0001); - assertEquals(3, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order2, order1, order3); + TestUtil.assertCollectionEquals(orders, order2, order1, order3, PathRoute.TRANSIT); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(650, path.getProfit(), 0.0001); - assertEquals(2, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order5, order4); + TestUtil.assertCollectionEquals(orders, order5, order4, PathRoute.TRANSIT); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(300, path.getProfit(), 0.0001); - assertEquals(1, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order7); + TestUtil.assertCollectionEquals(orders, order7, PathRoute.TRANSIT); } private PathRoute initTest4(){ + LOG.info("Init test 4"); v1 = new SimpleVendor("v1"); v2 = new SimpleVendor("v2"); v3 = new SimpleVendor("v3"); @@ -179,7 +183,8 @@ public class PathRouteTest extends Assert { @Test public void testPathRoute4() throws Exception { - PathRoute path = initTest4().getTail(); + LOG.info("Start path route test 4"); + PathRoute path = initTest4().getNext(); Collection orders = path.getOrders(); Order order1 = new Order(v1.getSell(ITEM1), v2.getBuy(ITEM1), 5); @@ -191,28 +196,24 @@ public class PathRouteTest extends Assert { assertEquals(1000, path.getProfit(), 0.0001); - assertEquals(3, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order3, order1, order4, order2); + TestUtil.assertCollectionEquals(orders, order3, order1, order4, PathRoute.TRANSIT, order2); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(650, path.getProfit(), 0.0001); - assertEquals(1, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order5); + TestUtil.assertCollectionEquals(orders, order5, PathRoute.TRANSIT); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(500, path.getProfit(), 0.0001); - assertEquals(0, path.getTransitIndex()); - assertTrue(orders.isEmpty()); + TestUtil.assertCollectionEquals(orders, PathRoute.TRANSIT); - path = path.getTail(); + path = path.getNext(); orders = path.getOrders(); assertEquals(500, path.getProfit(), 0.0001); - assertEquals(1, path.getTransitIndex()); - TestUtil.assertCollectionEquals(orders, order6); + TestUtil.assertCollectionEquals(orders, order6, PathRoute.TRANSIT); } }