Die Ackermann-Funktion

Die Ackermann-Funktion ist ein prominentes Beispiel für eine rekursive Funktion, die jetzt — und noch die nächsten Jahrzehnte — Informatiker im Studium beschäftigt.

Sie ist nach F. Wilhelm Ackermann (1886-1962) benannt. Viele Funktionen der mathematischen Praxis sind primitiv rekursiv, und David Hilbert stellte 1926 die Frage, ob alle Funktionen, deren Argumente und Werte natürliche Zahlen sind, primitiv rekursiv sind. Die Ackermann-Funktion steigt sehr stark an und ist für Theoretiker ein Beispiel dafür, dass es berechenbare Funktionen gibt, die aber nicht primitiv rekursiv sind. Im Jahre 1928 zeigte Ackermann dies an einem Beispiel: der Ackermann-Funktion.6 Sie wächst stärker als es Substitution und Rekursion ermöglichen und nur für kleine Argumente lassen sich die Funktionswerte noch ohne Rekursion berechnen. Darin bestand auch die Beweisidee von Ackermann, eine Funktion zu definieren, die schneller wächst als alle primitiv rekursiven Funktionen. Wir wollen hier nicht die originale Version von Ackermann benutzen, die durch die Funktionalgleichung

f(n‘, x‘, y‘) = f(n, f(n‘, x, y), x)

ausgedrückt wird, sondern die vereinfachte Variante von Hans Hermes. Wir wollen die Version von Hermes aber fortan auch Ackermann-Funktion nennen, da sie direkt aus dem Original gewonnen wird. Für die oben angegebene Funktion muss in der Abhandlung von Ackermann nachgeblättert werden, um den Nachweis des Nicht-primitiv-rekursiv-Seins zu finden.

Die neue Ackermann-Funktion ist eine Abbildung von zwei ganzen Zahlen auf eine ganze Zahl a(n,m). Sie ist mathematisch durch folgende Gesetzmäßigkeit definiert:

a(0,m) = m + 1
a(n,0) = a(n – 1, 1)
a(n,m) = a(n – 1, a(n, m – 1))

Die Ackermann-Funktion ist dafür berühmt, die Rechenkapazität ganz schnell zu erschöpfen. Sehen wir uns die Implementierung in Java an und testen wir das Programm mit ein paar Werten.

class Ackermann
{
  public static long ackermann( long n, long m )
  {
    if ( n == 0 )
      return m + 1;
    else
    
      if ( m == 0 )
        return ackermann( n - 1, 1 );
      else
        return ackermann( n - 1, ackermann(n, m - 1) );
    
  }
  public static void main( String args[] )
  {
    int x = 2,
        y = 2;
    System.out.println( "ackermann(" + x + "," + y + ")=" + ackermann(x,y) );
  }
}

Für den Aufruf ackermann(1, 2) veranschaulicht die folgende Ausgabe die rekursive Eigenschaft der Ackermann-Funktion. Die Stufen der Rekursion sind durch Einrückungen deutlich gemacht:

a(1,2):
 a(0,a(1,1)):
  a(0,a(1,0)):
   a(1,0):
    a(0,1)=2
   a(1,0)=2
   a(0,2)=3
  a(0,a(1,0))=3
  a(0,3)=4
 a(0,a(1,1))=4
a(1,2)=4

Bei festen Zahlen lässt sich der Wert der Ackermann-Funktion direkt angeben.

a(1,n) = n + 2

a(2,n) = 2n + 3

a(3,n) = 2n+3 – 3

Für große Zahlen übersteigt die Funktion aber schnell alle Berechnungsmöglichkeiten. Zum Vergleich: Die Ackermannfunktion berechnet beim Zahlenpaar (4,7) etwa 0,2*10^20. Unter der Definition a(0,y) = y+1; a(x+1) = a(x,1); a(x+1,y+1) = a(x, (a+1),y)) ergibt sie die folgende große Tabelle.

A(x,y)

y = 0

y = 1

y = 2

y = 3

y = 4

y = 5

x = 0

1

2

3

4

5

6

x = 1

2

3

4

5

6

7

x = 2

3

5

7

9

11

13

x = 3

5

13

29

61

125

253

x = 4

13

65533

265536-3

2265536-3

a(3,

2265536-3)

a(3,a(4,4))

x = 5

65533

a(4,65533)

a(4,

a(4,65533))

a(4,a(5,2))

a(4,a(5,3))

a(4,a(5,4))

x = 6

a(4,65533)

a(5,

a(4,65533))

a(5,a(6,1))

a(5,a(6,2))

a(5,a(6,3))

a(5,a(6,4))

Ackermann-Funktion für einige Werte

Die Ackermann Funktion wächst, obwohl sie Turing-berechenbar ist, schneller als alle primitiv rekursiven Funktionen. Normale mathematische Notationen versagen beim Schreiben dieser großen Zahlen und eine andere Möglichkeit muss her um große Zahlen zu definieren. Schauen wir uns doch erst einmal an, woher die großen Zahlen überhaupt kommen. Dazu eine alternative Definition, nach der ersten Zahle entwickelt:

a(0,n) = n + 1

a(1,n) = 2 + (n + 3) – 3

a(2,n) = 2 * (n + 3) – 3

a(3,n) = 2 ^ (n + 3) – 3

a(4,n) = 2 ^ 2 ^ … ^ 2 – 3 (wobei die Potenz (n+3)-mal erhoben wird)

Schon bei der Definition von a(4,n) tritt n mal eine Zweierpotenz auf. Mit der herkömmlichen Schreibweise unhandelbar. Glücklicherweise haben sich berühmte Männer mit neuen Notationen einen Namen gemacht, unter ihnen der nimmer müde werdende Donald E. Knuth. Er schuf 1976 die Up-Arrow-Notation (auf deutsch etwa Nach-oben-Pfeil-Notation – wir bleiben an dieser Stelle aber englisch). Die Schreibweise wird am besten an der Motivation, die Multiplikation einzuführen, verdeutlicht. Addieren wir n mal den Summanden m, so entspricht dies einer Multiplikation vom m mit der Anzahl n (m + m + … + m = m * n). Bilden wir die Potenz einer Zahl, multiplieren wir n mal m, so schreibt sich dies m ^ n = m m … m = mn. Führen wir dies weiter. Was kommt mach dem n-maligen Produkt? Es kommt danach die n-te Potenz (in Zeichen m^^n, daher zwei mal das Zeichen ^^). Fassen wir bisheriges mit einigen Beispielen zusammen:

  • m n = m + m + … + m = m * n
  • m^n = m m … m = mn

    2^2 = 2*2 = 4

    2^3 = 2*2*2 = 8

    2^4 = 2*2*2*2 = 16

    3^2 = 3*3 = 9

    3^3 = 3*3*3 = 27

    3^4 = 3*3*3*3 = 81

    4^2 = 4*4 = 16

    4^3 = 4*4*4 = 64

    4^4 = 4*4*4*4 = 256

  • m ^^ n = m ^ m ^ … ^ m = mm…m

    2^^2 = 2^2 = 4

    2^^3 = 2^2^2 = 2^4 = 16

    2^^4 = 2^2^2^2 = 2^16 = 65536

    3^^2 = 3^3 = 27

    3^^3 = 3^3^3 = 3^27 = 7625597484987

    3^^4 = 3^3^3^3 = 3^3^27 = 3^7625597484987

  • m ^^^ n = m ^^ m ^^ … ^^ m

    2^^^2 = 2^^2 = 4

    2^^^3 = 2^^2^^2 = 2^^4 = 65536

    2^^^4 = 2^^2^^2^^2 = 2^^65536 = 2^2^…^2 (65536 mal)

    3^^^2 = 3^^3 = 7625597484987

    3^^^3 = 3^^3^^3 = 3^^7625597484987 = 3^3^…^3 (7625597484987 mal)

    3^^^4 = 3^^3^^3^^3 = 3^^3^^7625597484987 = 3^3^…^3 (3^^7625597484987 mal)

  • m ^^^^ n = m ^^^ m ^^^ … ^^^ m (n mal)

    2^^^^2 = 2^^^2 = 4

    2^^^^3 = 2^^^2^^^2 = 2^^^4 = 2^2^…^2 (65536 mal)

    2^^^^4 = 2^^^2^^^2^^^2 = 2^^^2^2^…^2 (65536 mal)

    3^^^^2 = 3^^^3 = 3^3^…^3 (7625597484987 mal)

    3^^^^3 = 3^^^3^^^3 = 3^^^3^3^…^3 (7625597484987mal)

    = 3^^3^3^…^3 (3^3^…^3 (7625597484987 mal) mal)

  • Nun endlich lassen sich die großen Zahlen mit kleinen Formeln schreiben. So für die ersten fünf Elemente:

    a(1, n) = 2 + (n + 3) – 3

    a(2, n) = 2 (n + 3) – 3

    a(3, n) = 2 ^ (n + 3) – 3

    a(4, n) = 2 ^^ (n + 3) – 3

    a(5, n) = 2 ^^^ (n + 3) – 3

    John H. Conway geht noch einen Schritt weiter, denn auch die Up-Arrow-Schreibweise kann die Formel für a(m,n) nicht ohne den Zusatz »^…^ und dass n-2 mal« lösen. Conway schafft dies mit seiner Chained-Arrow Notation (zu deutsch etwa Ketten-Pfeil Notation, aber wir bleiben wieder beim englischen Begriff). Conway führt für »^…^« die Schreiweise

    a -> b -> c = a ^^…^^ b

    ein, wobei c die Anzahl der Potenzen beschreibt. Nun endlich vereinfacht sich auch der die Ackermann-Funktion. Es folgt:

    a(m, n) = 2 -> (n + 3) -> (m – 2) – 3

    Felder sind implizit Serializable

    Primitive Datentypen werden beim Serialisierungs-Prozess selbst in den Datenstrom geschrieben. Das gleiche gilt auch für Felder; sie sind automatisch Serializable.

    Neben der Methode clone() und dem Attribut length besitzt ein Feld eine zweite wichtige Eigenschaft, die eng mit clone() verbunden ist: Ein Feld lässt sich serialisieren. Dazu muss aber ein Array-Objekt die Schnittstelle java.io.Serializable implementieren, und dies macht es auch versteckt.

    Betrachten wir das folgende Programm, so erkennen wir, dass nur bei einer gültigen Referenz auf ein Feld-Objekt dieses Objekt instanceof Serializable ist.

    class ArrayIsSerializable
    {
      public static void main( String args[] )
      {
        int f1[] = null;
        int f2[] = new int[10];
    
        Serializable s = (Serializable)f1;
    
        System.out.println( s );   // null
    
        boolean b1 = f1 instanceof Serializable;
        boolean b2 = f2 instanceof Serializable;
    
        System.out.println( b1 );  // false
        System.out.println( b2 );  // true
      }
    }

    Die abstrakten Basisklassen für Container

    Das Designprinzip der Collection-Klassen folgt drei Stufen:

    • Schnittstellen legen Gruppen von Operationen für die verschiedenen Behältertypen fest.
    • Abstrakte Basisklassen führen die Operationen der Schnittstellen auf eine minimale Zahl von als abstrakt deklarierten Grundoperationen zurück, etwa addAll() auf add() oder isEmpty() auf getSize().
    • Konkrete Klassen für bestimmte Behältertypen beerben die entsprechende abstrakte Basisklasse und ergänzen die unbedingt erforderlichen Grundoperationen (und einige die Performance steigernde Abkürzungen gegenüber der allgemeinen Lösung in der Oberklasse).

    Es gibt eine Reihe von abstrakten Basisklassen, die den Containern eine Basisfunktionalität geben. Unter ihnen sind:

    AbstractCollection

    Implementiert die Methoden der Schnittstelle Collection ohne iterator() und size(). AbstractCollection ist Basisklasse von AbstractList und AbstractSet.

    AbstractList

    Erweitert AbstractCollection und implementiert die Schnittstelle List. Für eine konkrete Klasse müssen lediglich Methoden für get(int index) und size() implementiert werden. Soll die Liste auch Elemente aufnehmen, sollte sie auch set(int index, Object element) implementieren. Andernfalls bewirkt das Einfügen von Elementen nur eine Unsupported OperationException. Die direkten Unterklassen sind AbstractSequentialList, ArrayList und Vector.

    AbstractSequentialList

    AbstractSequentialList erweitert AbstractList (und damit auch AbstractCollection) und bildet die Grundlage für die Klasse LinkedList. Im Gegensatz zur konkreten Klasse ArrayList bereitet AbstractSequentialList die Klasse LinkedList darauf vor, die Elemente in einer Liste zu verwalten und nicht wie ArrayList in einem internen Array.

    AbstractSet

    Erweitert AbstractCollection und implementiert die Schnittstelle Set. Die Klasse AbstractSet dient als Basis für die beiden Klassen HashSet und TreeSet. Es überschreibt auch keine Methoden der Oberklasse AbstractCollection, sondern fügt nur die Methode equals(Object) und hashCode() hinzu.

    AbstractMap

    Implementiert die Schnittstelle Map. Um eine konkrete Unterklasse zu erstellen, muss put() sinnvoll implementiert werden; ohne Überschriebenes put() folgt eine UnsupportedOperationException. Für get(Object) gibt es eine Standard-Implementierung.

    Das Wissen um diese Basisimplementierung ist nützlich, wenn eigene Datenstrukturen implementiert werden. Das passiert selten, findet man aber etwa bei Google Guava.

    Absolute Koordinaten einer Gui-Komponente

    Um die absoluten Koordinaten des Elements am Bildschirm zu bestimmen, können wir folgende Idee einsetzen: Zunächst bestimmen wir die Koordinaten des Elements relativ zum Fenster. Sie sollen in den Variablen x und y stehen. Dann hangeln wir uns von der aktuellen Komponente bis zum aktuellen Fenster durch und fragen das Frame-Objekt über getLocation() nach der absoluten Position. Die Fenster-Koordinaten addieren wir zu den relativen Komponenten-Koordinaten, um die absoluten Koordinaten zu erhalten.

    Component c = this;
    while ( (c = c.getParent()) != null )
    if ( c instanceof JFrame )
    break;
    if ( c != null )
    {
      Point p = ((JFrame) c).getLocation();
      x += p.x;
      y += p.y;
    }

    Die Methode SwingUtilities.getWindowAncestor(Component) läuft für eine gegebene Komponente selbständig hoch und liefert das Window-Objekt oder null. Um einem Fenster ein Ereignis zum Schließen zu schicken, schreiben wir einfach:

    Window w = SwingUtilities.getWindowAncestor( c );
    w.dispatchEvent( new WindowEvent(w, WindowEvent.WINDOW_CLOSING) );

    Java EE-Beispiele von Apache TomEE

    Siehe https://tomee.apache.org/examples-trunk/index.html:

    Session Beans
    EntityManagers
  • injection-of-entitymanager *
  • jpa-eclipselink
  • jpa-hibernate
  • jpa-enumerated *
  • CDI
    EJB
  • access-timeout *
  • async-methods *
  • schedule-expression *
  • schedule-methods
  • interceptors
  • async-postconstruct
  • REST
  • simple-rest
  • rest-example
  • rest-example-with-application
  • rest-on-ejb
  • rest-xml-json
  • Web Services
  • simple-webservice *
  • webservice-handlerchain *
  • webservice-holder *
  • webservice-attachments
  • webservice-inheritance
  • webservice-security
  • webservice-ws-security
  • JMS and MDBs
  • injection-of-connectionfactory
  • simple-mdb-with-descriptor
  • simple-mdb *
  • Transactions
  • testing-transactions
  • transaction-rollback
  • applicationexception
  • Security
  • testing-security-3
  • testing-security-2
  • testing-security
  • DataSources
  • injection-of-datasource
  • datasource-ciphered-password *
  • dynamic-datasource-routing *
  • resources-declared-in-webapp
  • Referencing EJBs
  • injection-of-ejbs *
  • lookup-of-ejbs-with-descriptor
  • lookup-of-ejbs
  • Environment Entries
  • injection-of-env-entry
  • custom-injection
  • Java EE Connectors
  • quartz-app *
  • Testing Techniques
  • alternate-descriptors *
  • application-composer *
  • testcase-injection
  • ear-testing *
  • Frameworks
  • spring-integration
  • spring-data-proxy
  • struts
  • Meta-Annotations
  • access-timeout-meta *
  • schedule-methods-meta
  • testing-security-meta
  • movies-complete-meta
  • movies-complete
  • Proxy Beans
  • dynamic-dao-implementation *
  • dynamic-implementation
  • dynamic-proxy-to-access-mbean
  • spring-data-proxy
  • EJB Legacy
    Other Features
  • mbean-auto-registration *
  • bean-validation-design-by-contract *
  • telephone-stateful *
  • troubleshooting
  • Misc
  • applet
  • ejb-examples
  • ejb-webservice
  • jsf-cdi-and-ejb
  • jsf-managedBean-and-ejb
  • moviefun
  • helloworld-weblogic
  • polling-parent
  • Google Annotations Gallery: Immer wieder ein Schmunzler

    Siehe https://code.google.com/p/gag/:

    Disclaimer

    • @AhaMoment
    • @BossMadeMeDoIt
    • @HandsOff
    • @IAmAwesome
    • @LegacySucks

    Enforceable

    • @CantTouchThis
    • @ImaLetYouFinishBut

    Literary Verse (new subcategory)

    • @Burma Shave
    • @Clerihew
    • @DoubleDactyl
    • @Haiku (moved to this subcategory)
    • @Limerick
    • @Sonnet

    Remarks

    • @Fail
    • @OhNoYouDidnt
    • @RTFM
    • @Win

    Team (new category)

    • @Blame
    • @Channeling
    • @Fired
    • @HonorableMention
    • @Visionary

    JMapViewer, einfache Komponenten für OpenStreepMap Karte

    Siehe http://wiki.openstreetmap.org/wiki/JMapViewer.

    • Provides integrated zoom controls (slider and buttons) in the left upper corner (can be hidden)
    • Switch between different tile sources: Mapnik, Tiles@Home, Cyclemap, … (other tiled maps can be used, too)
    • Configurable in-memory and file-based caching of loaded map tiles
    • A list of map markers (the yellow circles in the screenshot) can be added. Map markers of different shape can be easily added by implementing the MapMarker interface.
    • Configurable/Extentable/Replaceable controller (code part that manages mouse interaction and how the map reacts to it)
    • Requirement: Java 1.6
    • License: GPL

     

    JMapViewer demo app screenshot

    Apache ODF Toolkit in Release 0.6

    Aus dem Changelog https://www.apache.org/dist/incubator/odftoolkit/CHANGES-0.6-incubating.txt:

    * Added document encryption support

    * Added metadata support

    * Support for OpenDocument-v1.2

    * Additional APIs for Simple API

    Zum Projekt selbst sagt die Homepage:

    The Apache ODF Toolkit is a set of Java modules that allow programmatic creation, scanning and manipulation of Open Document Format (ISO/IEC 26300 == ODF) documents. Unlike other approaches which rely on runtime manipulation of heavy-weight editors via an automation interface, the ODF Toolkit is lightweight and ideal for server use.

    Dokumente sind leicht erstellt, siehe https://incubator.apache.org/odftoolkit/simple/gettingstartguide.html:

    import java.net.URI;
    
    import org.odftoolkit.simple.TextDocument;
    import org.odftoolkit.simple.table.Cell;
    import org.odftoolkit.simple.table.Table;
    import org.odftoolkit.simple.text.list.List;
    
    public class HelloWorld {
        public static void main(String[] args) {
            TextDocument outputOdt;
            try {
                outputOdt = TextDocument.newTextDocument();
    
                // add image
                outputOdt.newImage(new URI("odf-logo.png"));
    
                // add paragraph
                outputOdt.addParagraph("Hello World, Hello Simple ODF!");
    
                // add list
                outputOdt.addParagraph("The following is a list.");
                List list = outputOdt.addList();
                String[] items = {"item1", "item2", "item3"};
                list.addItems(items);
    
                // add table
                Table table = outputOdt.addTable(2, 2);
                Cell cell = table.getCellByPosition(0, 0);
                cell.setStringValue("Hello World!");
    
                outputOdt.save("HelloWorld.odt");
            } catch (Exception e) {
                System.err.println("ERROR: unable to create output file.");
            }
        }
    }