diff --git a/core/src/main/java/ru/trader/analysis/AbstractCrawlerSpecification.java b/core/src/main/java/ru/trader/analysis/AbstractCrawlerSpecification.java index 6ff6e1a..7c0fcf5 100644 --- a/core/src/main/java/ru/trader/analysis/AbstractCrawlerSpecification.java +++ b/core/src/main/java/ru/trader/analysis/AbstractCrawlerSpecification.java @@ -10,10 +10,16 @@ import java.util.function.Consumer; public abstract class AbstractCrawlerSpecification implements CrawlerSpecification { private final RouteSpecification routeSpecification; private final Consumer>> onFoundFunc; + private final boolean loop; - protected AbstractCrawlerSpecification(RouteSpecification routeSpecification, Consumer>> onFoundFunc) { + protected AbstractCrawlerSpecification(RouteSpecification routeSpecification, Consumer>> onFoundFunc, boolean loop) { this.routeSpecification = routeSpecification; this.onFoundFunc = onFoundFunc; + this.loop = loop; + } + + protected boolean isLoop() { + return loop; } @Override diff --git a/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByProfit.java b/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByProfit.java index bffa864..1ab1935 100644 --- a/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByProfit.java +++ b/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByProfit.java @@ -10,28 +10,33 @@ import java.util.function.Consumer; public class CrawlerSpecificationByProfit extends AbstractCrawlerSpecification { public CrawlerSpecificationByProfit(Consumer>> onFoundFunc) { - super(null, onFoundFunc); + super(null, onFoundFunc, false); } - public CrawlerSpecificationByProfit(RouteSpecification routeSpecification, Consumer>> onFoundFunc) { - super(routeSpecification, onFoundFunc); + public CrawlerSpecificationByProfit(RouteSpecification routeSpecification, Consumer>> onFoundFunc, boolean loop) { + super(routeSpecification, onFoundFunc, loop); } @Override public double computeWeight(VendorsCrawler.VendorsEdge edge) { - return edge.getTime()/edge.getProfitByTonne(); + double profit = edge.getProfitByTonne(); + return profit > 0 ? edge.getTime()/profit : edge.getTime(); } @Override public double computeWeight(VendorsCrawler.VendorsTraversalEntry entry) { double profit = 0; double time = 0; Iterator> iterator = entry.routeIterator(); + boolean first = true; while (iterator.hasNext()){ VendorsCrawler.VendorsEdge edge = (VendorsCrawler.VendorsEdge)iterator.next(); if (edge != null){ - profit += edge.getProfitByTonne(); - time += edge.getTime(); + if (!isLoop() || first || iterator.hasNext()){ + profit += edge.getProfitByTonne(); + time += edge.getTime(); + } } + first = false; } return profit > 1 ? time / profit : time; } diff --git a/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByTime.java b/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByTime.java index be6766c..d4ad039 100644 --- a/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByTime.java +++ b/core/src/main/java/ru/trader/analysis/CrawlerSpecificationByTime.java @@ -9,29 +9,35 @@ import java.util.function.Consumer; public class CrawlerSpecificationByTime extends AbstractCrawlerSpecification { public CrawlerSpecificationByTime(Consumer>> onFoundFunc) { - super(null, onFoundFunc); + super(null, onFoundFunc, false); } - public CrawlerSpecificationByTime(RouteSpecification routeSpecification, Consumer>> onFoundFunc) { - super(routeSpecification, onFoundFunc); + public CrawlerSpecificationByTime(RouteSpecification routeSpecification, Consumer>> onFoundFunc, boolean loop) { + super(routeSpecification, onFoundFunc, loop); } @Override public double computeWeight(VendorsCrawler.VendorsEdge edge) { - return edge.getTime(); + double profit = edge.getProfitByTonne(); + return profit > 0 ? edge.getTime() + 1/profit : edge.getTime(); } @Override public double computeWeight(VendorsCrawler.VendorsTraversalEntry entry) { - double time = 0; + double profit = 0; long time = 0; Iterator> iterator = entry.routeIterator(); + boolean first = true; while (iterator.hasNext()){ VendorsCrawler.VendorsEdge edge = (VendorsCrawler.VendorsEdge)iterator.next(); if (edge != null){ - time += edge.getTime(); + if (!isLoop() || first || iterator.hasNext()){ + profit += edge.getProfitByTonne(); + time += edge.getTime(); + } } + first = false; } - return time; + return profit > 0 ? time + 1/profit : time + 0.999999999999999d; } } diff --git a/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java b/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java index db55a15..18cf88a 100644 --- a/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java +++ b/core/src/main/java/ru/trader/analysis/CrawlerSpecificator.java @@ -64,7 +64,7 @@ public class CrawlerSpecificator { this.offers.addAll(offers); } - public CrawlerSpecification build(Consumer>> onFoundFunc, RouteSpecification andSpec){ + public CrawlerSpecification build(Consumer>> onFoundFunc, RouteSpecification andSpec, boolean loop){ RouteSpecification spec; RouteSpecification res = null; if (!all.isEmpty()){ @@ -95,13 +95,13 @@ public class CrawlerSpecificator { } } if (byTime){ - return new CrawlerSpecificationByTime(res, onFoundFunc); + return new CrawlerSpecificationByTime(res, onFoundFunc, loop); } - return new CrawlerSpecificationByProfit(res, onFoundFunc); + return new CrawlerSpecificationByProfit(res, onFoundFunc, loop); } public CrawlerSpecification build(Consumer>> onFoundFunc){ - return build(onFoundFunc, null); + return build(onFoundFunc, null, false); } } diff --git a/core/src/main/java/ru/trader/analysis/RouteSearcher.java b/core/src/main/java/ru/trader/analysis/RouteSearcher.java index 98f0120..54e52f0 100644 --- a/core/src/main/java/ru/trader/analysis/RouteSearcher.java +++ b/core/src/main/java/ru/trader/analysis/RouteSearcher.java @@ -114,13 +114,19 @@ public class RouteSearcher { vGraph.build(source, vendors); LOG.trace("Graph is builds"); RouteCollector collector = new RouteCollector(); - Crawler crawler = vGraph.crawler(specificator.build(collector::add, new LoopRouteSpecification<>(true)), callback); + Crawler crawler = vGraph.crawler(specificator.build(collector::add, new LoopRouteSpecification<>(true), true), callback); crawler.setMaxSize(scorer.getProfile().getLands()); crawler.findMin(source, count); - crawler = vGraph.crawler(specificator.build(collector::add, new RouteSpecificationByTarget<>(source)), callback); + crawler = vGraph.crawler(specificator.build(collector::add, new RouteSpecificationByTarget<>(source), false), callback); crawler.setMaxSize(scorer.getProfile().getLands()); crawler.findMin(source, 1); - return collector.get(); + List routes = collector.get(); + routes.sort((r1, r2) -> { + double s1 = (r1.getProfit() - r1.getEntries().get(0).getProfit()) / r1.getTime(); + double s2 = (r2.getProfit() - r2.getEntries().get(0).getProfit()) / r2.getTime(); + return Double.compare(s2, s1); + }); + return routes; } private class RouteCollector { diff --git a/core/src/main/java/ru/trader/analysis/VendorsCrawler.java b/core/src/main/java/ru/trader/analysis/VendorsCrawler.java index 025e0ce..f5ba9c1 100644 --- a/core/src/main/java/ru/trader/analysis/VendorsCrawler.java +++ b/core/src/main/java/ru/trader/analysis/VendorsCrawler.java @@ -6,16 +6,17 @@ import ru.trader.core.Order; import ru.trader.core.Vendor; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; public class VendorsCrawler extends Crawler { private double startFuel; private double startBalance; + private final CrawlerSpecification specification; public VendorsCrawler(VendorsGraph graph, CrawlerSpecification specification, AnalysisCallBack callback) { super(graph, specification.routeSpecification(), specification.onFoundFunc(), callback); + this.specification = specification; startFuel = graph.getProfile().getShip().getTank(); startBalance = graph.getProfile().getBalance(); } @@ -83,16 +84,7 @@ public class VendorsCrawler extends Crawler { @Override public double getWeight() { if (weight == null){ - double profit = 0; double time = 0; - Iterator> iterator = routeIterator(); - while (iterator.hasNext()){ - VendorsEdge edge = (VendorsEdge)iterator.next(); - if (edge != null){ - profit += edge.getProfitByTonne(); - time += edge.getTime(); - } - } - weight = profit > 1 ? time / profit : time; + weight = specification.computeWeight(this); } return weight; } @@ -191,7 +183,7 @@ public class VendorsCrawler extends Crawler { @Override protected double computeWeight() { - return getTime()/getProfitByTonne(); + return specification.computeWeight(this); } @Override diff --git a/core/src/main/java/ru/trader/analysis/VendorsGraph.java b/core/src/main/java/ru/trader/analysis/VendorsGraph.java index 4408e36..d90d041 100644 --- a/core/src/main/java/ru/trader/analysis/VendorsGraph.java +++ b/core/src/main/java/ru/trader/analysis/VendorsGraph.java @@ -28,18 +28,10 @@ public class VendorsGraph extends ConnectibleGraph { return new VendorsCrawler(this, new CrawlerSpecificationByProfit(onFoundFunc), callback); } - public VendorsCrawler crawler(RouteSpecification specification, Consumer>> onFoundFunc, AnalysisCallBack callback){ - return new VendorsCrawler(this, new CrawlerSpecificationByProfit(specification, onFoundFunc), callback); - } - public VendorsCrawler crawlerByTime(Consumer>> onFoundFunc, AnalysisCallBack callback){ return new VendorsCrawler(this, new CrawlerSpecificationByTime(onFoundFunc), callback); } - public VendorsCrawler crawlerByTime(RouteSpecification specification, Consumer>> onFoundFunc, AnalysisCallBack callback){ - return new VendorsCrawler(this, new CrawlerSpecificationByTime(specification, onFoundFunc), callback); - } - public VendorsCrawler crawler(CrawlerSpecification specification, AnalysisCallBack callback){ return new VendorsCrawler(this, specification, callback); } diff --git a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java index 1c68a47..094d973 100644 --- a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java +++ b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java @@ -9,6 +9,8 @@ import ru.trader.core.*; import ru.trader.store.simple.Store; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -25,6 +27,7 @@ public class RouteSearcherTest extends Assert{ private Place dnDraconis; private Place aulin; private Place cmDraco; + private Place aulis; @Before public void setUp() throws Exception { @@ -37,6 +40,7 @@ public class RouteSearcherTest extends Assert{ dnDraconis = world.get("DN Draconis"); aulin = world.get("Aulin"); cmDraco = world.get("CM Draco"); + aulis = world.get("Aulis"); MarketFilter filter = new MarketFilter(); fWorld = new FilteredMarket(world, filter); @@ -78,7 +82,8 @@ public class RouteSearcherTest extends Assert{ assertEquals(2, route.getLands()); assertEquals(72.42, route.getDistance(), 0.01); - List apaths = searcher.getRoutes(ithaca_st, ithaca_st, fWorld.getMarkets(true).collect(Collectors.toList())); + Collection world = fWorld.getMarkets(true).collect(Collectors.toList()); + List apaths = searcher.getRoutes(ithaca_st, ithaca_st, world); /* List apaths = searcher.getRoutes(ithaca_st, ithaca_st, Arrays.asList(ithaca_st, lhs3262_st, morgor_st, lhs3006_st, ithaca.asTransit(), lhs3262.asTransit(), morgor.asTransit(), lhs3006.asTransit())); @@ -106,9 +111,84 @@ public class RouteSearcherTest extends Assert{ assertEquals(4, route.getLands()); assertEquals(109.51, route.getDistance(), 0.01); - apaths = searcher.getRoutes(ithaca_st, ithaca_st, fWorld.getMarkets(true).collect(Collectors.toList())); + apaths = searcher.getRoutes(ithaca_st, ithaca_st, world); actual = apaths.stream().findFirst().get(); assertEquals(route, actual); } + + @Test + public void testRoutesByTime() throws Exception { + // Balance: 6000000, cargo: 440, tank: 40, distance: 13.4, jumps: 6 + // Ithaca (Palladium to Aulis) -> Aulis (Consumer Technology to Ithaca) -> Aulis + // Profit: 231093.9, time: 476, distance: 17.36, lands: 2 + Vendor ithaca_st = ithaca.get().iterator().next(); + Vendor aulis_st = aulis.get().iterator().next(); + Ship ship = new Ship(); + ship.setCargo(440); ship.setTank(15); + ship.setEngine(5, 'A'); ship.setMass(466); + Profile profile = new Profile(ship); + profile.setBalance(6000000); profile.setJumps(6); + profile.setRoutesCount(100); profile.setLands(3); + Scorer scorer = new Scorer(fWorld, profile); + + LOG.info("Start search by times test"); + RouteSearcher searcher = new RouteSearcher(scorer); + + Route route = new Route(new RouteEntry(ithaca_st, 0, 1.73439683972718d, 0)); + route.add(new RouteEntry(aulis_st, 0, 1.726405672421771d, 0)); + route.add(new RouteEntry(ithaca_st, 0, 0, 0)); + RouteFiller filler = new RouteFiller(scorer); + filler.fill(route); + + assertEquals(231093.9, route.getProfit(), 0.1); + assertEquals(476, route.getTime(), 0.1); + assertEquals(2, route.getLands()); + assertEquals(17.36, route.getDistance(), 0.01); + + CrawlerSpecificator specificator = new CrawlerSpecificator(); + specificator.setByTime(true); + Collection world = fWorld.getMarkets(true).collect(Collectors.toList()); + List apaths = searcher.search(ithaca_st, ithaca_st, world, profile.getRoutesCount(), specificator); + Route actual = apaths.stream().findFirst().get(); + assertEquals("Routes is different", route, actual); + } + + @Test + public void testLoopRoutes() throws Exception { + // Balance: 6000000, cargo: 440, tank: 40, distance: 13.4, jumps: 6 + Ship ship = new Ship(); + ship.setCargo(440); ship.setTank(15); + ship.setEngine(5, 'A'); ship.setMass(466); + Profile profile = new Profile(ship); + profile.setBalance(6000000); profile.setJumps(6); + profile.setRoutesCount(100); profile.setLands(3); + Scorer scorer = new Scorer(fWorld, profile); + + LOG.info("Start loop search "); + RouteSearcher searcher = new RouteSearcher(scorer); + Vendor ithaca_st = ithaca.get().iterator().next(); + Collection world = fWorld.getMarkets(true).collect(Collectors.toList()); + + List apaths = searcher.getLoops(ithaca_st, world, profile.getRoutesCount()); + assertEquals(39, apaths.size()); + Collection vendors = new ArrayList<>(40); + apaths.forEach(route -> { + List entries = route.getEntries(); + Vendor v = entries.get(entries.size()-1).getVendor(); + assertFalse(vendors.contains(v)); + vendors.add(v); +/* List p = searcher.getRoutes(v, v, world, 1); + double profit = route.getProfit() - route.get(0).getProfit(); + long time = route.getTime() - route.get(0).getFullTime(); + double score = profit/time; + Route oRoute = p.get(0); + LOG.info("{} - loop", v); + LOG.info("profit: {}, time: {}, score: {} ", profit, time, score); + LOG.info("profit: {}, time: {}, score: {} ", oRoute.getProfit(), oRoute.getTime(), oRoute.getScore()); + assertTrue(score <= oRoute.getScore());*/ + }); + assertTrue(vendors.contains(ithaca_st)); + } + } diff --git a/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java b/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java index a10e870..6fe17a4 100644 --- a/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java +++ b/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java @@ -228,7 +228,7 @@ public class VendorsGraphTest extends Assert { vGraph.build(cabreraDock, fWorld.getMarkets(true).collect(Collectors.toList())); LOG.info("Search"); SimpleCollector paths = new SimpleCollector<>(); - Crawler crawler = vGraph.crawler(new LoopRouteSpecification<>(true), paths::add, new AnalysisCallBack()); + Crawler crawler = vGraph.crawler(new CrawlerSpecificationByProfit(new LoopRouteSpecification<>(true), paths::add, true), new AnalysisCallBack()); crawler.findMin(cabreraDock, 100); assertEquals(60, paths.get().size()); Collection vendors = new ArrayList<>(60);