84 Copyright 1996-1997 by Axel T. Schreiner. All Rights Reserved. Einen Algorithmus verkapseln Bei den zu Java gehörigen Paketen befindet sich kein Sortierverfahren. In diesem Abschnitt wird zuerst ein naives Sortierprogramm implementiert. Anschließend wird eine Methodik entwickelt, mit der verschiedene Sortieralgorithmen so verkapselt werden können, daß sie sich auf verschiedene Object-Sammlungen mit verschiedenen Vergleichskriterien anwenden lassen. Daraus kann dann ein modulares Sortierprogramm aufgebaut werden. Die Quellen befinden sich in einem Katalog programs/sort , der eine Reihe von Testprogrammen und ein Paket sort enthält, das die Sortierung verkapselt. Es zeigt sich, daß verschiedene Entscheidungen innerhalb der zu Java gehörigen Pakete nicht unbedingt hilfreich sind und daß das java.lang.reflect-Paket zu sehr bequemen Lösungen führt. Ein naives Sortierprogramm — sort/ShellSort ShellSort sortiert seine Argumente oder die Zeilen seiner Standard-Eingabe mit dem Sortierverfahren von Shell und gibt das Resultat als Standard-Ausgabe aus. ShellSort ist eine sehr naive Implementierung des UNIX-Kommandos sort und demonstriert vor allem den Umgang mit Vector und dem java.lang.reflect-Paket . {programs/sort/ShellSort.java} import java.io.*; import java.lang.reflect.*; import java.util.*; /** A class with naively implemented Shell sorting algorithms */ public class ShellSort { /** collects arguments or lines, has them sorted, outputs lines */ public static void main (String args []) { try { if (sort(args) != null && args.length > 0) // sort arguments for (int n = 0; n < args.length; ++ n) System.out.println(args[n]); else { // sort lines Vector v = new Vector(); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = in.readLine()) != null) v.addElement(line); for (Enumeration e = sort(v).elements(); e.hasMoreElements(); ) System.out.println(e.nextElement()); } } catch(Exception e) { System.err.println(e); } } {} 85 Eine Klassenmethode sort() sortiert den Argumentvektor, falls vorhanden. Andernfalls werden Zeilen mit readLine() von BufferedReader extrahiert und als Elemente eines Vector-Objekts gespeichert, dessen Kapazität automatisch wächst. Eine Klassenmethode sort() sortiert den Vector für aufsteigende Indexwerte relativ zu compareTo() aus String . stellt mit elements() eine Enumeration seiner Elemente bereit, die dabei wirklich vom kleinsten zum größten Index durchlaufen werden. Damit kann der sortierte Vector ausgegeben werden. Vector Einen Vector sortieren {programs/sort/ShellSort.java} /** performs Shell sort, based on K&R */ public static Vector sort (Vector v) { if (v != null) for (int gap = v.size()/2; gap > 0; gap /= 2) for (int i = gap; i < v.size(); ++ i) for (int j = i-gap; j >= 0; j -= gap) { String a = (String)v.elementAt(j); String b = (String)v.elementAt(j+gap); if (a.compareTo(b) > 0) { v.setElementAt(b, j); v.setElementAt(a, j+gap); } else break; } return v; } {} sort() beruht auf dem Zugriff zu einzelnen Elementen im Vector und auf der Verfügbarkeit von compareTo() — wozu aber Kenntnis der Element-Klasse nötig ist. 86 Einen Vektor sortieren Es muß eine zweite Klassenmethode sort() geben, denn ein Vector ist kein String[]. {programs/sort/ShellSort.java} /** performs Shell sort, based on K&R */ public static Object sort (Object v) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { final int len; if (v != null && v.getClass().isArray() && (len = Array.getLength(v)) > 1) { Class elts = Array.get(v, 0).getClass(); Method cmp = elts.getMethod("compareTo", new Class[] { elts }); for (int gap = len/2; gap > 0; gap /= 2) for (int i = gap; i < len; ++ i) for (int j = i-gap; j >= 0; j -= gap) { Object a = Array.get(v, j); Object b = Array.get(v, j+gap); if (((Integer)cmp.invoke(a, new Object[] { b })).intValue() > 0) { Array.set(v, j, b); Array.set(v, j+gap, a); } else break; } } return v; } } {} Class und das java.lang.reflect-Paket kooperieren, um zur Laufzeit Zugriff auf Typinformation zu bieten. Array ermöglicht Zugriff auf Number-Objekte eingesetzt. Vektoren mit beliebigen Elementen; falls nötig werden liefert Zugriff auf beliebige Methoden per Namen, wobei ebenfalls Number-Objekte als Argumente oder Resultate eingesetzt werden. Method Diese Lösung beruht auf Vektorelementen gleichen Typs und auf der Existenz einer Methode wie sie für String — aber nicht im Bereich von Number — existiert. Da die vorhandenen Unterklassen von Number alle final sind , wird dies trickreich... compareTo(), 87 Verpackung der Vergleiche Eine allgemeine Lösung sollte einen Sortieralgorithmus für Vector und Vektoren verfügbar machen und eine Vergleichsvorschrift davon separat verkapseln. Da Java keine Funktionszeiger besitzt, kann man ein Objekt mit dem Vergleich beauftragen: {programs/sort/Comparer.java} package sort; /** An interface for objects encapsulating comparison */ public interface Comparer { /** indicates x < == > y by returning -1 0 1 */ int compare (Object x, Object y); } {} Alternativ kann man auch Vergleichbarkeit mit Hilfe einer (potentiell asymmetrischen!) Methode verlangen: {programs/sort/Comparable.java} package sort; /** An interface for comparable objects */ public interface Comparable { /** indicates this < == > another by returning -1 0 1 */ public int compareTo (Comparable another); } {} Die Algorithmus-Klasse kann das eine Interface aus dem anderen herstellen: {programs/sort/Sorted.java} package sort; import java.lang.reflect.*; import java.util.*; /** A class to sort an Enumeration or a [] by binary insertion */ public class Sorted extends Vector { private final static class ComparableComparer implements sort.Comparer { public final int compare (Object x, Object y) { return ((Comparable)x).compareTo((Comparable)y); } } {} 88 Da String leider final ist, aber compareTo() besitzt berücksichtigen: , sollte man auch diese Situation {programs/sort/Sorted.java} private final static class ReflectComparer implements sort.Comparer { private final Method compareTo; public ReflectComparer (Object o) throws NoSuchMethodException { Class c = o.getClass(); compareTo = c.getMethod("compareTo", new Class[] {c}); } public final int compare (Object x, Object y) { try { return ((Integer)compareTo.invoke(x, new Object[] {y})).intValue(); } catch (IllegalAccessException e) { throw new RuntimeException("compareTo: "+ e); } catch (InvocationTargetException e) { throw new RuntimeException("compareTo: "+ e); } } } {} Je nach Ansatz kann man jetzt zum Beispiel auch vom ersten Objekt bestimmen, wie verglichen werden soll, und dann ein geeignetes Vergleichsobjekt intern generieren und speichern. {programs/sort/Sorted.java} /** record comparison method and sort direction<br> * These should be final, but a blank final must be initialized in every * initializer and jdk 1.1.5 does not analyze a call to this() correctly. */ private Comparer order; private boolean up; {} 89 Verpackung des Sortierverfahrens Wahrscheinlich möchte man einen beliebigen Container sortieren — vermutlich, ohne seine innere Struktur anzutasten. Ein Container sollte seine Elemente als Enumeration liefern, die dann sortiert verfügbar sein soll. Dazu muß man einen Index aufbauen. Diesen kann man als Vector repräsentieren und schon beim Einfügen sortieren. Aus Effizienzgründen verwendet man binäre Suche, aber dadurch wird nicht stabil sortiert: {programs/sort/Sorted.java} /** binary insertion of Enumeration into this, sorted up (or down) */ public final void insertSorted (Enumeration e) { if (e != null) while (e.hasMoreElements()) { Object o = e.nextElement(); // to insert int low = 0; for (int n = elementCount, high = n-1; low <= high; n >>= 1) { int mid = low + (n >> 1); int c = order.compare(o, elementData[mid]); if (c == 0) { low = mid; break; } // found at mid if (c < 0 == up) high = mid-1; // insert before mid else { low = mid+1; -- n; } // insert after mid } insertElementAt(o, low); } } {} ist true oder false und legt einfach die Interpretation des Vergleichs fest, wenn die beteiligten Objekte nicht gleich sind. up Andere Verfahren, insbesondere Nachbessern eines vorhandenen Index, kann man ähnlich verpacken. 90 Aufbereitung als Enumeration — Elements und Lines {programs/sort/Elements.java} package sort; import java.lang.reflect.Array; import java.util.*; /** A class to enumerate the elements in a [] or a single Object */ public class Elements implements Enumeration { private Object v; // to get elements from private int next = 0, len = 0; // lookahead public Elements (Object v) { // remember v if ((this.v = v) != null) len = v.getClass().isArray() ? Array.getLength(v) : 1; } public boolean hasMoreElements () { return next < len; } public Object nextElement () { if (!hasMoreElements()) throw new NoSuchElementException(); ++ next; return v.getClass().isArray() ? Array.get(v, next-1) : v; } } {programs/sort/Lines.java} package sort; import java.io.*; import java.util.*; /** A class to enumerate the lines in an InputStream */ public class Lines extends BufferedReader implements Enumeration { private String next; // not null? one line lookahead private boolean eof; // set at eof public Lines (Reader in, int sz) { super(in, sz); } public Lines (Reader in) { super(in); } public Lines (InputStream in) { this(new InputStreamReader(in)); } public boolean hasMoreElements () { if (!eof && next == null) try { next = readLine(); eof = next == null; } catch (IOException e) { next = null; eof = true; } return next != null; // if true: next is set } public Object nextElement () { if (!hasMoreElements()) throw new NoSuchElementException(); Object result = next; next = null; return result; } } {} Mit Elements kann man ein Objekt oder einen Vektor als Enumeration liefern, mit Lines einen InputStream oder Reader. 91 Konstruktoren Man sollte einen Comparer, eine Enumeration und die Sortierrichtung in verschiedenen Kombinationen angeben können. Wenn möglich, sollte man auch die Kapazität vorplanen: {programs/sort/Sorted.java} public Sorted (Enumeration e) throws NoSuchMethodException { this(e, 0, true); } public Sorted (Enumeration e, boolean up) throws NoSuchMethodException { this(e, 0, up); } public Sorted (Enumeration e, int initialCapacity) throws NoSuchMethodException { this(e, initialCapacity, true); } public Sorted (Enumeration e, int initialCapacity, boolean up) throws NoSuchMethodException { this.up = up; if (initialCapacity > 0) ensureCapacity(initialCapacity); addElement(e.nextElement()); // must exist to set order if (elementData[0] instanceof Comparable) order = new ComparableComparer(); else order = new ReflectComparer(elementData[0]); insertSorted(e); } public Sorted (Comparer order) { this(order, true); } public Sorted (Comparer order, boolean up) { this.order = order; this.up = up; } public Sorted (Enumeration e, Comparer order) { this(e, 0, order, true); } public Sorted (Enumeration e, Comparer order, boolean up) { this(e, 0, order, up); } public Sorted (Enumeration e, int initialCapacity, Comparer order) { this(e, initialCapacity, order, true); } public Sorted (Enumeration e, int initialCap, Comparer order, boolean up) { this(order, up); if (initialCap > 0) ensureCapacity(initialCap); insertSorted(e); } } {} Mit Hilfe von Elements kann man Sorted auch aus einzelnen Objekten oder Vektoren konstruieren. Aus Effizienzgründen sollte man vor insertSorted() zuerst ensureCapacity() aufrufen. 92 Tests Mit einer Reihe von trivialen Testprogrammen untersucht man, wie bequem Sorted für verschiedene Datenmengen zu verwenden ist. Print — ausgeben gibt verschiedene Datenmengen zeilenweise aus. Print demonstriert dies durch Ausgabe der Kommandozeile: Print {programs/sort/Print.java} package sort; import java.io.*; import java.util.*; /** A local class to print a Vector or print and consume an public class Print { public static void print (PrintStream p, Enumeration e) { while (e.hasMoreElements()) p.println(e.nextElement()); } public static void out (Enumeration e){ print(System.out, public static void out (Vector v) { print(System.out, public static void out (Object o) { print(System.out, public static void err (Enumeration e){ print(System.err, public static void err (Vector v) { print(System.err, public static void err (Object o) { print(System.err, /** prints arguments from the command line */ public static void main (String args []) { Print.out(args); } } enumeration */ e); } v.elements()); } new Elements(o)); } e); } v.elements()); } new Elements(o)); } {} A — Kommandozeile sortiert ausgeben A gibt die Kommandoargumente sortiert zeilenweise aus. A demonstriert Sorted für einen Vektor von String-Objekten, die mit einem ReflectComparer verglichen werden: import sort.Print; import sort.Elements; import sort.Sorted; {programs/sort/A.java} // A lives in same directory as sort package /** A trivial class to test Sorted for String[] */ public class A { public static void main (String args []) { try { Print.out(new Sorted(new Elements(args), args.length)); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } {} 93 B — Eingabezeilen sortiert ausgeben B gibt Eingabezeilen sortiert aus. B demonstriert Sorted für eine Enumeration von String-Objekten, die mit einem ReflectComparer verglichen werden: {programs/sort/B.java} import sort.Print; import sort.Sorted; import sort.Lines; /** A class to sort input lines as an Enumeration of String, up [or down] */ public class B { public static void main (String args []) { try { Print.out(new Sorted(new Lines(System.in), args.length == 0)); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } {} C — System-Properties sortiert ausgeben C gibt die System-Properties sortiert zeilenweise aus. C demonstriert, wie man Schlüssel sortieren und dann mit Werten ausgeben kann: {programs/sort/C.java} import sort.Print; import sort.Sorted; import java.util.*; /** A trivial class to emit system properties sorted up [or down] */ public class C { public static void main (String args []) { try { Enumeration n = System.getProperties().propertyNames(); n = new Sorted(n, args.length == 0).elements(); while (n.hasMoreElements()) { String s = n.nextElement().toString(); System.out.println(s+"\t"+System.getProperty(s)); } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } {} 94 D— System-Properties sortiert ausgeben D gibt die System-Properties sortiert zeilenweise aus. D faßt jeweils einen Namen und einen Wert zusammen und leitet sie als neue Enumeration weiter. D demonstriert Sorted für eine Enumeration von Comparable-Objekten: {programs/sort/D.java} import import import import sort.Print; sort.Sorted; sort.Comparable; java.util.*; /** A class to emit system properties using associations sorted up [or down] */ public class D implements Enumeration { private final static class KV implements Comparable { private final String key, value; public KV (String key, String value) { this.key = key; this.value = value; } public int compareTo (Comparable another) { if (another instanceof KV) return key.compareTo(((KV)another).key); throw new IllegalArgumentException("compareTo: expect key/value"); } public String toString () { return key +"\t"+ value; } } private Enumeration names = System.getProperties().propertyNames(); public boolean hasMoreElements () { return names.hasMoreElements(); } public Object nextElement () { String name = (String)names.nextElement(); return new KV(name, System.getProperty(name)); } public static void main (String args []) { try { Print.out(new Sorted(new D(), args.length == 0)); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } {} 95 E— Gleitkomma-Zahlen sortiert ausgeben E liest Gleitkommawerte von Eingabezeilen und gibt sie sortiert zeilenweise aus. E leitet die Werte als neue Enumeration von Float-Objekten weiter und verwendet einen Comparer: {programs/sort/E.java} import import import import import sort.Print; sort.Sorted; sort.Lines; sort.AsFloat; java.util.*; /** A class to take Floats from input lines and sort them up [or down] */ public class E implements Enumeration { private Enumeration values = new Lines(System.in); public boolean hasMoreElements () { return values.hasMoreElements(); } public Object nextElement () { String s = (String)values.nextElement(); for (;;) try { return new Float(s); } catch (NumberFormatException e) { if (s.length() <= 1) return new Float(0); s = s.substring(0, s.length()-1); } } public static void main (String args []) { try { Print.out(new Sorted(new E(), new AsFloat(), args.length == 0)); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } {programs/sort/AsFloat.java} package sort; Das /** A class to compare Number objects as float values */ public class AsFloat implements Comparer { public int compare (Object a, Object b) { if (a == b || (a != null && a.equals(b))) return 0; if (a instanceof Number && b instanceof Number) { float af = ((Number)a).floatValue(), bf = ((Number)b).floatValue(); if (af == bf) return 0; return af < bf ? -1 : 1; } throw new IllegalArgumentException("compareTo: expecting Number"); } } {} sort-Paket sollte möglichst viele derartige Comparer bereitstellen.