Archived
0

new more fasted route finder

This commit is contained in:
iMoHax
2015-07-13 15:20:09 +03:00
parent 6e0fe05576
commit 12716b7d3e
14 changed files with 801 additions and 201 deletions

View File

@@ -2,22 +2,20 @@ package ru.trader.analysis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.analysis.graph.ConnectibleEdge;
import ru.trader.analysis.graph.Crawler;
import ru.trader.analysis.graph.Edge;
import ru.trader.core.Order;
import ru.trader.core.TransitVendor;
import ru.trader.core.Vendor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
public class RouteSearcher {
private final static Logger LOG = LoggerFactory.getLogger(RouteSearcher.class);
private final VendorsGraph vGraph;
private final Scorer scorer;
public RouteSearcher(Scorer scorer) {
vGraph = new VendorsGraph(scorer);
this.scorer = scorer;
}
public List<Route> getRoutes(Vendor from, Vendor to, Collection<Vendor> vendors){
@@ -30,10 +28,13 @@ public class RouteSearcher {
private List<Route> search(Vendor source, Vendor target, Collection<Vendor> vendors){
LOG.trace("Start search route to {} from {}", source, target);
VendorsGraph vGraph = new VendorsGraph(scorer);
LOG.trace("Build vendors graph");
vGraph.build(source, vendors);
LOG.trace("Graph is builds");
RouteCollector collector = new RouteCollector();
Crawler<Vendor> crawler = vGraph.crawler(collector::add);
crawler.setMaxSize(scorer.getProfile().getLands());
if (target == null){
int count = vGraph.getProfile().getRoutesCount() / vendors.size();
for (Vendor vendor : vendors) {
@@ -49,10 +50,11 @@ public class RouteSearcher {
private class RouteCollector {
private List<Route> routes = new ArrayList<>();
public void add(List<Edge<Vendor>> edges){
public boolean add(List<Edge<Vendor>> edges){
Route route = toRoute(edges);
route.setBalance(vGraph.getProfile().getBalance());
route.setBalance(scorer.getProfile().getBalance());
routes.add(route);
return true;
}
public List<Route> get() {
@@ -62,34 +64,31 @@ public class RouteSearcher {
private Route toRoute(List<Edge<Vendor>> edges){
List<RouteEntry> entries = new ArrayList<>(edges.size()+1);
Vendor buyer = null;
VendorsGraph.VendorsEdge edge = null;
for (int i = 0; i < edges.size(); i++) {
edge = (VendorsGraph.VendorsEdge) edges.get(i);
Vendor vendor = edge.getSource().getEntry();
RouteEntry entry = new RouteEntry(vendor, edge.isRefill(), edge.getFuel(), edge.getWeight());
if (buyer != null && vendor.equals(buyer)){
entry.setLand(true);
}
List<Order> orders = edge.getOrders();
if (!orders.isEmpty()){
buyer = orders.get(0).getBuyer();
if (vendor instanceof TransitVendor){
Vendor seller = orders.get(0).getSell().getVendor();
for (int j = i-1; j >= 0; j--) {
RouteEntry sEntry = entries.get(j);
if (sEntry.is(seller)){
sEntry.addAll(orders);
break;
}
}
} else {
entry.addAll(orders);
VendorsGraph.VendorsEdge vEdge = null;
for (Edge<Vendor> e : edges) {
vEdge = (VendorsGraph.VendorsEdge) e;
List<ConnectibleEdge<Vendor>> transitEdges = vEdge.getPath().getEntries();
for (int k = 0; k < transitEdges.size(); k++) {
ConnectibleEdge<Vendor> edge = transitEdges.get(k);
Vendor vendor = edge.getSource().getEntry();
RouteEntry entry = new RouteEntry(vendor, edge.isRefill(), edge.getFuelCost(), 0);
if (buyer != null && vendor.equals(buyer)) {
entry.setLand(true);
buyer = null;
}
if (k == 0) {
entry.setScore(vEdge.getWeight());
List<Order> orders = vEdge.getOrders();
if (!orders.isEmpty()) {
buyer = orders.get(0).getBuyer();
entry.addAll(orders);
}
}
entries.add(entry);
}
entries.add(entry);
}
if (edge != null) {
RouteEntry entry = new RouteEntry(edge.getTarget().getEntry(), false, 0, 0);
if (vEdge != null) {
RouteEntry entry = new RouteEntry(vEdge.getTarget().getEntry(), false, 0, 0);
if (buyer != null) entry.setLand(true);
entries.add(entry);
}

View File

@@ -76,10 +76,6 @@ public class Scorer {
return avgDistance;
}
public double getFuel(double distance){
return profile.getShip().getFuelCost(distance);
}
public double getScore(RouteEntry entry, int jumps) {
int lands = entry.isLand() ? 1 : 0;
return getScore(entry.getVendor(), entry.getProfit(), jumps, lands, entry.getFuel());

View File

@@ -0,0 +1,112 @@
package ru.trader.analysis;
import ru.trader.analysis.graph.ConnectibleEdge;
import ru.trader.analysis.graph.ConnectibleGraph;
import ru.trader.analysis.graph.Path;
import ru.trader.core.Vendor;
import java.util.ArrayList;
import java.util.List;
public class TransitPath {
private final List<ConnectibleEdge<Vendor>> entries;
private double fuelCost;
private double remain;
private int refillCount;
public TransitPath(Path<Vendor> path, double fuel) {
List<ConnectibleGraph<Vendor>.BuildEdge> edges = path.getEdges();
entries = new ArrayList<>(edges.size());
createEdges(edges, fuel);
}
private void createEdges(List<ConnectibleGraph<Vendor>.BuildEdge> edges, double fuel) {
fuelCost = 0; refillCount = 0;
for (int i = edges.size() - 1; i >= 0; i--) {
ConnectibleGraph<Vendor>.BuildEdge edge = edges.get(i);
ConnectibleEdge<Vendor> cEdge = new ConnectibleEdge<>(edge.getSource(), edge.getTarget());
double fuelCost = edge.getFuelCost(fuel);
this.fuelCost += fuelCost;
cEdge.setFuelCost(fuelCost);
entries.add(cEdge);
if (fuel < 0 || fuel < edge.getMinFuel()){
refillCount++;
fuel = refill(edges, entries.size()-1);
} else {
fuel -= fuelCost;
}
}
remain = fuel;
}
private double refill(List<ConnectibleGraph<Vendor>.BuildEdge> edges, int startIndex){
double max = -1;
for (int i = startIndex; i >= 0; i--) {
ConnectibleGraph<Vendor>.BuildEdge e = edges.get(edges.size()-1-i);
if (max != -1){
max += e.getFuelCost(max + e.getMinFuel());
}
Vendor source = e.getSource().getEntry();
if (source.canRefill()){
ConnectibleEdge<Vendor> ce = entries.get(i);
if (ce.isRefill()){
throw new IllegalStateException("Is not exists path");
}
double remain = max != -1 ? Math.min(max, e.getRefill()) : e.getRefill();
double fuelCost = e.getFuelCost(remain);
this.fuelCost += fuelCost - ce.getFuelCost();
ce.setFuelCost(fuelCost);
ce.setRefill(remain);
remain = updateFuelCost(edges, i+1, startIndex, remain-fuelCost);
if (remain < 0){
continue;
}
return remain;
}
if (max == -1 || e.getMaxFuel() < max){
max = e.getMaxFuel();
}
}
throw new IllegalStateException("Is not exists path");
}
private double updateFuelCost(List<ConnectibleGraph<Vendor>.BuildEdge> edges, int startIndex, int endIndex, double fuel){
for (int i = startIndex+1; i <= endIndex; i++) {
ConnectibleGraph<Vendor>.BuildEdge e = edges.get(edges.size()-1-i);
ConnectibleEdge<Vendor> ce = entries.get(i);
double fuelCost = e.getFuelCost(fuel);
this.fuelCost = fuelCost - ce.getFuelCost();
ce.setFuelCost(fuelCost);
fuel -= fuelCost;
if (fuel < 0 || fuel < e.getMinFuel()){
return -1;
}
}
return fuel;
}
public List<ConnectibleEdge<Vendor>> getEntries() {
return entries;
}
public double getFuelCost() {
return fuelCost;
}
public double getRemain() {
return remain;
}
public int getRefillCount() {
return refillCount;
}
public boolean isRefill() {
return entries.get(0).isRefill();
}
public int size() {
return entries.size();
}
}

View File

@@ -0,0 +1,32 @@
package ru.trader.analysis;
import ru.trader.analysis.graph.Edge;
import ru.trader.analysis.graph.Path;
import ru.trader.analysis.graph.Vertex;
import ru.trader.core.Vendor;
import java.util.Collection;
import java.util.Optional;
public class VendorVertex extends Vertex<Vendor> {
public VendorVertex(Vendor entry, int index) {
super(entry, index);
}
@Override
public synchronized void connect(Edge<Vendor> edge) {
VendorsGraph.VendorsBuildEdge vEdge = (VendorsGraph.VendorsBuildEdge) edge;
Optional<Edge<Vendor>> old = getEdge(edge.getTarget());
if (old.isPresent()){
VendorsGraph.VendorsBuildEdge oEdge = (VendorsGraph.VendorsBuildEdge) old.get();
if (oEdge.getPaths() == null) return;
Collection<Path<Vendor>> paths = vEdge.getPaths();
for (Path<Vendor> path : paths) {
oEdge.add(path);
}
} else {
super.connect(edge);
}
}
}

View File

@@ -3,21 +3,21 @@ package ru.trader.analysis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.analysis.graph.*;
import ru.trader.core.Order;
import ru.trader.core.TransitVendor;
import ru.trader.core.Vendor;
import ru.trader.core.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.*;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.function.Function;
import java.util.stream.Collectors;
public class VendorsGraph extends ConnectibleGraph<Vendor> {
private final static Logger LOG = LoggerFactory.getLogger(VendorsGraph.class);
private final static int THRESHOLD = 8;
private final Scorer scorer;
private final List<VendorsGraphBuilder> deferredTasks = new ArrayList<>();
public VendorsGraph(Scorer scorer) {
super(scorer.getProfile());
@@ -29,18 +29,118 @@ public class VendorsGraph extends ConnectibleGraph<Vendor> {
this.scorer = scorer;
}
public VendorsCrawler crawler(Consumer<List<Edge<Vendor>>> onFoundFunc){
public VendorsCrawler crawler(Function<List<Edge<Vendor>>, Boolean> onFoundFunc){
return new VendorsCrawler(onFoundFunc);
}
@Override
protected Vertex<Vendor> newInstance(Vendor entry, int index) {
return new VendorVertex(entry, index);
}
@Override
protected GraphBuilder createGraphBuilder(Vertex<Vendor> vertex, Collection<Vendor> set, int deep, double limit) {
return new VendorsGraphBuilder(vertex, set, deep, limit);
}
protected class VendorsGraphBuilder extends ConnectibleGraphBuilder {
@Override
protected void onEnd() {
super.onEnd();
runDeferredTasks();
updateVertexes();
}
protected void holdTask(VendorsGraphBuilder task){
synchronized (deferredTasks){
deferredTasks.add(task);
}
}
private void runDeferredTasks(){
deferredTasks.sort((b1,b2) -> Integer.compare(b2.getDeep(), b1.getDeep()));
for (VendorsGraphBuilder task : deferredTasks) {
ForkJoinTask.invokeAll(task);
}
deferredTasks.clear();
}
private void updateVertexes(){
vertexes.removeIf(v -> v.getEntry() instanceof TransitVendor);
updateLevels(root);
}
private void updateLevels(Vertex<Vendor> vertex){
vertex.getEdges().forEach(e -> {
Vertex<Vendor> target = e.getTarget();
if (target.getLevel()+1 <vertex.getLevel()){
target.setLevel(vertex.getLevel()-1);
updateLevels(target);
}
});
}
private class VendorsGraphBuilder extends ConnectibleGraphBuilder {
private final ArrayList<RecursiveAction> subTasks = new ArrayList<>(THRESHOLD);
private final VendorsGraphBuilder head;
private final BuildEdge edge;
private boolean isAdding;
protected VendorsGraphBuilder(Vertex<Vendor> vertex, Collection<Vendor> set, int deep, double limit) {
super(vertex, set, deep, limit);
this.head = null;
this.edge = null;
}
private VendorsGraphBuilder(VendorsGraphBuilder head, BuildEdge edge, Collection<Vendor> set, int deep, double limit) {
super(edge.getTarget(), set, deep, limit);
this.head = head;
this.edge = edge;
}
public int getDeep(){
return deep;
}
@Override
protected void compute() {
LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep);
if (isAdding){
addAlreadyCheckedEdges();
} else {
checkVertex();
}
if (!subTasks.isEmpty()){
joinSubTasks();
}
LOG.trace("End build graph from {} on deep {}", vertex, deep);
}
private void checkVertex(){
Iterator<Vendor> iterator = set.iterator();
while (iterator.hasNext()) {
if (callback.isCancel()) break;
Vendor entry = iterator.next();
LOG.trace("Check {}", entry);
if (entry == vertex.getEntry()) continue;
double nextLimit = onConnect(entry);
if (nextLimit >= 0) {
LOG.trace("Connect {} to {}", entry, vertex);
Vertex<Vendor> next = getInstance(entry, 0, deep);
BuildEdge e;
if (entry instanceof TransitVendor){
e = super.createEdge(next);
} else {
e = createEdge(next);
vertex.connect(e);
}
addSubTask(e, nextLimit);
} else {
LOG.trace("Vertex {} is far away", entry);
}
if (subTasks.size() == THRESHOLD || !iterator.hasNext()){
joinSubTasks();
}
}
}
@Override
@@ -48,23 +148,241 @@ public class VendorsGraph extends ConnectibleGraph<Vendor> {
double nextlimit = super.onConnect(buyer);
Vendor seller = vertex.getEntry();
if (nextlimit > 0){
if (buyer instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1;
if (seller instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1;
if (buyer instanceof TransitVendor && (deep == 0 || seller.getPlace().equals(buyer.getPlace()))){
LOG.trace("Buyer is transit of seller or is end, skipping");
nextlimit = -1;
}
if (seller instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())){
LOG.trace("Seller is transit of buyer, skipping");
nextlimit = -1;
}
}
return nextlimit;
}
@Override
protected ConnectibleEdge<Vendor> createEdge(Vertex<Vendor> target) {
return new VendorsEdge(vertex, target, refill, fuelCost);
protected VendorsBuildEdge createEdge(Vertex<Vendor> target) {
BuildEdge cEdge = super.createEdge(target);
if (vertex.getEntry() instanceof TransitVendor){
addEdgesToHead(cEdge);
}
return new VendorsBuildEdge(cEdge);
}
private void addEdgesToHead(BuildEdge lastEdge){
Vertex<Vendor> target = lastEdge.getTarget();
assert vertex.getEntry() instanceof TransitVendor && !(target.getEntry() instanceof TransitVendor);
VendorsGraphBuilder h = this;
Path<Vendor> path = new Path<>(Collections.singleton(lastEdge));
while (h != null){
BuildEdge cEdge = h.edge;
Vertex<Vendor> source = cEdge.getSource();
if (source.equals(vertex)){
LOG.trace("Found loop, break");
break;
}
path = path.add(cEdge);
if (!source.equals(target)){
addEdge(source, target, path);
}
if (!(source.getEntry() instanceof TransitVendor)){
break;
}
h = h.head;
}
}
private void addEdge(Vertex<Vendor> source, Vertex<Vendor> target, Path<Vendor> path){
LOG.trace("Is not transit, add edge to {}", source);
VendorsBuildEdge vEdge = new VendorsBuildEdge(source, target, path);
source.connect(vEdge);
}
private void addAlreadyCheckedEdges(){
LOG.trace("Adding already checked vertex");
vertex.getEdges().parallelStream().forEach(aEdge -> {
VendorsBuildEdge e = (VendorsBuildEdge) aEdge;
if (callback.isCancel()) return;
Vendor entry = e.getTarget().getEntry();
LOG.trace("Check {}", entry);
if (limit >= e.getMinFuel() && limit <= e.getMaxFuel()) {
LOG.trace("Connect {} to {}", entry, vertex);
if (vertex.getEntry() instanceof TransitVendor && !(entry instanceof TransitVendor)) {
addCheckedEdgesToHead(e);
}
}
});
}
private void addCheckedEdgesToHead(VendorsBuildEdge lastEdge){
Vertex<Vendor> target = lastEdge.getTarget();
assert vertex.getEntry() instanceof TransitVendor && !(target.getEntry() instanceof TransitVendor);
List<Path<Vendor>> paths = lastEdge.paths;
int i = 1;
Path<Vendor> path = paths != null ? paths.get(0) : new Path<>(Collections.singleton((BuildEdge)lastEdge));
while (path != null){
VendorsGraphBuilder h = this;
while (h != null){
if (h.limit >= path.getMinFuel() && h.limit <= path.getMaxFuel()){
BuildEdge cEdge = h.edge;
Vertex<Vendor> source = cEdge.getSource();
if (source.equals(vertex)){
LOG.trace("Found loop, break");
break;
}
path = path.add(cEdge);
if (!source.equals(target)){
addEdge(source, target, path);
}
if (!(source.getEntry() instanceof TransitVendor)){
break;
}
h = h.head;
} else {
break;
}
}
if (paths == null || i >= paths.size()){
path = null;
} else {
path = paths.get(i++);
}
}
}
private void addSubTask(BuildEdge e, double nextLimit){
Vertex<Vendor> next = e.getTarget();
// If level > deep when vertex already added on upper deep
if (next.getLevel() < deep || next.getEntry() instanceof TransitVendor) {
boolean adding = next.getLevel() >= deep;
if (!adding){
next.setLevel(vertex.getLevel() - 1);
}
if (deep > 0 || adding) {
//Recursive build
VendorsGraphBuilder task = new VendorsGraphBuilder(this, e, set, deep - 1, nextLimit);
task.isAdding = adding;
if (adding){
holdTask(task);
} else {
task.fork();
subTasks.add(task);
}
}
} else {
LOG.trace("Vertex {} already check", next);
}
}
private void joinSubTasks(){
for (RecursiveAction subTask : subTasks) {
if (callback.isCancel()){
subTask.cancel(true);
} else {
subTask.join();
}
}
subTasks.clear();
}
}
public class VendorsBuildEdge extends BuildEdge {
private List<Path<Vendor>> paths = new ArrayList<>();
private List<Order> orders;
protected VendorsBuildEdge(Vertex<Vendor> source, Vertex<Vendor> target, Path<Vendor> path) {
super(source, target);
if (path == null) throw new IllegalArgumentException("Path must be no-null");
paths.add(path);
update(path);
}
protected VendorsBuildEdge(BuildEdge edge) {
super(edge.getSource(), edge.getTarget());
Path<Vendor> path = new Path<>(Collections.singleton(edge));
paths.add(path);
update(path);
}
private void update(Path<Vendor> path){
setFuel(path.getMinFuel(), path.getMaxFuel());
}
protected void setOrders(List<Order> orders){
this.orders = orders;
}
public List<Order> getOrders(){
if (orders == null){
Vendor seller = source.getEntry();
Vendor buyer = target.getEntry();
orders = MarketUtils.getOrders(seller, buyer);
}
return orders;
}
public double getProfit(){
return getOrders().stream().mapToDouble(Order::getProfit).sum();
}
public Collection<Path<Vendor>> getPaths() {
return paths;
}
private Path<Vendor> getPath(double fuel){
Path<Vendor> res = null;
for (Path<Vendor> p : paths) {
if (fuel >= p.getMinFuel() && fuel <= p.getMaxFuel() || getSource().getEntry().canRefill()) {
if (getProfile().getPathPriority().equals(Profile.PATH_PRIORITY.FAST)) {
if (res == null || p.getSize() < res.getSize() && p.getRefillCount(fuel) <= res.getRefillCount(fuel)) {
res = p;
}
} else {
if (res == null || p.getFuelCost() < res.getFuelCost()) {
res = p;
}
}
}
}
return res;
}
public void add(Path<Vendor> path) {
for (Iterator<Path<Vendor>> iterator = paths.iterator(); iterator.hasNext(); ) {
Path<Vendor> p = iterator.next();
if (p.getSize() >= path.getSize() && p.getMinFuel() >= path.getMinFuel() && p.getRefillCount() >= path.getRefillCount()) {
iterator.remove();
} else {
if (path.getSize() >= p.getSize() && path.getMinFuel() >= p.getMinFuel() && path.getRefillCount() >= p.getRefillCount()) {
return;
}
}
}
paths.add(path);
if (getMinFuel() > path.getMinFuel()) {
update(path);
}
}
public VendorsEdge getInstance(double fuel, double balance){
Path<Vendor> path = getPath(fuel);
if (path == null) return null;
VendorsEdge res = new VendorsEdge(source, target, new TransitPath(path,fuel));
res.setOrders(MarketUtils.getStack(getOrders(), balance, getShip().getCargo()));
return res;
}
}
public class VendorsEdge extends ConnectibleEdge<Vendor> {
private TransitPath path;
private List<Order> orders;
protected VendorsEdge(Vertex<Vendor> source, Vertex<Vendor> target, boolean refill, double fuel) {
super(source, target, refill, fuel);
protected VendorsEdge(Vertex<Vendor> source, Vertex<Vendor> target, TransitPath path) {
super(source, target);
if (path == null) throw new IllegalArgumentException("Path must be no-null");
this.path = path;
}
protected void setOrders(List<Order> orders){
@@ -84,100 +402,118 @@ public class VendorsGraph extends ConnectibleGraph<Vendor> {
return orders;
}
public double getRemain() {
return path.getRemain();
}
public boolean isRefill() {
return path.isRefill();
}
public TransitPath getPath() {
return path;
}
@Override
protected double computeWeight() {
int jumps = source.getEntry().getPlace().equals(target.getEntry().getPlace())? 0 : 1;
int lands = !(target.getEntry() instanceof TransitVendor) ? 1 : 0;
boolean transit = lands == 0 && source.getEntry() instanceof TransitVendor || target.getEntry() instanceof TransitVendor;
int lands = 1; double fuel = fuelCost;
if (path != null){
jumps = path.size(); fuel = getFuelCost();
lands += path.getRefillCount();
}
double profit = getProfit();
double score = transit ? scorer.getTransitScore(fuel) :
scorer.getScore(target.getEntry(), profit, jumps, lands, fuel);
double score = scorer.getScore(target.getEntry(), profit, jumps, lands, fuel);
score = scorer.getMaxScore() - score;
if (score < 0)
score = 0;
if (score < 0) score = 0;
return score;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof VendorsEdge)) return false;
if (!super.equals(o)) return false;
VendorsEdge edge = (VendorsEdge) o;
return !(path != null ? !path.equals(edge.path) : edge.path != null);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
}
public class VendorsCrawler extends CCrawler<Vendor> {
protected VendorsCrawler(Consumer<List<Edge<Vendor>>> onFoundFunc) {
public class VendorsCrawler extends Crawler<Vendor> {
private double startFuel;
private double startBalance;
protected VendorsCrawler(Function<List<Edge<Vendor>>, Boolean> onFoundFunc) {
super(VendorsGraph.this, onFoundFunc);
startFuel = getShip().getTank();
startBalance = getProfile().getBalance();
}
protected VendorsCrawler(Function<Edge<Vendor>, Boolean> isFoundFunc, Function<List<Edge<Vendor>>, Boolean> onFoundFunc) {
super(VendorsGraph.this, isFoundFunc, onFoundFunc);
startFuel = getShip().getTank();
startBalance = getProfile().getBalance();
}
public void setStartFuel(double startFuel) {
this.startFuel = startFuel;
}
public void setStartBalance(double startBalance) {
this.startBalance = startBalance;
}
@Override
protected VendorsTraversalEntry start(Vertex<Vendor> vertex) {
double balance = getProfile().getBalance();
return new VendorsTraversalEntry(super.start(vertex), balance);
return new VendorsTraversalEntry(super.start(vertex), startFuel, startBalance);
}
@Override
protected VendorsTraversalEntry travers(final CostTraversalEntry entry, final Edge<Vendor> edge, final Vendor target) {
protected VendorsTraversalEntry travers(final CostTraversalEntry entry, final Edge<Vendor> edge) {
VendorsTraversalEntry vEntry = (VendorsTraversalEntry)entry;
VendorsEdge vEdge = (VendorsEdge) edge;
CCostTraversalEntry ce = super.travers(entry, edge, target);
return new VendorsTraversalEntry((VendorsTraversalEntry) entry, edge, ce.getFuel(), ((VendorsTraversalEntry)entry).balance + vEdge.getProfit());
return new VendorsTraversalEntry(vEntry, vEdge);
}
protected class VendorsTraversalEntry extends CCostTraversalEntry {
protected class VendorsTraversalEntry extends CostTraversalEntry {
private final double fuel;
private final double balance;
protected VendorsTraversalEntry(CCostTraversalEntry entry, double balance) {
super(entry.getTarget(), entry.getFuel());
protected VendorsTraversalEntry(CostTraversalEntry entry, double fuel, double balance) {
super(entry.getTarget());
this.fuel = fuel;
this.balance = balance;
}
protected VendorsTraversalEntry(VendorsTraversalEntry head, Edge<Vendor> edge, double fuel, double balance) {
super(head, edge, fuel);
this.balance = balance;
protected VendorsTraversalEntry(VendorsTraversalEntry head, VendorsEdge edge) {
super(head, edge);
this.balance = head.balance + edge.getProfit();
this.fuel = edge.getRemain();
}
@Override
protected boolean check(Edge<Vendor> e) {
boolean good = super.check(e);
// remove transit cicles
if (good && e.getSource().getEntry() instanceof TransitVendor && !(e.getTarget().getEntry() instanceof TransitVendor)){
Optional<Vendor> seller = getSeller();
good = seller.isPresent() && !e.getTarget().isEntry(seller.get());
}
return good;
public List<Edge<Vendor>> collect(Collection<Edge<Vendor>> src) {
return src.stream().filter(this::check).map(this::wrap).filter(e -> e != null).collect(Collectors.toList());
}
@Override
protected VendorsEdge wrap(Edge<Vendor> edge) {
ConnectibleEdge<Vendor> cEdge = super.wrap(edge);
Vendor buyer = edge.getTarget().getEntry();
List<Order> orders = new ArrayList<>();
orders.addAll(((VendorsEdge) edge).getOrders());
if (edge.getSource().getEntry() instanceof TransitVendor && !(buyer instanceof TransitVendor)){
LOG.trace("{} is transit, search seller", edge.getSource().getEntry());
Optional<Vendor> seller = getSeller();
if (seller.isPresent()){
orders = MarketUtils.getOrders(seller.get(), buyer);
}
}
orders = MarketUtils.getStack(orders, balance, getShip().getCargo());
VendorsEdge res = new VendorsEdge(edge.getSource(), edge.getTarget(), cEdge.isRefill(), cEdge.getFuel());
res.setOrders(orders);
return res;
protected boolean check(Edge<Vendor> e){
VendorsBuildEdge edge = (VendorsBuildEdge) e;
return fuel <= edge.getMaxFuel() && (fuel >= edge.getMinFuel() || edge.getSource().getEntry().canRefill()) && (edge.getProfit() > 0 || isFound(edge));
}
private Optional<Vendor> getSeller(){
Vendor res = null;
Edge<Vendor> e = getEdge();
Vendor seller = e.getSource().getEntry();
if (!(seller instanceof TransitVendor)){
res = seller;
} else {
for (int i = head.size() - 1; i >= 0; i--) {
e = head.getEdge();
seller = e.getSource().getEntry();
if (!(seller instanceof TransitVendor)){
res = seller;
break;
}
}
}
return Optional.ofNullable(res);
protected VendorsEdge wrap(Edge<Vendor> e) {
VendorsBuildEdge edge = (VendorsBuildEdge) e;
return edge.getInstance(fuel, balance);
}
}

View File

@@ -18,7 +18,6 @@ public abstract class AbstractGraph<T> implements Graph<T> {
protected Vertex<T> root;
protected final List<Vertex<T>> vertexes;
protected final GraphCallBack callback;
protected int minJumps;
protected AbstractGraph() {
@@ -37,17 +36,26 @@ public abstract class AbstractGraph<T> implements Graph<T> {
minJumps = 1;
root = getInstance(start, maxDeep, maxDeep);
POOL.invoke(createGraphBuilder(root, set, maxDeep - 1, limit));
onEnd();
callback.endBuild();
}
private Vertex<T> getInstance(T entry, int level, int deep){
protected void onEnd(){
}
protected Vertex<T> newInstance(T entry, int index){
return new Vertex<>(entry, index);
}
protected Vertex<T> getInstance(T entry, int level, int deep){
Vertex<T> vertex = getVertex(entry).orElse(null);
if (vertex == null) {
synchronized (vertexes){
vertex = getVertex(entry).orElse(null);
if (vertex == null){
LOG.trace("Is new vertex");
vertex = new Vertex<>(entry, vertexes.size());
vertex = newInstance(entry, vertexes.size());
vertex.setLevel(level);
vertexes.add(vertex);
int jumps = root != null ? root.getLevel() - deep : 0;

View File

@@ -15,12 +15,12 @@ public class CCrawler<T extends Connectable<T>> extends Crawler<T> {
private final static Logger LOG = LoggerFactory.getLogger(CCrawler.class);
private double startFuel;
public CCrawler(Graph<T> graph, Function<List<Edge<T>>, Boolean> onFoundFunc) {
public CCrawler(ConnectibleGraph<T> graph, Function<List<Edge<T>>, Boolean> onFoundFunc) {
super(graph, onFoundFunc);
startFuel = getShip().getTank();
}
public CCrawler(Graph<T> graph, Function<Edge<T>, Boolean> isFoundFunc, Function<List<Edge<T>>, Boolean> onFoundFunc) {
public CCrawler(ConnectibleGraph<T> graph, Function<Edge<T>, Boolean> isFoundFunc, Function<List<Edge<T>>, Boolean> onFoundFunc) {
super(graph, isFoundFunc, onFoundFunc);
startFuel = getShip().getTank();
}
@@ -78,22 +78,23 @@ public class CCrawler<T extends Connectable<T>> extends Crawler<T> {
@Override
public List<Edge<T>> collect(Collection<Edge<T>> src) {
return src.stream().filter(this::check).map(this::wrap).collect(Collectors.toList());
return src.stream().filter(this::check).map(this::wrap).filter(e -> e != null).collect(Collectors.toList());
}
protected boolean check(Edge<T> e){
ConnectibleEdge<T> edge = (ConnectibleEdge<T>) e;
return edge.getFuelCost() <= fuel || edge.getSource().getEntry().canRefill();
ConnectibleGraph<T>.BuildEdge edge = (ConnectibleGraph<T>.BuildEdge) e;
return fuel <= edge.getMaxFuel() && (fuel >= edge.getMinFuel() || edge.getSource().getEntry().canRefill());
}
protected ConnectibleEdge<T> wrap(Edge<T> edge){
protected ConnectibleEdge<T> wrap(Edge<T> e){
ConnectibleGraph<T>.BuildEdge edge = (ConnectibleGraph<T>.BuildEdge) e;
T source = edge.source.getEntry();
double distance = source.getDistance(edge.target.getEntry());
double fuelCost = getShip().getFuelCost(fuel, distance);
double nextLimit = getProfile().withRefill() ? fuel - fuelCost : getShip().getTank();
double refill = nextLimit < 0 && source.canRefill() ? getShip().getRoundMaxFuel(distance) : 0;
double fuelCost = edge.getFuelCost(fuel);
double nextLimit = fuel - fuelCost;
if (nextLimit < 0 && !source.canRefill()) return null;
double refill = nextLimit < 0 ? edge.getRefill() : 0;
if (refill > 0) {
fuelCost = getShip().getFuelCost(refill, distance);
fuelCost = edge.getFuelCost(refill);
}
ConnectibleEdge<T> cEdge = new ConnectibleEdge<>(edge.getSource(), edge.getTarget());
cEdge.setRefill(refill);

View File

@@ -8,7 +8,6 @@ import ru.trader.core.Ship;
import ru.trader.graph.Connectable;
import java.util.Collection;
import java.util.function.Predicate;
public class ConnectibleGraph<T extends Connectable<T>> extends AbstractGraph<T> {
private final static Logger LOG = LoggerFactory.getLogger(ConnectibleGraph.class);
@@ -42,58 +41,95 @@ public class ConnectibleGraph<T extends Connectable<T>> extends AbstractGraph<T>
super.build(start, set, profile.getJumps(), getShip().getTank());
}
private class DistanceFilter implements Predicate<Double> {
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 <= getShip().getJumpRange(limit) || (profile.withRefill() && distance <= getShip().getMaxJumpRange() && source.canRefill());
}
}
protected class ConnectibleGraphBuilder extends GraphBuilder {
private final DistanceFilter distanceFilter;
protected double refill;
protected double fuelCost;
protected double minFuel;
protected double maxFuel;
protected double distance;
protected ConnectibleGraphBuilder(Vertex<T> vertex, Collection<T> set, int deep, double limit) {
super(vertex, set, deep, limit);
distanceFilter = new DistanceFilter(limit, vertex.getEntry());
}
@Override
protected double onConnect(T entry) {
double distance = vertex.getEntry().getDistance(entry);
if (!distanceFilter.test(distance)){
distance = vertex.getEntry().getDistance(entry);
if (distance > getShip().getMaxJumpRange()){
LOG.trace("Vertex {} is far away, {}", entry, distance);
return -1;
}
fuelCost = getShip().getFuelCost(limit, distance);
double nextLimit = profile.withRefill() ? limit - fuelCost : getShip().getTank();
if (nextLimit < 0 && vertex.getEntry().canRefill()) {
LOG.trace("Refill");
refill = getShip().getRoundMaxFuel(distance);
fuelCost = getShip().getFuelCost(refill, distance);
nextLimit = refill - fuelCost;
} else {
refill = 0;
}
return nextLimit;
maxFuel = getShip().getMaxFuel(distance);
minFuel = getShip().getMinFuel(distance);
double fuel = getProfile().withRefill() ? vertex.getEntry().canRefill() ? getShip().getRoundMaxFuel(distance) : limit : getShip().getTank();
double fuelCost = getShip().getFuelCost(fuel, distance);
return fuel - fuelCost;
}
@Override
protected ConnectibleEdge<T> createEdge(Vertex<T> target) {
ConnectibleEdge<T> res = new ConnectibleEdge<>(vertex, target);
res.setRefill(refill);
res.setFuelCost(fuelCost);
protected BuildEdge createEdge(Vertex<T> target) {
BuildEdge res = new BuildEdge(vertex, target);
res.setFuel(minFuel, maxFuel);
res.setDistance(distance);
return res;
}
}
public class BuildEdge extends Edge<T> {
private double distance;
private double minFuel;
private double maxFuel;
public BuildEdge(Vertex<T> source, Vertex<T> target) {
super(source, target);
}
public double getMinFuel() {
return minFuel;
}
public double getMaxFuel() {
return maxFuel;
}
public double getRoundMaxFuel() {
return getShip().getRoundFuel(maxFuel);
}
public void setFuel(double minFuel, double maxFuel) {
this.minFuel = minFuel;
this.maxFuel = maxFuel;
}
public void setDistance(double distance) {
this.distance = distance;
}
public double getDistance() {
return distance;
}
public double getFuelCost(double fuel){
return getProfile().withRefill() ? getShip().getFuelCost(fuel, distance) : 0;
}
public double getRefill(){
return getShip().getRoundMaxFuel(distance);
}
public Ship getShip(){
return ConnectibleGraph.this.getShip();
}
@Override
protected double computeWeight() {
return distance;
}
@Override
public String toString() {
return source.getEntry().toString() + " - "+ weight
+"("+minFuel + " - " + maxFuel + ")"
+" -> " + target.getEntry().toString();
}
}
}

View File

@@ -1,24 +1,112 @@
package ru.trader.analysis.graph;
import ru.trader.core.Ship;
import ru.trader.graph.Connectable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class Path<T extends Connectable<T>> {
private final List<PathEntry<T>> entries;
private final List<ConnectibleGraph<T>.BuildEdge> entries;
private double minFuel;
private double maxFuel;
private double fuelCost;
private int refillCount;
public Path(List<ConnectibleEdge<T>> edges) {
entries = new ArrayList<>(edges.size());
for (int i = 0; i < edges.size(); i++) {
ConnectibleEdge<T> edge = edges.get(i);
if (i==0) entries.add(new PathEntry<>(edge.getSource().getEntry(), false));
entries.add(new PathEntry<>(edge.getTarget().getEntry(), edge.isRefill()));
public Path(Collection<ConnectibleGraph<T>.BuildEdge> edges) {
entries = new ArrayList<>(edges);
updateStat();
}
private void updateStat(){
Ship ship = entries.get(0).getShip();
double fuel = ship.getTank();
minFuel = 0; maxFuel = 0; fuelCost = 0; refillCount = 0;
boolean adding = true;
for (int i = entries.size() - 1; i >= 0; i--) {
ConnectibleGraph<T>.BuildEdge edge = entries.get(i);
if (i < entries.size() - 1 && edge.getSource().getEntry().canRefill()){
adding = false;
fuel = edge.getMaxFuel();
}
if (fuel < 0 || fuel < edge.getMinFuel()){
minFuel = ship.getTank()+1;
}
double cost = edge.getFuelCost(fuel);
fuelCost += cost;
fuel -= cost;
if (adding) {
minFuel += edge.getMinFuel();
}
}
maxFuel = -1;
for (ConnectibleGraph<T>.BuildEdge edge : entries) {
if (maxFuel != -1){
maxFuel += edge.getFuelCost(maxFuel + edge.getMinFuel());
}
if (maxFuel == -1 || edge.getMaxFuel() < maxFuel){
maxFuel = edge.getMaxFuel();
}
}
refillCount = getRefillCount(ship.getTank());
}
public PathEntry<T> get(int index){
return entries.get(index);
public List<ConnectibleGraph<T>.BuildEdge> getEdges() {
return entries;
}
public double getFuelCost() {
return fuelCost;
}
public double getMinFuel() {
return minFuel;
}
public double getMaxFuel() {
return maxFuel;
}
public int getSize(){
return entries.size();
}
public int getRefillCount() {
return refillCount;
}
public int getRefillCount(double fuel){
int res = 0;
for (int i = entries.size() - 1; i >= 0; i--) {
ConnectibleGraph<T>.BuildEdge edge = entries.get(i);
fuel -= edge.getFuelCost(fuel);
if (fuel < 0) {
res++;
fuel = edge.getMaxFuel();
}
}
return res;
}
public Path<T> add(ConnectibleGraph<T>.BuildEdge edge){
Path<T> res = new Path<>(entries);
res.entries.add(edge);
res.updateStat();
return res;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
for (int i = entries.size() - 1; i >= 0; i--) {
ConnectibleGraph<T>.BuildEdge entry = entries.get(i);
sb.append(entry);
if (i>0)
sb.append(", ");
}
sb.append('}');
return sb.toString();
}
}

View File

@@ -1,19 +0,0 @@
package ru.trader.analysis.graph;
public class PathEntry<T> {
private final T entry;
private boolean refill;
public PathEntry(T entry, boolean refill) {
this.entry = entry;
this.refill = refill;
}
public T getEntry() {
return entry;
}
public boolean isRefill() {
return refill;
}
}

View File

@@ -1,6 +1,7 @@
package ru.trader.core;
public class Profile {
public static enum PATH_PRIORITY {FAST, ECO}
private double balance;
private int jumps;
@@ -14,6 +15,7 @@ public class Profile {
private double jumpMult;
private double landMult;
private double fuelPrice;
private PATH_PRIORITY pathPriority;
public Profile(Ship ship) {
this.ship = ship;
@@ -25,6 +27,7 @@ public class Profile {
landMult = 0.95;
fuelPrice = 100;
jumpMult = 0.5;
pathPriority = PATH_PRIORITY.ECO;
}
public double getBalance() {
@@ -114,4 +117,12 @@ public class Profile {
public void setFuelPrice(double fuelPrice) {
this.fuelPrice = fuelPrice;
}
public PATH_PRIORITY getPathPriority() {
return pathPriority;
}
public void setPathPriority(PATH_PRIORITY pathPriority) {
this.pathPriority = pathPriority;
}
}

View File

@@ -93,11 +93,6 @@ public class TransitVendor implements Vendor {
return d == 0 ? 0 : d > 0 ? 1 : -1;
}
@Override
public boolean canRefill() {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -78,6 +78,10 @@ public class RouteSearcherTest extends Assert{
assertEquals(route.getDistance(), 72.42, 0.01);
List<Route> apaths = searcher.getRoutes(ithaca_st, ithaca_st, fWorld.getMarkets(true).collect(Collectors.toList()));
/* List<Route> 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()));
*/
Route actual = apaths.stream().findFirst().get();
assertEquals("Routes is different", route, actual);

View File

@@ -6,6 +6,7 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.analysis.graph.ConnectibleGraph;
import ru.trader.analysis.graph.Crawler;
import ru.trader.analysis.graph.SimpleCollector;
import ru.trader.analysis.graph.Vertex;
@@ -58,10 +59,10 @@ public class VendorsGraphTest extends Assert {
LOG.info("Build vendors graph");
VendorsGraph vGraph = new VendorsGraph(scorer);
vGraph.build(cabreraDock, fWorld.getMarkets(true).collect(Collectors.toList()));
LOG.info("Search");
SimpleCollector<Vendor> paths = new SimpleCollector<>();
Crawler<Vendor> crawler = vGraph.crawler(paths::add);
// Cabrera Dock -> Transit Wolf 1323 -> Transit Wolf 1325 -> Quimper Ring -> Transit Bhadaba -> Transit Wolf 1325 -> Cabrera Dock]
crawler.findMin(cabreraDock, 100);
assertEquals(100, paths.get().size());
paths.clear();