Archived
0

Implement path find

This commit is contained in:
iMoHax
2014-08-15 16:36:37 +04:00
parent dc506b2920
commit 5fcf43b0a0
25 changed files with 1355 additions and 45 deletions

View File

@@ -42,8 +42,6 @@ public interface Market {
void setChange(boolean change);
Collection<Order> getTop(int limit, double balance, long max);
void updateName(Vendor vendor, String name);
void updateName(Item item, String name);

View File

@@ -0,0 +1,87 @@
package ru.trader.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.graph.Graph;
import ru.trader.graph.Path;
import ru.trader.graph.PathRoute;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;
public class MarketAnalyzer {
private final static Logger LOG = LoggerFactory.getLogger(MarketAnalyzer.class);
private Market market;
private Graph<Vendor> graph;
private double stock;
private double maxDistance;
private int jumps;
public MarketAnalyzer(Market market) {
this.market = market;
}
public Collection<Order> getTop(int limit, double balance, long max){
LOG.debug("Get top {}", limit);
TreeSet<Order> top = new TreeSet<>();
for (Vendor vendor : market.get()) {
LOG.trace("Check vendor {}", vendor);
for (Offer sell : vendor.getAllSellOffers()) {
long count = Math.min(max, (long) Math.floor(balance / sell.getPrice()));
LOG.trace("Sell offer {}, count = {}", sell, count);
if (count == 0) continue;
Iterator<Offer> buyers = market.getStatBuy(sell.getItem()).getOffers().descendingIterator();
while (buyers.hasNext()){
Offer buy = buyers.next();
Order order = new Order(sell, buy, count);
LOG.trace("Buy offer {} profit = {}", buy, order.getProfit());
if (order.getProfit() <= 0 ) break;
if (top.size() == limit){
LOG.trace("Min order {}", top.first());
if (top.first().getProfit() < order.getProfit()) {
LOG.trace("Add to top");
top.add(order);
top.pollFirst();
} else {
LOG.trace("Is low profit, skip");
break;
}
} else {
top.add(order);
}
}
}
}
return top;
}
private void rebuild(Vendor source){
graph = new Graph<>(source, market.get(), stock, maxDistance, true, jumps, PathRoute::new);
}
private void setSource(Vendor source){
if (graph == null || !graph.getRoot().equals(source))
rebuild(source);
}
public Collection<Path<Vendor>> getPaths(Vendor from, Vendor to){
setSource(from);
return graph.getPathsTo(to, true);
}
public void setStock(double stock) {
this.stock = stock;
}
public void setMaxDistance(double maxDistance) {
this.maxDistance = maxDistance;
}
public void setJumps(int jumps) {
this.jumps = jumps;
}
}

View File

@@ -169,39 +169,5 @@ public abstract class MarketSupport implements Market {
this.change = change;
}
@Override
public Collection<Order> getTop(int limit, double balance, long max){
LOG.debug("Get top {}", limit);
TreeSet<Order> top = new TreeSet<>();
for (Vendor vendor : getVendors()) {
LOG.trace("Check vendor {}", vendor);
for (Offer sell : vendor.getAllSellOffers()) {
long count = Math.min(max, (long) Math.floor(balance / sell.getPrice()));
LOG.trace("Sell offer {}, count = {}", sell, count);
if (count == 0) continue;
Iterator<Offer> buyers = getStatBuy(sell.getItem()).getOffers().descendingIterator();
while (buyers.hasNext()){
Offer buy = buyers.next();
Order order = new Order(sell, buy, count);
LOG.trace("Buy offer {} profit = {}", buy, order.getProfit());
if (order.getProfit() <= 0 ) break;
if (top.size() == limit){
LOG.trace("Min order {}", top.first());
if (top.first().getProfit() < order.getProfit()) {
LOG.trace("Add to top");
top.add(order);
top.pollFirst();
} else {
LOG.trace("Is low profit, skip");
break;
}
} else {
top.add(order);
}
}
}
}
return top;
}
}

View File

@@ -8,10 +8,18 @@ public class Order implements Comparable<Order> {
private Offer sell;
private Offer buy;
private double profit;
private long count;
public Order(Offer sell, Offer buy) {
this.sell = sell;
this.buy = buy;
this.profit = Double.NaN;
}
public Order(Offer sell, Offer buy, long count) {
this.sell = sell;
this.buy = buy;
this.count = count;
this.profit = (buy.getPrice() - sell.getPrice()) * count;
}
@@ -23,10 +31,31 @@ public class Order implements Comparable<Order> {
return buy;
}
public void setCount(long count){
this.count = count;
this.profit = (buy.getPrice() - sell.getPrice()) * count;
}
public double getProfit(){
return profit;
}
public Order getCopy(long count){
return new Order(sell, buy, count);
}
public long getCount() {
return count;
}
public double getDistance() {
return sell.getVendor().getDistance(buy.getVendor());
}
public boolean isBuyer(Vendor buyer) {
return buy.getVendor().equals(buyer);
}
@Override
public int compareTo(@NotNull Order order) {
Objects.requireNonNull(order, "Not compare with null");
@@ -56,11 +85,8 @@ public class Order implements Comparable<Order> {
@Override
public int hashCode() {
int result;
long temp;
result = sell.hashCode();
result = 31 * result + buy.hashCode();
temp = Double.doubleToLongBits(profit);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@@ -73,4 +99,5 @@ public class Order implements Comparable<Order> {
sb.append('}');
return sb.toString();
}
}

View File

@@ -0,0 +1,25 @@
package ru.trader.core;
import ru.trader.graph.Path;
import java.util.LinkedList;
public class Route {
private double profit;
private double distance;
private final LinkedList<Order> orders = new LinkedList<>();
public Route() {
profit = 0;
distance = 0;
}
public void add(Order order){
orders.add(order);
profit += order.getProfit();
distance += order.getDistance();
}
}

View File

@@ -0,0 +1,52 @@
package ru.trader.core;
public class Ship {
private double balance;
private long cargo;
private double engine;
private int jumps;
public Ship(double balance, long cargo, double engine, int jumps) {
this.balance = balance;
this.cargo = cargo;
this.engine = engine;
this.jumps = jumps;
}
public static Ship copyOf(Ship other){
return new Ship(other.balance, other.cargo, other.engine, other.jumps);
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public long getCargo() {
return cargo;
}
public void setCargo(long cargo) {
this.cargo = cargo;
}
public double getEngine() {
return engine;
}
public void setEngine(double engine) {
this.engine = engine;
}
public int getJumps() {
return jumps;
}
public void setJumps(int jumps) {
this.jumps = jumps;
}
}

View File

@@ -3,6 +3,7 @@ package ru.trader.core;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.graph.Connectable;
import java.util.Collection;
import java.util.Collections;
@@ -10,7 +11,7 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public abstract class Vendor implements Comparable<Vendor> {
public abstract class Vendor implements Comparable<Vendor>, Connectable<Vendor> {
private final static Logger LOG = LoggerFactory.getLogger(Vendor.class);
private String name;
@@ -111,10 +112,16 @@ public abstract class Vendor implements Comparable<Vendor> {
return name != null ? other.name != null ? name.compareTo(other.name) : -1 : 0;
}
@Override
public double getDistance(Vendor other){
return getDistance(other.x, other.y, other.z);
}
@Override
public boolean canRefill() {
return !getAllSellOffers().isEmpty() || !getAllBuyOffers().isEmpty();
}
public double getDistance(double x, double y, double z){
return Math.sqrt(Math.pow(x - this.x, 2) + Math.pow(y-this.y, 2) + Math.pow(z - this.z, 2));
}

View File

@@ -0,0 +1,9 @@
package ru.trader.graph;
public interface Connectable<T> {
public double getDistance(T other);
public boolean canRefill();
}

View File

@@ -0,0 +1,60 @@
package ru.trader.graph;
public class Edge<T extends Connectable<T>> {
protected double length;
protected final Vertex<T> target;
protected final Vertex<T> source;
public Edge(Vertex<T> source, T target) {
this(source, new Vertex<>(target));
}
public Edge(Vertex<T> source, Vertex<T> target) {
this.target = target;
this.source = source;
this.length = source.getEntry().getDistance(target.getEntry());
}
public double getLength(){
return length;
}
public boolean isConnect(T other){
return target.getEntry().equals(other);
}
public boolean isConnect(Vertex<T> target){
return getTarget().equals(target);
}
public Vertex<T> getTarget(){
return target;
}
public Vertex<T> getSource() {
return source;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edge edge = (Edge) o;
return source.equals(edge.source) && target.equals(edge.target);
}
@Override
public int hashCode() {
int result = target.hashCode();
result = 31 * result + source.hashCode();
return result;
}
@Override
public String toString() {
return source.toString() + " -> " + target.toString();
}
}

View File

@@ -0,0 +1,182 @@
package ru.trader.graph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
public class Graph<T extends Connectable<T>> {
@FunctionalInterface
public interface PathConstructor<E extends Connectable<E>> {
Path<E> build(Vertex<E> source);
}
private final static Logger LOG = LoggerFactory.getLogger(Graph.class);
private final Vertex<T> root;
private final HashMap<T,Vertex<T>> vertexes;
private final double stock;
private final double maxDistance;
private final boolean withRefill;
private final PathConstructor<T> pathFabric;
public Graph(T start, Collection<T> set, double stock, int maxDeep) {
this(start, set, stock, stock, false, maxDeep, Path::new);
}
public Graph(T start, Collection<T> set, double stock, double maxDistance, int maxDeep) {
this(start, set, stock, maxDistance, true, maxDeep, Path::new);
}
public Graph(T start, Collection<T> set, double stock, boolean withRefill, int maxDeep) {
this(start, set, stock, stock, withRefill, maxDeep, Path::new);
}
public Graph(T start, Collection<T> set, double stock, boolean withRefill, int maxDeep, PathConstructor<T> pathFabric) {
this(start, set, stock, stock, withRefill, maxDeep, pathFabric);
}
public Graph(T start, Collection<T> set, double stock, double maxDistance, boolean withRefill, int maxDeep, PathConstructor<T> pathFabric) {
this.maxDistance = maxDistance;
this.stock = stock;
this.withRefill = withRefill;
this.pathFabric = pathFabric;
root = new Vertex<>(start);
root.setLevel(maxDeep);
vertexes = new HashMap<>();
vertexes.put(root.getEntry(), root);
buildGraph(root, set, maxDeep-1, stock);
}
private void buildGraph(Vertex<T> vertex, Collection<T> set, int deep, double limit) {
LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep);
for (T entry : set) {
if (entry == vertex.getEntry()) continue;
double distance = vertex.getEntry().getDistance(entry);
if (distance <= this.maxDistance){
if (withRefill && distance > limit && !vertex.getEntry().canRefill()){
LOG.trace("Vertex {} is far away, {}", entry, distance);
continue;
}
Vertex<T> next = vertexes.get(entry);
if (next == null){
LOG.trace("Is new vertex");
next = new Vertex<>(entry);
vertexes.put(entry, next);
}
LOG.trace("Add edge from {} to {}", vertex, next);
Edge<T> edge = new Edge<>(vertex, next);
double nextLimit = withRefill ? limit - edge.getLength(): stock;
if (nextLimit < 0) {
LOG.trace("Refill");
nextLimit = stock - edge.getLength();
}
vertex.addEdge(edge);
// If level >= deep when vertex already added on upper deep
if (next.getLevel() < deep){
next.setLevel(vertex.getLevel()-1);
if (deep > 0){
buildGraph(next, set, deep-1, nextLimit);
}
}
}
}
LOG.trace("End build graph from {} on deep {}", vertex, deep);
}
public boolean isAccessible(T entry){
return vertexes.containsKey(entry);
}
public Vertex<T> getVertex(T entry){
return vertexes.get(entry);
}
public Collection<Path<T>> getPathsTo(T entry, boolean all){
Vertex<T> target = getVertex(entry);
return findPaths(pathFabric.build(root), target, root.getLevel()-1, stock, all);
}
private Collection<Path<T>> findPaths(Path<T> head, Vertex<T> target, int deep, double limit, boolean all){
Collection<Path<T>> paths = new ArrayList<>();
if (target == null) return paths;
boolean found =false;
Vertex<T> source = head.getTarget();
LOG.trace("Find path to deep from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head);
Edge<T> edge = source.getEdge(target);
if (edge != null ){
if (!(withRefill && Math.min(limit, maxDistance) < edge.getLength() && !source.getEntry().canRefill())){
found = true;
Path<T> path = head.connectTo(edge, limit < edge.getLength());
path.finish();
LOG.trace("Last edge find, add path {}", path);
paths.add(path);
}
}
if ((all || !found) && deep > 0 ){
if (source.getEdgesCount() > 0){
LOG.trace("Search around");
for (Edge<T> next : source.getEdges()) {
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
if (head.contains(next)) continue;
// target already added if source consist edge
if (next.isConnect(target)) continue;
Path<T> path = head.connectTo(next, limit < next.getLength());
double nextLimit = withRefill ? limit - next.getLength(): stock;
// refill
if (nextLimit < 0 ) nextLimit = maxDistance - next.getLength();
paths.addAll(findPaths(path, target, deep - 1, nextLimit, all));
if (!all && !paths.isEmpty()) break;
}
}
}
return paths;
}
public Path<T> getFastPathTo(T entry){
Vertex<T> target = getVertex(entry);
return findFastPath(pathFabric.build(root), target, target.getLevel()+1, stock);
}
private Path<T> findFastPath(Path<T> head, Vertex<T> target, int deep, double limit) {
Vertex<T> source = head.getTarget();
LOG.trace("Find fast path from {} to {}, deep {}, limit {}, head {}", source, target, deep, limit, head);
if (deep == source.getLevel()){
for (Edge<T> next : source.getEdges()) {
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
if (head.contains(next)) continue;
if (next.isConnect(target)) {
Path<T> path = head.connectTo(next, limit < next.getLength());
path.finish();
LOG.trace("Last edge find, path {}", path);
return path;
}
}
}
if (deep < source.getLevel()){
LOG.trace("Search around");
for (Edge<T> next : source.getEdges()) {
if (next.getTarget().getLevel() >= source.getLevel()) continue;
if (withRefill && Math.min(limit, maxDistance) < next.getLength() && !source.getEntry().canRefill()) continue;
Path<T> path = head.connectTo(next, limit < next.getLength());
double nextLimit = withRefill ? limit - next.getLength(): stock;
// refill
if (nextLimit < 0 ) nextLimit = stock - next.getLength();
Path<T> res = findFastPath(path, target, deep, nextLimit);
if (res != null) return res;
}
}
return null;
}
public T getRoot() {
return root.getEntry();
}
}

View File

@@ -0,0 +1,95 @@
package ru.trader.graph;
public class Path<T extends Connectable<T>> {
private final Path<T> head;
private final Vertex<T> target;
private boolean refill;
public Path(Vertex<T> source) {
this.head = null;
this.target = source;
this.refill = false;
}
protected Path(Path<T> head, Vertex<T> vertex, boolean refill) {
this.head = head;
this.target = vertex;
this.refill = refill;
}
public Path<T> connectTo(Edge<T> edge, boolean refill){
return new Path<>(this, edge.getTarget(), refill);
}
public void finish(){
finish(target);
}
protected void finish(Vertex<T> target){
if (!isRoot()){
head.finish(target);
if (target != head.target) head.finish();
}
}
public boolean isRoot(){
return head == null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Path)) return false;
Path path = (Path) o;
return (isRoot() ? path.isRoot() : head.equals(path.head)) && target.equals(path.target);
}
@Override
public int hashCode() {
int result = head != null ? head.hashCode() : 0;
result = 31 * result + target.hashCode();
return result;
}
@Override
public String toString(){
if (isRoot()) return target.getEntry().toString();
final StringBuilder sb = new StringBuilder(head.toString());
if (refill) sb.append("(R)");
sb.append(" -> ").append(target.getEntry());
return sb.toString();
}
public boolean contains(Edge<T> edge) {
return target.equals(edge.getTarget()) || (!isRoot() && head.contains(edge));
}
@SafeVarargs
public static <T extends Connectable<T>> Path<T> toPath(T... items){
T s = items[0];
Path<T> path = new Path<>(new Vertex<>(s));
for (int i = 1; i < items.length; i++) {
T t = items[i];
path = new Path<>(path, new Vertex<>(t), false);
s = t;
}
return path;
}
public Vertex<T> getTarget() {
return target;
}
public boolean isRefill(){
return refill;
}
protected Path<T> getHead(){
return head;
}
}

View File

@@ -0,0 +1,96 @@
package ru.trader.graph;
import ru.trader.core.Offer;
import ru.trader.core.Order;
import ru.trader.core.Vendor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
public class PathRoute extends Path<Vendor> {
private final ArrayList<Order> orders = new ArrayList<>();
public PathRoute(Vertex<Vendor> source) {
super(source);
}
protected PathRoute(Path<Vendor> head, Vertex<Vendor> vertex, boolean refill) {
super(head, vertex, refill);
}
@Override
public Path<Vendor> connectTo(Edge<Vendor> edge, boolean refill) {
return new PathRoute(this.getCopy(), edge.getTarget(), refill);
}
@Override
protected void finish(Vertex<Vendor> target) {
if (!isRoot()) {
if (!contains(target.getEntry())){
updateOrders(target.getEntry());
getHead().finish(target);
if (getHead().getTarget() != target) getHead().finish();
}
}
}
private boolean contains(Vendor buyer){
for (Order order : orders) {
if (order.isBuyer(buyer)) return true;
}
return false;
}
private void updateOrders(Vendor buyer){
Vendor seller = getHead().getTarget().getEntry();
for (Offer sell : seller.getAllSellOffers()) {
Offer buy = buyer.getBuy(sell.getItem());
if (buy != null) orders.add(new Order(sell, buy));
}
}
public Collection<Order> getOrders() {
return 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.getBuy().getVendor()).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 Path<Vendor> getCopy(){
Path<Vendor> res;
LinkedList<Path<Vendor>> v = new LinkedList<>();
Path<Vendor> p = this;
while (!p.isRoot()){
v.add(p);
p = p.getHead();
}
res = p;
Iterator<Path<Vendor>> it = v.descendingIterator();
while (it.hasNext()){
p = it.next();
res = new PathRoute(res, p.getTarget(), p.isRefill());
}
return res;
}
}

View File

@@ -0,0 +1,83 @@
package ru.trader.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
public class Vertex<T extends Connectable<T>> {
private final ArrayList<Edge<T>> edges = new ArrayList<>();
private final T entry;
private int level = -1;
public Vertex(T entry) {
this.entry = entry;
}
public T getEntry() {
return entry;
}
public boolean isConnected(Vertex<T> other){
return isConnected(other.entry);
}
public boolean isConnected(T other){
return edges.stream().anyMatch((e) -> e.isConnect(other));
}
public void addEdge(Edge<T> edge){
if (edges.contains(edge)) return;
edges.add(edge);
}
public Collection<Edge<T>> getEdges() {
return edges;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vertex vertex = (Vertex) o;
return entry.equals(vertex.entry);
}
@Override
public int hashCode() {
return entry.hashCode();
}
public void sortEdges(Comparator<Edge> comparator){
edges.sort(comparator);
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getEdgesCount(){
return edges.size();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Vertex{");
sb.append(entry);
sb.append(", lvl=").append(level);
sb.append('}');
return sb.toString();
}
public Edge<T> getEdge(Vertex<T> target) {
for (Edge<T> edge : edges) {
if (edge.isConnect(target)) return edge;
}
return null;
}
}