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
  • java-forum.org ist wieder online

    Das kam heute per E-Mail rein:

    […] das Java-Forum.org ist wieder online. Aufgrund einer heftigen DDoS-Attacke war das Forum für mehrere Tage offline. Der Angriff war so intensiv, dass die Netzwerkinfrastruktur von Profihost (alter Server von Vladimir) hierdurch massiv beeinträchtigt wurde. Aus diesem Grund wollte der Hoster Profihost die Seite nicht mehr online stellen. Entsprechende Daten auf der Festplatte von Profihost wurden leider auch nicht mehr gefunden. Alle Bemühungen schlugen fehl und uns waren leider die Hände gebunden.
    Die zweite schlechte Nachricht ist, dass die Datenbank während der DDoS-Attacke gehackt wurde. Dabei wurde die Tabelle mit den Beiträgen des Forums geleert. Die Leute des Managed Servers von Vladimir haben leider keine tägliche Backups erstellt und Vladimir hatte selber nur ein Backup vom 11. Februar 2013, heißt also: Uns fehlen ca. 6000 erstellte Themen inklusive Beiträge die von Februar bis jetzt geschrieben wurden. 🙁
    Mittlerweile befindet sich die Seite auf unserem Server. Absofort werden täglich jede Nacht automatisch Backups erstellt und extern gespeichert. Außerdem ist der neue Server um einiges leistungsfähiger als der alte und kann auch größere DDoS-Attacken standhalten.
    Bei Störungen am Server wird umgehend ein Servicetechniker per SMS informiert.
    Ich bitte die lange Ausfallzeit zu entschuldigen.

    Ob man das Forum nutzen und erweitern möchte muss jeder für sich selbst entscheiden, eine Empfehlung ist stattdessen das http://forum.byte-welt.net/forums/6-Java-Forum.