package com.lyndir.omicron.api.util;
import com.google.common.collect.ImmutableSet;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
import com.lyndir.lhunath.opal.system.util.PredicateNN;
import java.util.*;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
public abstract class PathUtils {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( PathUtils.class );
/**
* A breath-first search from root.
*
* @param root The object to start the search from.
* @param foundFunction The function that checks a neighbouring object to see if it's the object we're looking for.
* @param costFunction The function that determines the cost for navigating from a given object to a given neighbouring object.
* @param maxCost The maximum cost of a path. Any paths that cost more than this amount are abandoned.
* @param neighboursFunction The function that determines what an object's direct neighbours are.
* @param <E> The type of objects we're searching.
*
* @return An optional path to the found object, or empty if no path was found (no neighbours left or all paths too expensive).
*/
public static <E, R extends E> Optional<Path<E>> find(final R root, final PredicateNN<E> foundFunction,
final NNFunctionNN<Step<E>, Double> costFunction, final double maxCost,
final NNFunctionNN<E, Stream<? extends E>> neighboursFunction) {
// Test the root.
if (foundFunction.apply( root )) {
logger.trc( "found root: %s", root );
return Optional.of( new Path<>( root, 0 ) );
}
// Initialize breath-first.
Set<E> testedNodes = new HashSet<>();
Deque<Path<E>> testPaths = new LinkedList<>();
testPaths.addLast( new Path<>( root, 0 ) );
testedNodes.add( root );
// Search breath-first.
while (!testPaths.isEmpty()) {
Path<E> testPath = testPaths.removeFirst();
// Check each neighbour.
Iterator<? extends E> neighboursIt = neighboursFunction.apply( testPath.getTarget() ).iterator();
while (neighboursIt.hasNext()) {
E neighbour = neighboursIt.next();
if (!testedNodes.add( neighbour ))
// Neighbour was already tested.
continue;
double neighbourCost = testPath.getCost() + costFunction.apply( new Step<>( testPath.getTarget(), neighbour ) );
if (neighbourCost > maxCost) {
// Stepping to neighbour from here would exceed maximum cost.
logger.trc( "neighbour exceeds maximum cost (%.2f > %.2f): %s", neighbourCost, maxCost, neighbour );
continue;
}
// Did we find the target?
Path<E> neighbourPath = new Path<>( testPath, neighbour, neighbourCost );
if (foundFunction.apply( neighbour )) {
logger.trc( "found neighbour at cost %.2f: %s", neighbourCost, neighbour );
return Optional.of( neighbourPath );
}
logger.trc( "intermediate neighbour at cost %.2f: %s", neighbourCost, neighbour );
// Neighbour is not the target, add it for testing its neighbours later.
testPaths.add( neighbourPath );
}
}
return Optional.empty();
}
/**
* A variation of the breath-first search from root which just enumerates all the objects around root.
*
* @param root The object to start the search from.
* @param radius The maximum distance of an object. Any objects farther removed from the root than the radius are
* abandoned
* and not included.
* @param neighboursFunction The function that determines what an object's direct neighbours are.
* @param <E> The type of objects we're searching.
*
* @return A collection of the object's neighbours.
*/
public static <E> Collection<E> neighbours(final E root, final int radius, final NNFunctionNN<E, Iterable<? extends E>> neighboursFunction) {
if (radius == 0)
return ImmutableSet.of( root );
// Initialize breath-first.
Set<E> neighbours = new HashSet<>();
Deque<Path<E>> testPaths = new LinkedList<>();
testPaths.addLast( new Path<>( root, 0 ) );
neighbours.add( root );
// Search breath-first.
while (!testPaths.isEmpty()) {
Path<E> testPath = testPaths.removeFirst();
// Check each neighbour.
for (final E neighbour : neighboursFunction.apply( testPath.getTarget() )) {
if (!neighbours.add( neighbour ))
// Neighbour was already tested.
continue;
double neighbourDistance = testPath.getCost() + 1;
if (neighbourDistance > radius) {
// Stepping to neighbour from here would exceed maximum cost.
logger.trc( "neighbour exceeds radius (%.2f > %.2f): %s", neighbourDistance, radius, neighbour );
continue;
}
// Add it for testing its neighbours later.
logger.trc( "neighbour at distance %.2f: %s", neighbourDistance, neighbour );
testPaths.add( new Path<>( testPath, neighbour, neighbourDistance ) );
}
}
return neighbours;
}
public static class Path<E> {
private final Optional<Path<E>> parent;
private final E target;
private final double cost;
Path(final E target, final double cost) {
parent = Optional.empty();
this.target = target;
this.cost = cost;
}
Path(@Nonnull final Path<E> parent, final E target, final double cost) {
this.parent = Optional.of( parent );
this.target = target;
this.cost = cost;
}
public Optional<Path<E>> getParent() {
return parent;
}
public double getCost() {
return cost;
}
public E getTarget() {
return target;
}
}
public static class Step<E> {
private final E from;
private final E to;
Step(final E from, final E to) {
this.from = from;
this.to = to;
}
public E getFrom() {
return from;
}
public E getTo() {
return to;
}
}
}