A*-Implementierung flexibler machen

Kar

Mitglied
Hallo,

ich habe wie folgt den A*-Algorithmus implementiert:
Java:
package astar;

import java.awt.Point;
import java.util.*;

public abstract class AStar {
	public static interface World {
		public List<Node> getNeighbours(Node node);
	}

	public static interface CostsCalculator {
		public int getMovementCosts(Node end);
		public int getEstimatedCosts(Node start, Node end);
	}

	public static CostsCalculator costsCalculator;
	public static Comparator<Node> costsComparator;

	public static Stack<Point> getPath(World world, Node start, Node end) {
		List<Node> openList = new ArrayList<Node>();
		List<Node> closedList = new ArrayList<Node>();
		openList.add(start);
		while(!closedList.contains(end) || openList.isEmpty()) {
			Collections.sort(openList, costsComparator);
			start = openList.get(0);
			openList.remove(start);
			closedList.add(start);
			for(Node neighbour : world.getNeighbours(start)) {
				if(!closedList.contains(neighbour)) {
					neighbour.previousNode = start;
					neighbour.calculateCosts(costsCalculator, end);
					if(!openList.contains(neighbour))
						openList.add(neighbour);
				}
			}
		}
		Stack<Point> path = new Stack<Point>();
		Node node = closedList.get(closedList.indexOf(end));
		while(node.previousNode != null) {
			path.push(node);
			node = node.previousNode;
		}
		return path;
	}

	static {
		costsCalculator = new CostsCalculator() {
			@Override
			public int getMovementCosts(Node end) {
				int costs = 10;
				if(end.previousNode != null)
					costs += end.getCosts(Node.Costs.MOVEMENT);
				return costs;
			}

			@Override
			public int getEstimatedCosts(Node start, Node end) {
				return (Math.abs(start.x - end.x) 
						+ Math.abs(start.y - end.y)) * 10;
			}
		};

		costsComparator = new Comparator<Node>() {
			@Override
			public int compare(Node a, Node b) {
				if(a.getCosts(Node.Costs.ALL) < b.getCosts(Node.Costs.ALL)) 
					return -1;
				else if(a.getCosts(Node.Costs.ALL) > b.getCosts(Node.Costs.ALL))
					return 1;
				return 0;
			}
		};
	}

	static class Node extends Point {
		public static enum Costs {
			ALL, MOVEMENT, ESTIMATED
		}

		public Node previousNode;

		private int movementCosts;
		private int estimatedCosts;

		public Node(int x, int y) {
			super(x, y);
			previousNode = null;
			movementCosts = 0;
			estimatedCosts = 0;
		}

		public void calculateCosts(CostsCalculator c, Node node) {
			movementCosts = c.getMovementCosts(node);
			estimatedCosts = c.getEstimatedCosts(this, node);
		}

		public int getCosts(Costs costs) {
			switch(costs) {
			case ALL:
				return movementCosts + estimatedCosts;
			case MOVEMENT:
				return movementCosts;
			case ESTIMATED:
				return estimatedCosts;
			default:
				return -1;
			}
		}
	}
}

Zum Algorithmus an sich habe ich keine Frage. Der funktioniert soweit super.
Mir geht es darum, dass ich diese Implementierung flexibel halten möchte, sodass er öfter einsetzbar ist. Das habe ich durch die Schnittstellen World, CostsCalculator und Comparator<Node> versucht. Für die letzteren beiden wird eine Defaultimplementierung mitgeliefert.

Geht es auch anders? Es gibt keine Methoden- bzw. Funktionszeiger in Java soweit ich weiß und mit den Schnittstellen wollte ich mir einen Workaround basteln. Allerdings finde ich das nicht sehr sauber.
 
S

SlaterB

Gast
Comparator<Node> extern finde ich unnötig, Node selber kann doch Comparable sein,
dann sieht es für Subklassen schlecht aus mit eigener Sortierung, aber das sollte auch kaum nötig sein,
Sortierung nach Kosten Hauptkriterium bleiben,

die Kostenberechnung ermöglicht die privaten Kosten-Attribute, durchaus alles machbar, theoretisch auch in die Klasse hineinzuziehen,
World mit getNeighbours() passt

für mich eigentlich nichts zu meckern, als ich das Posting angefangen hatte hatte ich noch bisschen mehr im Sinn,
durch CostsCalculator aber doch kein Fehler ;)

hier noch ein Link zu ähnlichem, aber ich glaube nicht so flexibel wie deins, nicht genau angeschaut
http://www.java-forum.org/spiele-multimedia-programmierung/140117-pfadfindung.html
 

Marco13

Top Contributor
Das sieht (beim Überfliegen) schon sehr allgemein aus. Ich bin mir nicht sicher, ob man es vielleicht eleganter machen könnte, aber da müßte man genauer drüber nachdenken. Als erstes würde ich überlegen, ob man "Node" nicht auch als Interface machen könnte...

Nur hier muss ich nachhaken:

Comparator<Node> extern finde ich unnötig, Node selber kann doch Comparable sein,

Ich finde, es gibt nur SEHR wenige Fälle, wo man eine Implementierung von "Comparable" vorschreiben sollte. Ganz allgemein ist es ja so, dass man für Comparable-Objekte leicht einen generischen "ComparableComparator" schreiben kann (und dass es sowas in der Standard-API nicht gibt, obwohl es an einigen Stellen viel Code und Arbeit hätte sparen können, finde ich ziemlich :autsch: ). Umgekehrt, wenn davon ausgegangen wird, dass die übergebenen Objekte Comparable sind, hat man keinen Einfluß mehr auf die interne Arbeitsweise.

Hier dachte ich als konkretes Beispiel zuerst daran, dass vielleicht jemand nicht die euklidische, sondern z.B. die Manhattan-Distanz verwenden wollen könnte, aber aber das geht hier ja im "CostsCalculator" unter - deswegen ist das nicht direkt anwendbar (solange man die Node-Klasse als solche beibehalten will...)
 

Kar

Mitglied
Vielen Dank für die Antworten.

Der Gedanke um den Comparator<Node> war der, dass ich dem Anwender ermöglichen wollte, nicht nur den kostengünstigen Pfad, sondern auch - aus welchem Grund auch immer - den kostenintensivsten Pfad zu ermitteln. Ich habe das aber jetzt dahingehend geändert, dass der Anwender einfach die öffentliche Variable compareMode ändern. Die kann entweder CompareMode.ASCENDING (Standard) oder CompareMode.DESCENDING annehmen.
Den CostsCalculator habe ich in die Klasse Node ausgelagert. Ich finde, dort passt es besser hin. Dass dieses Interface austauschbar ist kommt daher, dass der Algorithmus für viele Fälle einsetzbar sein soll. Wenn man an Spiele denkt, dessen Welten unterschiedliche Bodenbeschaffenheiten besitzen, könnte man die Bewegungskosten und/oder geschätzte Kosten leicht anpassen.

Hier ist der neue Code:
Java:
package astar;

import java.awt.Point;
import java.util.*;

public abstract class AStar {
	public static interface World {
		public List<AStar.Node> getNeighbours(AStar.Node node);
	}
	
	public static enum CompareMode {
		ASCENDING, DESCENDING
	}
	
	public static CompareMode compareMode = CompareMode.ASCENDING;

	public static Stack<Point> getPath(AStar.World world, AStar.Node start, AStar.Node end) {
		List<Node> openList = new ArrayList<Node>();
		List<Node> closedList = new ArrayList<Node>();
		openList.add(start);
		while(!closedList.contains(end) || openList.isEmpty()) {
			Collections.sort(openList, getCostsComparator(compareMode));
			start = openList.get(0);
			openList.remove(start);
			closedList.add(start);
			for(Node neighbour : world.getNeighbours(start)) {
				if(!closedList.contains(neighbour)) {
					neighbour.previousNode = start;
					neighbour.calculateCosts(end);
					if(!openList.contains(neighbour))
						openList.add(neighbour);
				}
			}
		}
		Stack<Point> path = new Stack<Point>();
		Node node = closedList.get(closedList.indexOf(end));
		while(node.previousNode != null) {
			path.push(node);
			node = node.previousNode;
		}
		return path;
	}

	private static Comparator<AStar.Node> getCostsComparator(final CompareMode cmpMode) {
		return new Comparator<Node>() {
			@Override
			public int compare(Node a, Node b) {
				if(a.getCosts(Node.Costs.ALL) < b.getCosts(Node.Costs.ALL)) 
					return cmpMode == CompareMode.ASCENDING ? -1 : 1;
				else if(a.getCosts(Node.Costs.ALL) > b.getCosts(Node.Costs.ALL))
					return cmpMode == CompareMode.ASCENDING ? 1 : -1;
				return 0;
			}
		};
	}

	public static class Node extends Point {
		public static interface CostsCalculator {
			public int getMovementCosts(AStar.Node end);
			public int getEstimatedCosts(AStar.Node start, AStar.Node end);
		}
		
		public static enum Costs {
			ALL, MOVEMENT, ESTIMATED
		}
		
		public CostsCalculator costsCalculator;

		public Node previousNode;

		private int movementCosts;
		private int estimatedCosts;

		public Node(int x, int y) {
			super(x, y);
			costsCalculator = getDefaultCostsCalculator();
			previousNode = null;
			movementCosts = 0;
			estimatedCosts = 0;
		}

		public void calculateCosts(Node node) {
			movementCosts = costsCalculator.getMovementCosts(node);
			estimatedCosts = costsCalculator.getEstimatedCosts(this, node);
		}

		public int getCosts(Costs costs) {
			switch(costs) {
			case ALL:
				return movementCosts + estimatedCosts;
			case MOVEMENT:
				return movementCosts;
			case ESTIMATED:
				return estimatedCosts;
			default:
				return -1;
			}
		}
		
		public CostsCalculator getDefaultCostsCalculator() {
			return new CostsCalculator() {
				@Override
				public int getMovementCosts(AStar.Node end) {
					int costs = 10;
					if(end.previousNode != null)
						costs += end.getCosts(Node.Costs.MOVEMENT);
					return costs;
				}

				@Override
				public int getEstimatedCosts(AStar.Node start, AStar.Node end) {
					return (Math.abs(start.x - end.x) 
							+ Math.abs(start.y - end.y)) * 10;
				}
			};
		}
	}
}

Wenn man die Nodeklasse zu einem Interface machen würde, müssten zum Beispiel Spielobjekte dieses zwangsläufig implementieren. Und mir gefällt es nicht, wenn ein Spielobjekt ein Knoten für A* repräsentiert. Um den Pfad zu ermitteln reicht es, eine Nodeinstanz mit den jeweiligen Koordinaten des "echten" Objektes zu erzeugen. Das finde ich persönlich besser.

Nun einmal zu einer Stilfrage:
Ich habe ja überwiegend statische Member, weil die AStar-Klasse abstrakt ist. Wäre es "besser" oder "schöner", diese Klasse instanziibar zu machen? Wenn ja, wieso?
Dazu hatte ich mir mal überlegt, der Klasse bei Instanziierung ein World-Objekt zu übergehen und vorarb schonmal zu untersuchen (z.B auf begehbare Flächen), sodass die Pfadermittlung möglicherweise zügiger vonstatten geht.
 
S

SlaterB

Gast
mit dem statischen CompareMode bist zu etwas angreifbar, was ist wenn jemand das mitten in der Suche ändert?
lege dir lieber eine lokale Kopie zu Beginn deiner Methode an,

mit Objekten hättest du etwas mehr Möglichkeiten, etwa zwei Suchen gleichzeitig mit unterschiedlichen CompareMode,
ginge als weiterer statischer Parameter natürlich auch,

dann noch alle allgemeinen OO-Features, Untermethoden ohne Endlos-Parameter,
Rückgabewert muss nicht alle Infos als Stack ausgedrückt enthalten, das Objekt könnte nach der Berechnung noch weiterbestehen und befragt werden, getNearestNode(), wieSchwerFandenSieDieseAufgabe() usw.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
L Unterschied zwischen List und LinkedList implementierung? Allgemeine Java-Themen 15
boschl2000 Springerproblem-Implementierung funktioniert nicht richtig Allgemeine Java-Themen 1
L rotateLeft implementierung Allgemeine Java-Themen 2
R In der Ausgabe sollte anstelle des obersten Sterns ein "+" stehen nur scheitere ich bei der Implementierung Allgemeine Java-Themen 9
D Input/Output Implementierung eines CommandHandlers/Parsers für viele Eingaben Allgemeine Java-Themen 26
Stonie Prüfen von direkter Implementierung eines Interfaces Allgemeine Java-Themen 7
S Mutable objects und Implementierung von ChangeEvents Allgemeine Java-Themen 5
W Queue Implementierung Allgemeine Java-Themen 6
C Ein Iterator ist eine Implementierung des Interface Iterable? Allgemeine Java-Themen 2
F Implementierung von Teilprogrammen [Java|Python] Allgemeine Java-Themen 7
I TimSort - Sortieralgorithmus - Erklärung und Pseudocode - Implementierung Allgemeine Java-Themen 2
L Implementierung eines AVT-Baums Allgemeine Java-Themen 2
ruutaiokwu burstsort-implementierung in java? Allgemeine Java-Themen 2
D Implementierung einer Mehrsprachigkeit, wichtig ? Allgemeine Java-Themen 5
D Implementierung einer Rechteverwaltung Allgemeine Java-Themen 2
A Implementierung eines Algorithmus (Farthest Insertion zur Lösung des TSP) in O(n²) Allgemeine Java-Themen 2
R "Countdown" Implementierung Allgemeine Java-Themen 5
J Java-Implementierung diverser Beziehungen zwischen Klassen bzw. Objekten Allgemeine Java-Themen 2
S BlueJ Cäsar-Implementierung Allgemeine Java-Themen 6
S Implementierung Programmneustart Allgemeine Java-Themen 10
R Implementierung eines Interface durch 2 verschiedene Klassen Allgemeine Java-Themen 6
G Implementierung einer Kommunikation Allgemeine Java-Themen 7
S Implementierung einer PluginArchitektur Allgemeine Java-Themen 5
A OOP: Überschreiben/Implementierung von Methoden Allgemeine Java-Themen 5
K Objekt einer konkreten Implementierung eines Interfaces durch übergebenen String Allgemeine Java-Themen 2
R Intervall-Implementierung mit selbstgebauter LinkedList Allgemeine Java-Themen 7
J Best Practice für implementierung von equals(...) Allgemeine Java-Themen 7
Kr0e Eigene RMI Implementierung Allgemeine Java-Themen 3
V Wie finde ich die konkrete Implementierung? Allgemeine Java-Themen 8
G Implementierung vom AKS-Test Allgemeine Java-Themen 11
R software implementierung Allgemeine Java-Themen 3
N Observer/Observable der JAVA-API od. eigene Implementierung Allgemeine Java-Themen 2
K Design / Implementierung Allgemeine Java-Themen 5
B jre browser implementierung ? Allgemeine Java-Themen 4
B Elegantere Lösung bei der Implementierung eines Interfaces Allgemeine Java-Themen 2
G Klasse Queue Implementierung in Java Allgemeine Java-Themen 4
G Eigene PrintService Implementierung. Allgemeine Java-Themen 5
S Build-Zeitpunt (Datum und Uhrzeit) irgendwie während der Laufzeit zugänglich machen..? Allgemeine Java-Themen 4
K Java Anwendung machen Anleitung Allgemeine Java-Themen 5
berserkerdq2 Ist es schlechter Programmierstyle mehrere Panes aufeinander zu machen? Allgemeine Java-Themen 1
X Regex mit mehreren Bedingungen machen Allgemeine Java-Themen 5
B Mit Java Click bei (x,y) machen? Allgemeine Java-Themen 6
A Objekt aus anderen Objekten machen Allgemeine Java-Themen 8
O Leerzeichen und Umlaute im Pfad einer Java Applikation machen Probleme Allgemeine Java-Themen 13
A Best Practice Wie viele Referenzen machen Sinn? Weniger ist mehr? Allgemeine Java-Themen 1
P Gif transparent machen Allgemeine Java-Themen 2
LimDul Mittels Streams aus Strings A B C den String A, B und C machen Allgemeine Java-Themen 12
P Was machen bei NoSuchSessionException? Allgemeine Java-Themen 4
X Kapselung Wie würdet ihr ein Service Layer erreichbar machen ... Allgemeine Java-Themen 62
M Dieses Programm schneller machen? Allgemeine Java-Themen 2
T String aus While Schleife für ganze Klasse sichtbar machen Allgemeine Java-Themen 5
D Eine Forschleife mit Threads abarbeiten um es zu schneller zu machen. Ist das möglich? Allgemeine Java-Themen 20
K Properties serializable machen Allgemeine Java-Themen 6
J Aus mehreren Klassen ein Datei ausführbare machen Allgemeine Java-Themen 6
X Aus Programm "Installationsprogramm" machen Allgemeine Java-Themen 6
Tacofan Button nicht mehr anklickbar machen Allgemeine Java-Themen 8
F CPU Last eines Thread ausfindig machen Allgemeine Java-Themen 0
K Best Practice JFrame Objekt allgemein zugänglich machen Allgemeine Java-Themen 8
J Java code "plugin" fähig machen Allgemeine Java-Themen 4
Z Klassen ArrayList selbst machen Allgemeine Java-Themen 5
D Code bitte mit 19 stelligen Zahlen kompatibel machen Allgemeine Java-Themen 5
F Input/Output Problem mit iText: Formularfeld uneditierbar machen Allgemeine Java-Themen 0
T Programm "diebstahlsicher" machen? Allgemeine Java-Themen 4
A BufferedImage einzelne Pixel transparent machen V2.0 Allgemeine Java-Themen 2
L Variable auch in der function verfügbar machen? Allgemeine Java-Themen 4
M Enum austauschbar machen Allgemeine Java-Themen 3
Java-Insel Zeilen im Terminalfenster unsichtbar machen Allgemeine Java-Themen 9
Java-Insel Wie kann ich ein Java-Programm zum "automatischen Öffner" einer Dateisorte machen? Allgemeine Java-Themen 13
B exe-Datei aufrufen und dort Konsoleneingaben machen Allgemeine Java-Themen 2
E Klassen Enum überladen od. austauschbar machen? Allgemeine Java-Themen 2
D [Visualization lib] ColumnChart Grenzlinie machen Allgemeine Java-Themen 2
M Variablen Wie Variable verfügbar machen? Allgemeine Java-Themen 16
K Interface Interface comparable machen Allgemeine Java-Themen 9
V 2D-Grafik Bild transparent machen. Allgemeine Java-Themen 4
cedi Eingegebenen Text in der Konsole nicht sichtbar machen oder nur in Sternchen anzeigen Allgemeine Java-Themen 2
S Umlaute machen probleme Allgemeine Java-Themen 3
Rudolf Aus Collection<Integer> eine Zahl machen Allgemeine Java-Themen 2
K Serialisierung komplett selbst machen Allgemeine Java-Themen 13
M Farbe transparent machen Allgemeine Java-Themen 3
C Linie in Matrix machen Allgemeine Java-Themen 5
J Aus Applikation ein Applet machen Allgemeine Java-Themen 5
L in zufälligen Sekunden Ausgabe machen Allgemeine Java-Themen 2
K Escapen rückgängig machen Allgemeine Java-Themen 2
Q BufferedImage enzelne Pixel tranzparent machen Allgemeine Java-Themen 2
H Server Threaded machen. Port-Problem Frage Allgemeine Java-Themen 2
hdi Bilder JAR-kompatibel machen Allgemeine Java-Themen 7
feuervogel Performanzprobleme - Code schneller machen Allgemeine Java-Themen 18
E Speicher frei machen (List) Allgemeine Java-Themen 9
I .jar Datei erstellen und rückwärskompatibel machen Allgemeine Java-Themen 6
C Programm objektorientierter machen Allgemeine Java-Themen 12
P Servlet Eingaben sicher machen Allgemeine Java-Themen 5
Developer_X JButton soll gar nichts machen Allgemeine Java-Themen 8
J Externes Programm - Konsolenausgabe kopieren/sichtbar machen Allgemeine Java-Themen 22
T Objekt der Garbage Collection zugaenglich machen? Allgemeine Java-Themen 7
G sun.awt.image.OffScreenImage Serializable machen Allgemeine Java-Themen 5
L Abstrakte Klasse: Member zwingend überschreibbar machen Allgemeine Java-Themen 2
E Aus mehreren PDFs eines machen, zusammenfügen mittels iText Allgemeine Java-Themen 1
J unlesbar machen von Code Allgemeine Java-Themen 11
C Java 6 Programme irgendwie lauffähig machen für Mac 10.5 Allgemeine Java-Themen 11
V Class Mapping - Klasse unter anderem Namen verfügbar machen Allgemeine Java-Themen 8

Ähnliche Java Themen

Neue Themen


Oben