From 8536e78458ed0683a19b8d3d7346456eb6c4b286 Mon Sep 17 00:00:00 2001 From: iMoHax Date: Wed, 9 Sep 2015 15:27:02 +0300 Subject: [PATCH] implement add offer to route --- .../main/java/ru/trader/analysis/Route.java | 4 + .../java/ru/trader/analysis/RouteEntry.java | 98 +++++++- .../java/ru/trader/analysis/RouteFiller.java | 131 +++++++++++ core/src/main/java/ru/trader/core/Order.java | 6 +- .../src/main/java/ru/trader/core/Profile.java | 22 +- .../ru/trader/store/simple/SimpleOffer.java | 31 +++ .../ru/trader/analysis/RouteFillerTest.java | 216 ++++++++++++++++++ 7 files changed, 499 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/ru/trader/analysis/Route.java b/core/src/main/java/ru/trader/analysis/Route.java index 3ef09b1..f485166 100644 --- a/core/src/main/java/ru/trader/analysis/Route.java +++ b/core/src/main/java/ru/trader/analysis/Route.java @@ -80,6 +80,10 @@ public class Route implements Comparable { return entries.size(); } + public boolean isLoop(){ + return !isEmpty() && entries.get(0).is(entries.get(entries.size()-1).getVendor()); + } + public void add(RouteEntry entry){ LOG.trace("Add entry {} to route {}", entry, this); entries.add(entry); diff --git a/core/src/main/java/ru/trader/analysis/RouteEntry.java b/core/src/main/java/ru/trader/analysis/RouteEntry.java index 36f0db8..d4b6039 100644 --- a/core/src/main/java/ru/trader/analysis/RouteEntry.java +++ b/core/src/main/java/ru/trader/analysis/RouteEntry.java @@ -3,14 +3,13 @@ package ru.trader.analysis; import ru.trader.core.Order; import ru.trader.core.Vendor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public class RouteEntry { private final Vendor vendor; private final double fuel; - private final List orders; + private final List orders; private boolean land; private double refill; private double profit; @@ -74,15 +73,77 @@ public class RouteEntry { } void add(Order order){ - orders.add(order); + orders.add(fixedWrap(order)); } void addAll(Collection orders){ - this.orders.addAll(orders); + orders.forEach(this::add); + } + + void addOrder(Order order){ + orders.add(wrap(order)); + } + + void removeOrder(Order order){ + assert order instanceof OrderWrapper; + orders.remove(order); + } + + void clearTemporal(){ + for (Iterator iterator = orders.iterator(); iterator.hasNext(); ) { + OrderWrapper order = iterator.next(); + if (!order.fixed){ + iterator.remove(); + } + } + orders.forEach(OrderWrapper::reset); } public List getOrders() { - return orders; + return new AbstractList() { + @Override + public Order get(int index) { + return orders.get(index); + } + + @Override + public int size() { + return orders.size(); + } + }; + } + + public List getFixedOrders(){ + return orders.stream().filter(o -> o.fixed).collect(Collectors.toList()); + } + + void reserve(long count){ + List fixedOrders = getFixedOrders(); + fixedOrders.sort((o1, o2) -> Double.compare(o1.getProfitByTonne(), o2.getProfitByTonne())); + for (Order order : fixedOrders) { + long newCount = order.getCount() - count; + if (newCount < 0){ + newCount = 0; + } + count -= order.getCount() - newCount; + order.setCount(newCount); + if (count <= 0) break; + } + } + + void fill(long count){ + List fixedOrders = getFixedOrders(); + fixedOrders.sort((o1, o2) -> Double.compare(o2.getProfitByTonne(), o1.getProfitByTonne())); + for (Order order : fixedOrders) { + long newCount = Math.min(((OrderWrapper)order).max, order.getCount() + count); + count -= order.getCount() - newCount; + order.setCount(newCount); + if (count <= 0) break; + } + } + + public long getCargo(){ + return orders.stream().mapToLong(Order::getCount).sum(); } void clearOrders(){ @@ -137,4 +198,27 @@ public class RouteEntry { public String toString() { return vendor + (isRefill() ? " (R)":""); } + + private OrderWrapper wrap(Order order){ + return new OrderWrapper(order, false); + } + + private OrderWrapper fixedWrap(Order order){ + return new OrderWrapper(order, true); + } + + private class OrderWrapper extends Order { + private final boolean fixed; + private final long max; + + private OrderWrapper(Order order, boolean fixed) { + super(order.getSell(), order.getBuy(), order.getCount()); + this.fixed = fixed; + this.max = order.getCount(); + } + + public void reset(){ + setCount(max); + } + } } diff --git a/core/src/main/java/ru/trader/analysis/RouteFiller.java b/core/src/main/java/ru/trader/analysis/RouteFiller.java index cfa0172..5563558 100644 --- a/core/src/main/java/ru/trader/analysis/RouteFiller.java +++ b/core/src/main/java/ru/trader/analysis/RouteFiller.java @@ -357,4 +357,135 @@ public class RouteFiller { return "{" + bestOrders + "}"; } } + + public static double[] getLostProfits(Route route, int offset, Vendor target, long count, long cargo){ + List entries = route.getEntries(); + int size = entries.size() - (route.isLoop() ? 1 : 0); + double[] res = new double[size]; + for (int i = 0; i < size; i++) { + int index = i + offset; + if (index >= size) index -= size; + RouteEntry entry = entries.get(index); + if (entry.isTransit()) continue; + List orders = new ArrayList<>(entry.getFixedOrders()); + orders.sort((o1, o2) -> Double.compare(o1.getProfitByTonne(), o2.getProfitByTonne())); + long empty = cargo - orders.stream().mapToLong(Order::getCount).sum(); + long need = count - empty; + double profit = 0; + for (Order order : orders) { + if (need > 0){ + long reserved = Math.min(order.getCount(), need); + profit += reserved * order.getProfitByTonne(); + need -= reserved; + } else { + break; + } + } + for (int j = 0; j < size; j++) { + index = i - j + offset; + if (index >= size) index -= size; + if (index < 0) index += size; + entry = entries.get(index); + if (!entry.isTransit() && entry.is(target)) { + break; + } + res[index] += profit; + } + } + return res; + } + + public static void reservedCargo(final Route route, final int offset, Vendor target, long count, long cargo){ + List entries = route.getEntries(); + int size = entries.size() - (route.isLoop() ? 1 : 0); + for (int i = 0; i < size; i++) { + int index = i + offset; + if (index >= size) index -= size; + RouteEntry entry = entries.get(index); + if (entry.isTransit()) continue; + if (entry.is(target)) { + break; + } + long empty = cargo - entry.getCargo(); + long need = count - empty; + if (need > 0){ + entry.reserve(need); + } + } + } + + public static void fillCargo(final Route route, final int offset, Vendor target, long count){ + List entries = route.getEntries(); + int size = entries.size() - (route.isLoop() ? 1 : 0); + for (int i = 0; i < size; i++) { + int index = i + offset; + if (index >= size) index -= size; + RouteEntry entry = entries.get(index); + if (entry.isTransit()) continue; + if (entry.is(target)) { + break; + } + entry.fill(count); + } + } + + public static void removeOrders(final Route route, final Offer buyOffer){ + List entries = route.getEntries(); + int size = entries.size() - (route.isLoop() ? 1 : 0); + for (int i = 0; i < size; i++) { + RouteEntry entry = entries.get(i); + if (entry.isTransit()) continue; + Optional order = entry.getOrders().stream().filter(o -> o.getBuy().equals(buyOffer)).findAny(); + if (order.isPresent()){ + fillCargo(route, i, buyOffer.getVendor(), order.get().getCount()); + entry.removeOrder(order.get()); + } + } + } + + public static void addOrders(final Route route, final int startEntry, final Offer buyOffer, final long cargo){ + final double[] profits = getLostProfits(route, startEntry, buyOffer.getVendor(), buyOffer.getCount(), cargo); + + class SortHelper { + RouteEntry entry; + int index; + Order sell; + + SortHelper(RouteEntry entry, int index) { + this.entry = entry; + this.index = index; + Offer sell = entry.getVendor().getSell(buyOffer.getItem()); + if (sell != null){ + this.sell = new Order(sell, buyOffer, route.getBalance(), buyOffer.getCount()); + } + } + + private double getProfit(){ + return sell != null ? profits[index] - sell.getProfit() : profits[index]; + } + } + + List entries = route.getEntries(); + int size = entries.size()-1; + List sortedEntries = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + RouteEntry entry = entries.get(i); + sortedEntries.add(new SortHelper(entry, i)); + } + sortedEntries.sort((e1, e2) -> Double.compare(e1.getProfit(), e2.getProfit())); + + long need = buyOffer.getCount(); + double balance = route.getBalance(); + for (SortHelper helper : sortedEntries) { + RouteEntry entry = helper.entry; + if (helper.sell != null){ + Order order = new Order(helper.sell.getSell(), buyOffer, balance, need); + reservedCargo(route, helper.index, buyOffer.getVendor(), order.getCount(), cargo); + entry.addOrder(order); + need -= order.getCount(); + if (need <= 0) break; + } + balance += entry.getProfit(); + } + } } diff --git a/core/src/main/java/ru/trader/core/Order.java b/core/src/main/java/ru/trader/core/Order.java index c366bd8..52782c4 100644 --- a/core/src/main/java/ru/trader/core/Order.java +++ b/core/src/main/java/ru/trader/core/Order.java @@ -44,6 +44,10 @@ public class Order implements Comparable { return profit; } + public double getProfitByTonne(){ + return count == 0 ? buy.getPrice() - sell.getPrice() : profit/count; + } + public long getCount() { return count; } @@ -86,7 +90,7 @@ public class Order implements Comparable { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof Order)) return false; Order order = (Order) o; diff --git a/core/src/main/java/ru/trader/core/Profile.java b/core/src/main/java/ru/trader/core/Profile.java index 78ec64e..7138394 100644 --- a/core/src/main/java/ru/trader/core/Profile.java +++ b/core/src/main/java/ru/trader/core/Profile.java @@ -226,5 +226,25 @@ public class Profile { values.setProperty("profile.search.times.recharge", String.valueOf(rechargeTime)); ship.writeTo(values); } - + + public Profile copy(){ + Profile res = new Profile(ship); + res.name = this.name; + res.balance = this.balance; + res.system = this.system; + res.station = this.station; + res.docked = this.docked; + res.jumps = this.jumps; + res.lands = this.lands; + res.refill = this.refill; + res.routesCount = this.routesCount; + res.distanceTime = this.distanceTime; + res.jumpTime = this.jumpTime; + res.landingTime = this.landingTime; + res.takeoffTime = this.takeoffTime; + res.rechargeTime = this.rechargeTime; + res.fuelPrice = this.fuelPrice; + res.pathPriority = this.pathPriority; + return res; + } } diff --git a/core/src/main/java/ru/trader/store/simple/SimpleOffer.java b/core/src/main/java/ru/trader/store/simple/SimpleOffer.java index 9906d4f..0bbf5cf 100644 --- a/core/src/main/java/ru/trader/store/simple/SimpleOffer.java +++ b/core/src/main/java/ru/trader/store/simple/SimpleOffer.java @@ -61,4 +61,35 @@ public class SimpleOffer extends AbstractOffer { this.count = count; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SimpleOffer)) return false; + + SimpleOffer that = (SimpleOffer) o; + + if (count != that.count) return false; + if (Double.compare(that.price, price) != 0) return false; + if (!item.equals(that.item)) return false; + if (type != that.type) return false; + if (!vendor.equals(that.vendor)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = vendor.hashCode(); + result = 31 * result + item.hashCode(); + result = 31 * result + type.hashCode(); + return result; + } + + public static Offer fakeBuy(Vendor buyer, Item item, double price, long count){ + SimpleOffer res = new SimpleOffer(OFFER_TYPE.BUY, item, price, count); + res.setVendor(buyer); + return res; + } + } diff --git a/core/src/test/java/ru/trader/analysis/RouteFillerTest.java b/core/src/test/java/ru/trader/analysis/RouteFillerTest.java index 51e68f7..8d7fb7e 100644 --- a/core/src/test/java/ru/trader/analysis/RouteFillerTest.java +++ b/core/src/test/java/ru/trader/analysis/RouteFillerTest.java @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import ru.trader.TestUtil; import ru.trader.core.*; import ru.trader.store.simple.SimpleMarket; +import ru.trader.store.simple.SimpleOffer; import java.util.List; @@ -471,4 +472,219 @@ public class RouteFillerTest extends Assert { } + @Test + public void testLostProfits() throws Exception { + LOG.info("Start lost profit test"); + Route route = initTest5(); + int cargo = 7; + RouteFiller filler = getFillerInstance(700, cargo, true, market); + filler.fill(route); + + assertEquals(750, route.getProfit(), 0.0001); + assertEquals(3, route.getLands()); + + /* v1 3x100 + 2x20 -> v2 5x50 -> v3 3x60 -> v4 */ + double[] profits = RouteFiller.getLostProfits(route, 0, v4, cargo, cargo); + assertArrayEquals(new double[]{750, 430, 180, 0}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, cargo, cargo); + assertArrayEquals(new double[]{0, 430, 180, 0}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 1, v2, cargo, cargo); + assertArrayEquals(new double[]{320, 0, 500, 320}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 2, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 570}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 570}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, 2, cargo); + assertArrayEquals(new double[]{0, 0, 0, 0}, profits, 0.001); + + /* v1 -2x20 -> v2 -2x50 -> v3 -> v4 */ + profits = RouteFiller.getLostProfits(route, 0, v4, 4, cargo); + assertArrayEquals(new double[]{120, 100, 0, 0}, profits, 0.001); + + /* v1 -1x100 - 2x20 -> v2 -3x50 -> v3 -1x60 -> v4 */ + profits = RouteFiller.getLostProfits(route, 0, v4, 5, cargo); + assertArrayEquals(new double[]{330, 210, 60, 0}, profits, 0.001); + } + + private Route initTest5c(){ + LOG.info("Init test 5c"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + ITEM4 = market.addItem("ITEM4", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v4 = market.addPlace("p4",0,0,0).addVendor("v4"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM4, 40, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM1, 150, 5); + v2.addOffer(OFFER_TYPE.SELL, ITEM3, 320, 5); + v3.addOffer(OFFER_TYPE.SELL, ITEM3, 390, 5); + v4.addOffer(OFFER_TYPE.SELL, ITEM3, 200, 5); + + v1.addOffer(OFFER_TYPE.BUY, ITEM3, 250, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM4, 50, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 200, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 450, -1); + + Route route = new Route(new RouteEntry(v1, 0, 0,0)); + route.add(new RouteEntry(v2, 0, 0,0)); + route.add(new RouteEntry(v3, 0, 0,0)); + route.add(new RouteEntry(v4, 0, 0,0)); + route.add(new RouteEntry(v3, 0, 0,0)); + route.add(new RouteEntry(v1, 0, 0,0)); + + return route; + } + + @Test + public void testProfitsByLoop() throws Exception { + LOG.info("Start profit by loop test"); + Route route = initTest5c(); + int cargo = 7; + RouteFiller filler = getFillerInstance(700, cargo, true, market); + filler.fill(route); + + assertEquals(1000, route.getProfit(), 0.0001); + assertEquals(4, route.getLands()); + + /* v1 3x100 + 2x20 -> v2 5x50 -> v3 3x60 -> v4 5x50 -> v3 transit -> v1 */ + double[] profits = RouteFiller.getLostProfits(route, 0, v4, cargo, cargo); + assertArrayEquals(new double[]{750, 430, 180, 0, 750}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, cargo, cargo); + assertArrayEquals(new double[]{0, 680, 430, 250, 0}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 1, v2, cargo, cargo); + assertArrayEquals(new double[]{320, 0, 750, 570, 320}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 2, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 820, 570}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 820, 570}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, 2, cargo); + assertArrayEquals(new double[]{0, 0, 0, 0, 0}, profits, 0.001); + + } + + private Route initTest5d(){ + LOG.info("Init test 5d"); + market = new SimpleMarket(); + ITEM1 = market.addItem("ITEM1", null); + ITEM2 = market.addItem("ITEM2", null); + ITEM3 = market.addItem("ITEM3", null); + ITEM4 = market.addItem("ITEM4", null); + v1 = market.addPlace("p1",0,0,0).addVendor("v1"); + v2 = market.addPlace("p2",0,0,0).addVendor("v2"); + v3 = market.addPlace("p3",0,0,0).addVendor("v3"); + v4 = market.addPlace("p4",0,0,0).addVendor("v4"); + v1.addOffer(OFFER_TYPE.SELL, ITEM1, 100, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM2, 200, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM3, 300, 5); + v1.addOffer(OFFER_TYPE.SELL, ITEM4, 40, -1); + v2.addOffer(OFFER_TYPE.SELL, ITEM1, 150, 5); + v2.addOffer(OFFER_TYPE.SELL, ITEM3, 320, 5); + v3.addOffer(OFFER_TYPE.SELL, ITEM3, 390, 5); + v4.addOffer(OFFER_TYPE.SELL, ITEM1, 180, 5); + + v1.addOffer(OFFER_TYPE.BUY, ITEM3, 400, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM2, 300, -1); + v2.addOffer(OFFER_TYPE.BUY, ITEM4, 50, -1); + v3.addOffer(OFFER_TYPE.BUY, ITEM1, 200, -1); + v4.addOffer(OFFER_TYPE.BUY, ITEM3, 450, -1); + + Route route = new Route(new RouteEntry(v1, 0, 0,0)); + route.add(new RouteEntry(v2, 0, 0,0)); + route.add(new RouteEntry(v3, 0, 0,0)); + route.add(new RouteEntry(v4, 0, 0,0)); + route.add(new RouteEntry(v3, 0, 0,0)); + route.add(new RouteEntry(v1, 0, 0,0)); + + return route; + } + + @Test + public void testProfitsByLoop2() throws Exception { + LOG.info("Start profit by loop2 test"); + Route route = initTest5d(); + int cargo = 7; + RouteFiller filler = getFillerInstance(700, cargo, true, market); + filler.fill(route); + + assertEquals(880, route.getProfit(), 0.0001); + assertEquals(5, route.getLands()); + + /* v1 3x100 + 2x20 -> v2 5x50 -> v3 3x60 -> v4 5x20 -> v3 3x10 -> v1 */ + double[] profits = RouteFiller.getLostProfits(route, 0, v4, cargo, cargo); + assertArrayEquals(new double[]{750, 430, 180, 0, 780}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, cargo, cargo); + assertArrayEquals(new double[]{0, 560, 310, 130, 30}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 1, v2, cargo, cargo); + assertArrayEquals(new double[]{320, 0, 630, 450, 350}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 2, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 100, 0}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v3, cargo, cargo); + assertArrayEquals(new double[]{570, 250, 0, 100, 0}, profits, 0.001); + + profits = RouteFiller.getLostProfits(route, 0, v1, 2, cargo); + assertArrayEquals(new double[]{0, 0, 0, 0, 0}, profits, 0.001); + + } + + @Test + public void testAddOffer() throws Exception { + LOG.info("Start add offer test"); + Route route = initTest5d(); + int cargo = 7; + RouteFiller filler = getFillerInstance(700, cargo, true, market); + filler.fill(route); + + assertEquals(880, route.getProfit(), 0.0001); + assertEquals(5, route.getLands()); + + /* v1 3x100 + 2x20 -> v2 5x50 -> v3 3x60 -> v4 5x20 -> v3 3x10 -> v1 */ + Offer offer = SimpleOffer.fakeBuy(v2, ITEM3, 210, 3); + RouteFiller.addOrders(route, 1, offer, cargo); + + Order order1 = new Order(v1.getSell(ITEM2), v2.getBuy(ITEM2), 3); + Order order2 = new Order(v1.getSell(ITEM4), v2.getBuy(ITEM4), 1); + Order order3 = new Order(v2.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order4 = new Order(v3.getSell(ITEM3), v4.getBuy(ITEM3), 3); + Order order5 = new Order(v4.getSell(ITEM1), v3.getBuy(ITEM1), 5); + Order order6 = new Order(v3.getSell(ITEM3), v1.getBuy(ITEM3), 3); + Order order7 = new Order(v1.getSell(ITEM3), offer, 2); + Order order8 = new Order(v3.getSell(ITEM3), offer, 1); + + TestUtil.assertCollectionEquals(route.get(0).getOrders(), order1, order2, order7); + TestUtil.assertCollectionEquals(route.get(1).getOrders(), order3); + TestUtil.assertCollectionEquals(route.get(2).getOrders(), order4); + TestUtil.assertCollectionEquals(route.get(3).getOrders(), order5); + TestUtil.assertCollectionEquals(route.get(4).getOrders(), order6, order8); + + order2 = new Order(v1.getSell(ITEM4), v2.getBuy(ITEM4), 2); + + RouteFiller.removeOrders(route, offer); + TestUtil.assertCollectionEquals(route.get(0).getOrders(), order1, order2); + TestUtil.assertCollectionEquals(route.get(1).getOrders(), order3); + TestUtil.assertCollectionEquals(route.get(2).getOrders(), order4); + TestUtil.assertCollectionEquals(route.get(3).getOrders(), order5); + TestUtil.assertCollectionEquals(route.get(4).getOrders(), order6); + } + }