Programmiertechnik/Gruppenwechsel

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Als Gruppenwechsel (engl. control break) wird der entscheidende Vorgang beim Prinzip der Gruppenkontrolle (häufig auch Gruppenwechsellogik oder Gruppenwechselverarbeitung) bezeichnet. Für die Durchführung eines Gruppenwechsels müssen die Eingabedaten nach dem Gruppenkriterium sortiert[1] vorliegen und sequentiell verarbeitet werden. Der Gruppenwechsel wird durchgeführt, wenn der gerade zu verarbeitende Datensatz einer anderen Gruppe zugehörig ist als der unmittelbar zuvor verarbeitete Datensatz.

Unterschieden werden einstufige und mehrstufige Gruppenwechsel. Bei einem einstufigen Gruppenwechsel hängt eine Gruppe von nur einem einzigen Gruppenbezeichner ab (Gruppenwechsel 1. Ordnung). Bei mehrstufigen Gruppenwechseln hängt die Zugehörigkeit eines Datensatzes zu einer (Unter-) Gruppe von zwei oder mehr Kriterien ab (Gruppenwechsel 2. bis n. Ordnung).

Vorgehensweise

Screenshot Struktogram
Struktogramm der Programmlogik für einen zweistufigen Gruppenwechsel bei sequentieller Verarbeitung einer Datei

Grundsätzlich gliedert sich die Vorgehensweise zur Durchführung eines Gruppenwechsels in die drei Schritte Gruppenvorlauf, Gruppendurchlauf und Gruppennachlauf. Zuvor kann eine Initialisierung und anschließend eine Nachbearbeitung stattfinden. Der eigentliche Gruppenwechsel findet implizit nach dem Gruppennachlauf statt, d. h. die Gruppe wird durch die vorgelagerten Schritte bereits gewechselt, ohne dass der Wechsel selbst noch explizit angegeben werden muss (vgl. die folgenden Ausführungen).

Ein mehrstufiger Gruppenwechsel besteht aus mehreren, ineinander verschachtelten, einstufigen Gruppenwechseln, d. h. innerhalb des Gruppendurchlaufs der Obergruppe finden zusätzlich die drei Schritte Vor-, Durch- und Nachlauf der Untergruppe statt. Dies lässt sich beliebig oft wiederholen (bzw. beliebig tief verschachteln).

Die einzelnen Schritte zur Durchführung einer Gruppenkontrolle werden im Folgenden beschrieben. Zum besseren Verständnis des Geschriebenen folgen anschließend einige Beispiele.

Initialisierung

In der Initialisierungsphase werden beispielsweise generelle, d. h. für alle (Unter-)Gruppen bzw. alle Elemente gültige Variablen initialisiert. So werden u. a. Zählervariablen für die spätere Ausgabe einer Summenzeile hier initialisiert. Erfolgt eine Ausgabe von tabellarischen Daten könnte beispielsweise der Tabellenkopf während der Initialisierungsphase ausgegeben werden.

Gruppenvorlauf

Im Unterschied zur Initialisierung werden beim Gruppenvorlauf z. B. spezifische, also die gerade durchlaufene Gruppe betreffende Variablen initialisiert. Dies können Zählervariablen sein, wenn z. B. später eine Summe für die einzelne Gruppe ausgegeben werden soll. Bei einer direkten Ausgabe könnte im Gruppenvorlauf auch der Name der Gruppe oder ähnliches ausgegeben werden.

Gruppendurchlauf

Der Gruppendurchlauf erfolgt solange, bis "das Ende" einer Gruppe erreicht wurde. Dies ist der Fall, wenn der aktuell verarbeitete Datensatz einer anderen Gruppe als der vorherige Datensatz angehört oder es sich um den letzten Datensatz handelt. Dann folgt der Gruppennachlauf. Ansonsten werden im Gruppendurchlauf z. B. die generellen und spezifischen Zählervariablen erhöht oder – im Falle einer Ausgabe – zum Beispiel der Name des Elements ausgegeben.

Gruppennachlauf

Der Gruppennachlauf folgt auf das letzte Element der aktuell verarbeiteten Gruppe. Hier kann beispielsweise eine Summenzeile für die aktuell verarbeitete Gruppe ausgegeben werden.

Nachbearbeitung

Die Nachbearbeitung folgt auf das letzte verarbeitete Element. Die Nachbearbeitung ist ähnlich dem Gruppennachlauf, nur dass hier beispielsweise die während der Initialisierung angelegten Variablen als Gesamtsumme aller Elemente ausgegeben werden können.

Vollständige Vorgehensweise

Screenshot Aktivitätsdiagramm
UML-Aktivitätsdiagramm für einen einstufigen Gruppenwechsel bei sequentieller Verarbeitung

Wie die Gruppenkontrolle konkret umgesetzt wird hängt grundsätzlich von den vorliegenden Daten und der verwendeten Programmiersprache, sprich vom konkreten Anwendungsfall, ab. Die prinzipielle Vorgehensweise ähnelt sich aber. Im Nebenstehenden UML-Aktivitätsdiagramm ist die entscheidende Programmlogik für die sequentielle Datenverarbeitung bei einem einstufigen Gruppenwechsel dargestellt.

Im Folgenden der Pseudocode für einen einstufigen Gruppenwechsel:

  • Hole die zu verarbeitenden Daten
  • Sortiere[1] die Daten entsprechend des Gruppenkriteriums
  • Initialisiere eine Variable namens GRUPPE mit dem Wert NULL
  • Durchlaufe alle Datensätze
    • Wenn die Gruppe des aktuell verarbeiteten Datensatzes ungleich der GRUPPE ist
      • Setze den Wert der Variable GRUPPE gleich der Gruppe des aktuell verarbeiteten Datensatzes
  • KOMMENTAR: Aktuell verarbeiteter Datensatz gehört der Gruppe GRUPPE an

Der Pseudocode für einen zweistufigen Gruppenwechsel:

  • Hole die zu verarbeitenden Daten
  • Sortiere[1] die Daten entsprechend des Gruppen- und des Untergruppenkriteriums
  • Initialisiere eine Variable namens GRUPPE mit dem Wert NULL
  • Initialisiere eine Variable namens UNTERGRUPPE mit dem Wert NULL
  • Durchlaufe alle Datensätze
    • Wenn die Gruppe des aktuell verarbeiteten Datensatzes ungleich der GRUPPE ist
      • Setze den Wert der Variable GRUPPE gleich der Gruppe des aktuell verarbeiteten Datensatzes
      • Setze den Wert der Variable UNTERGRUPPE gleich der Untergruppe des aktuell verarbeiteten Datensatzes
    • Wenn die Untergruppe des aktuell verarbeiteten Datensatzes ungleich der UNTERGRUPPE ist
      • Setze den Wert der Variable UNTERGRUPPE gleich der Untergruppe des aktuell verarbeiteten Datensatzes
  • KOMMENTAR: Aktuell verarbeiteter Datensatz gehört der Gruppe GRUPPE und der Untergruppe UNTERGRUPPE an

Beispiele

Im Folgenden werden einige Beispiele für Gruppenwechsel dargestellt. Die generelle Vorgehensweise kann auf andere Probleme übertragen werden.

Beachten Sie: Die Beispiele sollen einen einfachen Überblick über eine mögliche Umsetzung von Gruppenwechseln geben und ein Verständnis für das Vorgehen vermitteln. In der Praxis gibt es viele verschiedene Möglichkeiten für die Implementierung einer Gruppenkontrolle. Die Beispiele müssen daher nicht der best practice entsprechen.

Allgemein

Allgemeine Beispiele die (hoffentlich) das Prinzip des Gruppenwechsels anschaulich erklären.

Bahnreisender

Screenshot Fahrplan
Der Fahrplan des Bahnreisenden[2]

Ein Bahnreisender möchte mit dem ICE von Hamburg nach München reisen. Da er in Eile war hatte er vergessen auf den Fahrplan zu schauen. Er möchte aber gerne wissen, welche und wie viele Bahnhöfe der Zug in welchem Bundesland und insgesamt anfährt. Obwohl er gut in Geografie ist, kann er sich andere Dinge schlecht merken und rechnen ist auch nicht unbedingt seine Stärke. Glücklicherweise hat er aber trotz der Eile an sein Notizbuch gedacht und es eingepackt.

Er steigt in den Zug ein und fasst den Plan, einfach alles in sein Notizbuch zu schreiben um am Ende der Reise trotzdem zu wissen, welche Route der Zug gefahren ist. Zuerst schlägt er eine neue Seite seines Notizbuches auf und schreibt oben dick Anzahl Bahnhöfe hin. Bei jedem Bahnhof, an dem der Zug hält, wird er hier einen Strich machen. So braucht er am Ende nur die Striche abzählen und weiß, wie viele Bahnhöfe der Zug insgesamt angefahren ist.

Darunter malt er ein großes Kästchen. Hier wird er immer das Bundesland reinschreiben, in dem er sich gerade befindet. Gut, das er aus Gewohnheit immer mit Bleistift schreibt, so kann er das Kästchen immer wieder ausradieren, wenn er in ein neues Bundesland kommt.

Auf der restlichen Notizbuchseite hat er nun genug Platz, um die einzelnen Bahnhöfe und Bundesländer aufzuschreiben. Er wird immer, wenn der Zug an einem Bahnhof eines anderen Bundeslandes ankommt (dann hat er also offensichtlich das letzte Bundesland verlassen, so überlegt er sich) das neue Bundesland aufschreiben und darunter einfach immer die Bahnhöfe, an denen der Zug hält. Das Bundesland wird er zudem unterstreichen, damit er auf einen Blick sieht, dass es sich dabei um ein Bundesland und keinen Bahnhof handelt. Und daneben wird er bei jedem Halt einen Strich machen, so braucht er auch hier später nur die Striche zählen und weiß, wie viele Bahnhöfe der Zug in diesem Bundesland angefahren hat.

"Die Fahrt kann also beginnen!", denkt sich der Bahnreisende, bevor er doch noch einmal kurz seinen Plan überdenkt: Er möchte eigentlich alle Bahnhöfe auflisten. Bei seinem Prinzip hatte er aber ganz vergessen, dass er bereits in den Zug eingestiegen ist und sich im Hamburger Bahnhof befindet. Er macht also schnell einen Strich hinter Anzahl Bahnhöfe. Außerdem schreibt er in das große Kästchen Hamburg, da er sich gerade im Bundesland Hamburg befindet. Und unter das Kästchen schreibt er Hamburg (das Bundesland) und unterstreicht es, macht einen Strich dahinter (da dies der erste Bahnhof im Bundesland Hamburg ist) und schreibt darunter Hamburg Hbf, also seinen Einsteigebahnhof.

Der Zug setzt sich in Bewegung und stoppt schon kurze Zeit später in Hamburg-Harburg. Der Bahnreisende befindet sich offentsichtlich noch in Hamburg und macht daher einen Strich neben Hamburg. Außerdem zeichnet er einen Strich neben Anzahl Bahnhöfe und den schreibt den Namen des Bahnhofs, hier also Hamburg-Harburg an das Ende seiner Liste. Da er dies bei jedem Halt macht, wird er es in dieser Geschichte nicht weiter erwähnen. Der Zug fährt weiter.

Nächster Halt: Hannover Hbf. Der Bahnreisende weiß, dass er sich jetzt in der Landeshauptstadt Niedersachsens befindet. Er blickt auf sein Notizbuch und sieht, dass in dem großen Kästchen oben auf der Seite aber Hamburg steht. Er muss somit Hamburg verlassen haben und nun in Niedersachsen angekommen sein. Daher schreibt er nun Niedersachsen auf seinen Notizzettel, unterstreicht es und malt gleichzeitig wieder einen Strich daneben. Zudem radiert er Hamburg aus dem Kasten oben auf der Seite aus und schreibt stattdessen Niedersachsen hinein.

Als nächstes hält der Zug in Göttingen. Hier macht der Bahnreisende sowohl einen Strich bei Anzahl Bahnhöfe als auch neben Niedersachsen und schreibt zudem Göttingen an das Ende seiner Notizen. Bis er in München ankommt macht er also immer wieder das Gleiche wie zuvor beschrieben: Bei jedem Bahnhof macht er einen Strich neben Anzahl Bahnhöfe. Sollte er sich noch immer im selben Bundesland wie zuvor befinden schreibt er einen Strich neben das Bundesland. Wenn er merkt, dass er ein Bundesland verlassen hat, schreibt er den Namen des "neuen" Bundeslandes in das Kästchen. Außerdem schreibt er es in diesem Fall an das Ende seiner Notizen und unterstreicht es. Zuletzt schreibt er den Namen des Bahnhofs an das Ende seiner Notizen.

Der Zug fährt in München Hbf ein, das Ende seiner Reise. Der Bahnreisende kann nun nach einem kurzen Blick auf seinen Notizzettel seinen Freunden, die dort bereits auf ihn warten, erzählen, wie seine Bahnreise verlief:

Screenshot Notizzettel
Der Notizzettel des Bahnreisenden

JavaScript

Beispiele für Gruppenwechsel in JavaScript.

Länder und Kontinente (1. Ordnung)

Kontinente
Die Kontinente der Erde farblich hervorgehoben

In einem bereits existierenden HTML-Dokument sollen die Länder nach Kontinenten gruppiert farbig markiert werden. Die Länder und zugehörigen Kontinente liegen nach dem Gruppenkriterium sortiert[1] in einer HTML-Tabelle ähnlich der folgenden Darstellung vor:

Länder und Kontinente
Land Kontinent
Algerien Afrika
Nigeria Afrika
Seychellen Afrika
Indien Asien
Indonesien Asien
Volksrepublik China Asien
Australien Australien / Ozeanien
Nauru Australien / Ozeanien
Neuseeland Australien / Ozeanien
Deutschland Europa
Frankreich Europa
Vereinigtes Königreich Europa
Kanada Nordamerika
Mexico Nordamerika
Vereinigte Staaten Nordamerika
Argentinien Südamerika
Brasilien Südamerika
Peru Südamerika
Gruppenwechsel Kontinente ansehen …
// Hex-Farbcodes zum Einfärben der Tabellenzeilen
var farben = ['#FED52E', '#F33E01', '#C04080', '#C10000', '#00CC00', '#008000'];

// Die Tabellenzeilen der Tabelle holen
var laender = document.querySelectorAll('tbody tr');

// Gruppenvariable "Kontinent" initialisieren
var kontinent = null;

// Den Zähler, welche Farbe gerade verwendet wird, initialisieren
var farbZaehler = -1;

// Die Tabellenzeilen durchlaufen
for (var i = 0; i < laender.length; i++) {
    
    // Tabellenzellen der Zeile holen; das entspricht einem Tabelleneintrag
    var eintrag = laender[i].querySelectorAll('td');

    // Prüfen, ob es sich um den ersten Durchlauf handelt oder das aktuelle Land
    // einem anderen Kontinent als das vorherige Land angehört
    // (das ist der eigentliche Gruppenwechsel)
    if (i == 0 || kontinent != eintrag[1].innerHTML) {
        
        // Den aktuellen Kontinent als Gruppenvariable setzen
        kontinent = eintrag[1].innerHTML;
        
        // Die Farbe wechseln; dies ist die Farbe für diesen und für alle
        // zukünftigen Einträge dieser Gruppe
        farbZaehler++;
    }
    
    // Gruppendurchlauf: Die Tabellenzelle mit der aktuellen Gruppenfarbe
    // einfärben
    laender[i].style.backgroundColor = farben[farbZaehler];
}

PHP

Beispiele für Gruppenwechsel in PHP.

Tierarten (1. Ordnung)

In zwei normalisierten Datenbanktabellen sind Tiere sowie die "Tierart", der sie angehören, aufgeführt:

Tierart
ID Name
1 Vögel
2 Säugetiere
Tier
Tierart-ID Name
1 Amsel
1 Drossel
1 Fink
1 Star
2 Hund
2 Katze
2 Maus
2 Elefant

Die Tiere sollen nun nach "Tierarten" gruppiert ausgegeben werden. Dazu wird eine Datenbank-Abfrage gestartet, welche mit Hilfe von Joins die Daten nach dem Gruppenkriterium "Tierart" sortiert[1] zur Verfügung stellt. Die resultierende Tabelle (und damit das resultierende PHP-Array) ist somit:

Tiere
Tierart Name
Säugetiere Elefant
Säugetiere Hund
Säugetiere Katze
Säugetiere Maus
Vögel Amsel
Vögel Drossel
Vögel Fink
Vögel Star
Gruppenwechsel Tierdatenbank ansehen …
// Die sortierten Daten aus der Datenbank holen
$tiere = [/*...*/];

// Tierart (Gruppe) initialisieren
$tierart = null;

// Das Array mit den Tieren durchlaufen
foreach ($tiere as $tier) {
    // $tier ist ein assoziatives Array:
    // $tier = array('tierart' => 'Name der Tierart', 'name' => 'Name des Tieres')

    // Prüfen, ob das Tier einer anderen Tierart als das Tier zuvor angehört;
    // das ist der eigentliche Gruppenwechsel
    if ($tierart != $tier['tierart']) {

        // Aktuellen Gruppennamen (die Tierart) merken
        $tierart = $tier['tierart'];

        // Die Gruppe (Tierart) als Überschrift ausgeben
        printf('<h1>%s</h1>', $tierart);
    }

    // Den Tiernamen ausgeben
    printf('<p>%s</p>', $tier['name']);
}

Schüler einer Klasse (2. Ordnung)

In drei normalisierten Datenbanktabellen sind Schulen, zugehörige Klassen sowie die Schüler, die eine Klasse besuchen, aufgelistet:

Schulen [3]
Schul-ID Name
1 Albert-Schweitzer-Schule
2 Geschwister-Scholl-Schule
Klassen
Klassen-ID Schul-ID Name
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
6 2 4
Schüler [4]
Klassen-ID Name
1 Elias
1 Emily
1 Finn
1 Lea
2 Lena
2 Luca
2 Marie
2 Noah
3 Anna
3 Ben
4 Emilia
4 Emma
4 Hannah
4 Jonas
5 Leon
5 Luis
5 Lukas
6 Mia
6 Paul
6 Sophia

Die Schüler sollen nun nach Schule und Klasse gruppiert ausgegeben werden. Außerdem soll die Anzahl der Schüler einer Schule ausgegeben werden. Dazu wird zuerst eine Datenbank-Abfrage gestartet, welche mit Hilfe von Joins die Daten nach den Gruppenkriterien "Schule" und "Klasse" sortiert[1] zur Verfügung stellt. Die resultierende Tabelle (und damit das resultierende PHP-Array) ist somit:

Schüler
Schule Klasse Name
Albert-Schweitzer-Schule 1 Elias
Albert-Schweitzer-Schule 1 Emily
Albert-Schweitzer-Schule 1 Finn
Albert-Schweitzer-Schule 1 Lea
Albert-Schweitzer-Schule 2 Lena
Albert-Schweitzer-Schule 2 Luca
Albert-Schweitzer-Schule 2 Marie
Albert-Schweitzer-Schule 2 Noah
Geschwister-Scholl-Schule 1 Anna
Geschwister-Scholl-Schule 1 Ben
Geschwister-Scholl-Schule 2 Emilia
Geschwister-Scholl-Schule 2 Emma
Geschwister-Scholl-Schule 2 Hannah
Geschwister-Scholl-Schule 2 Jonas
Geschwister-Scholl-Schule 3 Leon
Geschwister-Scholl-Schule 3 Luis
Geschwister-Scholl-Schule 3 Lukas
Geschwister-Scholl-Schule 4 Mia
Geschwister-Scholl-Schule 4 Paul
Geschwister-Scholl-Schule 4 Sofia
Gruppenwechsel Schülerdatenbank ansehen …
// Die sortierten Daten aus der Datenbank holen
$schueler = [/*...*/];

// Schule (Hauptgruppe) initialisieren
$schule = null;

// Zählvariable für die Anzahl der Schüler einer Schule
$anzahlSchueler = 0;

// Klasse (Nebengruppe) initialisieren
$klasse = null;

// Die Schüler der aktuell verarbeiteten Klasse als Array initialisieren
$schuelerDerKlasse = [];

// Das Array mit den Schülern durchlaufen
foreach ($schueler as $einSchueler) {
    // $einSchueler ist ein assoziatives Array:
    // $einSchueler = [
    //     'schule' => 'Name der Schule',
    //     'klasse' => 'Welcher Klasse der Schüler zugehört',
    //     'name' => 'Name des Schülers',
    // ]

    // Prüfen, ob der aktuelle Schüler eine andere Klasse als der Schüler
    // aus dem letzten Schleifendurchlauf besucht; das ist der Gruppenwechsel
    // der Nebengruppe
    if ($einSchueler['klasse'] != $klasse) {
		 
        // Der Schüler besucht eine andere Klasse als der zuletzt verarbeitete
        // Schüler

        // Die Klasse und die zugehörigen Schüler ausgeben, falls dies nicht der
        // erste Schleifendurchlauf ist; dies ist der Gruppennachlauf der
        // Nebengruppe
        if ($klasse !== null) {
            printf('<p>Klasse %d: %s</p>', $klasse, implode(', ', $schuelerDerKlasse));
        }

        // Den neuen Nebengruppennamen (die Klasse des aktuellen Schülers)
        // merken, hier beginnt der Gruppenvorlauf der Nebengruppe
        $klasse = $einSchueler['klasse'];

        // Das Array mit den Schülern einer Klasse zurücksetzen
        $schuelerDerKlasse = [];
    }

    // Prüfen, ob der aktuelle Schüler eine andere Schule als der Schüler
    // aus dem letzten Schleifendurchlauf besucht; das ist der eigentliche
    // Gruppenwechsel
    if ($einSchueler['schule'] != $schule) {

        // Der Schüler besucht eine andere Schule als der zuletzt verarbeitete
        // Schüler
    	
    	// Die Anzahl der Schüler der Schule ausgeben, falls dies nicht der
    	// erste Schleifendurchlauf ist; dies ist der Gruppennachlauf der
    	// Hauptgruppe
    	if ($schule !== null) {
    	    printf('<p>%d Schüler besuchen die %s.</p>', $anzahlSchueler, $schule);
    	}

    	// Den neuen Gruppennamen (die Schule des aktuellen Schülers) merken;
    	// hier beginnt der Gruppenvorlauf der Hauptgruppe
    	$schule = $einSchueler['schule'];
    	
    	// Den Zähler für die Anzahl der Schüler zurücksetzen
    	$anzahlSchueler = 0;
    	
    	// Den Namen der Schule ausgeben
    	printf('<h1>%s</h1>', $schule);
    }
    
    // Den Namen des Schülers merken, da er die aktuell verarbeitete Klasse
    // besucht (das ist der Gruppendurchlauf der Nebengruppe)
    $schuelerDerKlasse[] = $einSchueler['name'];
    
    // Den Zähler für die Anzahl der Schüler einer Schule um 1 erhöhen
    // (das ist der Gruppendurchlauf der Hauptgruppe)
    $anzahlSchueler++;
}

// Die Nachbearbeitung: Für die letzte Schule und die letzte Klasse muss noch,
// ähnlich dem Gruppennachlauf, eine Ausgabe generiert werden
printf('<p>Klasse %d: %s</p>', $klasse, implode(', ', $schuelerDerKlasse));
printf('<p>%d Schüler besuchen die %s.</p>', $anzahlSchueler, $schule);
Hinweis:
Um das Vorgehen verständlicher zu machen wurde im Beispiel auf best practice verzichtet, so wurden beispielsweise bei der Ausgabe Code-Teile dupliziert, was schlechter Stil und nicht wartungsfreundlich ist. Abhilfe könnte in diesem Beispiel beispielsweise eine funktionale oder objektorientierte Vorgehensweise statt der hier verwendeten sequentiellen Programmierung bieten, d. h. die Ausgabe wird in einer Funktion oder Methode generiert (unabhängig davon sollten Eingabe, Verarbeitung und Ausgabe getrennt werden, vgl. EVA-Prinzip).

Forumsbeiträge (3. Ordnung)

In einer Datenbanktabelle sind Forenbeiträge ähnlich der folgenden Tabelle gespeichert:

Forenbeiträge
Beitrag-ID Datum Autor-ID Beitrag
1 2015-11-11 ... ...
2 2015-11-12 ... ...
3 2015-11-12 ... ...
... ... ... ...
31 2016-01-03 ... ...

Mit Hilfe der Gruppenwechsellogik soll nun die Anzahl der Beiträge eines Jahres, eines Monats und eines Tags ermittelt und in einer ungeordneten Liste ausgegeben werden. Darüber hinaus sollen Zählung (die Verarbeitung der Datensätze) und Ausgabe separiert werden (vgl. EVA-Prinzip).

Beachten Sie: Zur Zählung von Datensätzen existieren weitere Methoden und die Gruppenwechsellogik ist nicht unbedingt die beste Lösung für diese Aufgabe. Das folgende Beispiel soll lediglich einen Eindruck von den Möglichkeiten eines Gruppenwechsels bieten und einzelne Phasen eines mehrstufigen Gruppenwechsels deutlich machen.


Gruppenwechsel Beitragsdatenbank ansehen …
Eingabe
// Die nach Datum sortierten Beiträge aus der Datenbank holen
$beitraege = [/*...*/];
Verarbeitung
// Initialisierung: Die Gruppenvariablen initialisieren
$gruppe = [
    'jahr' => ['wert' => null, 'anzahl' => 0],
    'monat' => ['wert' => null, 'anzahl' => 0],
    'tag' => ['wert' => null, 'anzahl' => 0],
];

// Die Zeitfunktionen für die spätere Ausgabe auf "deutsch" einstellen
// (vgl. dazu den Kommentar weiter unten bei "DateTime")
setlocale(LC_TIME, 'de_DE', 'de', 'deu', 'german');

// Das Datenarray für die Ausgabe initialisieren
$ausgabe = [];

// Arrays zum Zwischenspeichern von Monaten und Tagen initialisieren
$monate = [];
$tage = [];

// Alle Beiträge durchlaufen
foreach ($beitraege as $index => $beitrag) {
    
    // Das Beitragsdatum in ein DateTime-Objekt "umwandeln" (das muss nicht
    // unbedingt gemacht werden, es schadet aber auch nicht; insgesamt ist es
    // guter Stil, Objekte wie DateTime zu verwenden, da sie wie in diesem Fall
    // später Arbeit sparen; hier wird durch das Verwenden des Objekts das
    // parsen des Datums "gespart" und die spätere Ausgabe erleichtert)
    $datum = new DateTime($beitrag['datum']);
    
    // Datumsvariablen initialisieren
    $tag = $datum->format('d.m.Y');
    $monat = strftime('%B', $datum->getTimestamp());
    $jahr = $datum->format('Y');
    
    // Prüfen, ob Beitragstag, -monat oder -jahr dieses Beitrags anders als
    // beim vorherigen Beitrag sind
    if ($gruppe['tag']['wert'] != $tag
        || $gruppe['monat']['wert'] != $monat
        || $gruppe['jahr']['wert'] != $jahr
    ) {
        // Prüfen, ob es sich nicht um den ersten Gruppendurchlauf handelt
        if ($index != 0) {
    
            // Gruppennachlauf 3. Gruppe: Den Tag in den Tagzwischenspeicher
            // schreiben
            $tage[] = [
                'wert' => $gruppe['tag']['wert'],
                'anzahl' => $gruppe['tag']['anzahl']
            ];
        }
    
        // Gruppenvorlauf 3. Gruppe: Den aktuellen Tag als Gruppenvariable
        // setzen und den Beitragszähler zurücksetzen
        $gruppe['tag']['wert'] = $tag;
        $gruppe['tag']['anzahl'] = 0;
    }

    // Prüfen, ob Beitragsmonat oder -jahr dieses Beitrags anders als beim
    // vorherigen Beitrag sind
    if ($gruppe['monat']['wert'] != $monat
        || $gruppe['jahr']['wert'] != $jahr
    ) {
        // Prüfen, ob es sich nicht um den ersten Gruppendurchlauf handelt
        if ($index != 0) {
    
            // Gruppennachlauf 2. Gruppe: Den Monat in den Monatszwischen-
            // speicher schreiben
            $monate[] = [
                'wert' => $gruppe['monat']['wert'],
                'anzahl' => $gruppe['monat']['anzahl'],
                'tage' => $tage
            ];
        }
    
        // Gruppenvorlauf 2. Gruppe: Den aktuellen Monat als Gruppenvariable
        // setzen und den Beitragszähler sowie den Zwischenspeicher zurücksetzen
        $gruppe['monat']['wert'] = $monat;
        $gruppe['monat']['anzahl'] = 0;
        $tage = [];
    }
    
    // Prüfen, ob das Beitragsjahr dieses Beitrags ein anderes als das des
    // vorherigen Beitrags ist
    if ($gruppe['jahr']['wert'] != $jahr) {
        
        // Prüfen, ob es sich nicht um den ersten Gruppendurchlauf handelt
        if ($index != 0) {
            
            // Gruppennachlauf 1. Gruppe: Das Jahr in die Ausgabedaten schreiben
            $ausgabe[] = [
                'wert' => $gruppe['jahr']['wert'],
                'anzahl' => $gruppe['jahr']['anzahl'],
                'monate' => $monate
            ];
        }
        
        // Gruppenvorlauf 1. Gruppe: Das aktuelle Jahr als Gruppenvariable
        // setzen und den Beitragszähler sowie den Zwischenspeicher zurücksetzen
        $gruppe['jahr']['wert'] = $jahr;
        $gruppe['jahr']['anzahl'] = 0;
        $monate = [];
    }
    
    // Gruppendurchlauf: Die Beitragszähler um 1 erhöhen
    $gruppe['jahr']['anzahl']++;
    $gruppe['monat']['anzahl']++;
    $gruppe['tag']['anzahl']++;
}

// Nachbearbeitung Tage: Die letzte Gruppe in die Ausgabedaten schreiben
$tage[] = [
    'wert' => $gruppe['tag']['wert'],
    'anzahl' => $gruppe['tag']['anzahl'],
];

// Nachbearbeitung Monate: Die letzte Gruppe in die Ausgabedaten schreiben
$monate[] = [
    'wert' => $gruppe['monat']['wert'],
    'anzahl' => $gruppe['monat']['anzahl'],
    'tage' => $tage
];

// Nachbearbeitung Jahre: Die letzte Gruppe in die Ausgabedaten schreiben
$ausgabe[] = [
    'wert' => $gruppe['jahr']['wert'],
    'anzahl' => $gruppe['jahr']['anzahl'],
    'monate' => $monate
];
Ausgabe
// Die Jahre als Liste ausgeben
echo '<ul>';

foreach ($ausgabe as $jahr) {
    echo '<li>';
    printf('%s (%d)', $jahr['wert'], $jahr['anzahl']);
    
    // Die Monate als Liste ausgeben
    echo '<ul>';
    
    foreach ($jahr['monate'] as $monat) {
        echo '<li>';
        printf('%s (%d)', $monat['wert'], $monat['anzahl']);
        
        // Die Tage als Liste ausgeben
        echo '<ul>';
        
        foreach ($monat['tage'] as $tag) {
            echo '<li>';
            printf('%s (%d)', $tag['wert'], $tag['anzahl']);
            echo '</li>';
        }
        
        echo '</ul>';
        echo '</li>';
    }
    
    echo '</ul>';
    echo '</li>';
}

echo '</ul>';
Beachten Sie: Da PHP (ursprünglich) eine Template-Sprache ist, ist es grundsätzlich besser, PHP in HTML zu "schachteln" statt umgekehrt (wie es im obigen Beispiel der Fall ist).

Besser ist es somit etwas wie

<p><?= $var ?></p>

statt

<?php echo '<p>' . $var . '</p>' ?>
zu verwenden.

FAQ - Häufig gestellte Fragen

Im folgenden werden einige Hinweise zum Gruppenwechsel gegeben.

Wann verwende ich einen Gruppenwechsel?

Das Gruppenwechsel-Prinzip wird immer dann verwendet, wenn Daten sequentiell vorliegen (dies ist z. B. meistens bei der Datenhaltung in einer Datenbank der Fall) und nach einer oder mehreren Eigenschaften gruppiert ausgegeben werden sollen.

Was sind die Voraussetzungen für einen Gruppenwechsel?

Die Hauptvoraussetzungen für eine Gruppenwechsel sind, dass die Daten anhand eines oder mehrerer Eigenschaften gruppierbar sind und zudem nach diesen Gruppenkriterien sortiert[1] vorliegen.

Wo sind die Grenzen des Gruppenwechsels?

Wenn die Voraussetzungen für einen Gruppenwechsel gegeben sind gibt es keine Einschränkungen. Allerdings wird ein "klassischer" Gruppenwechsel mit mehr als ca. drei Gruppierungskriterien sehr unübersichtlich. Es bieten sich bei mehreren Eigenschaften, nach den gruppiert werden soll, häufig alternative Vorgehensweisen an.

Was sind Alternativen zum Gruppenwechsel?

Das hängt ganz vom speziellen Anwendungsfall ab. Häufig bieten Funktionen oder Rekursion weitere Möglichkeiten. Zudem besitzen einige Programmiersprachen mächtige Werkzeuge, die einen Gruppenwechsel indirekt ermöglichen (bspw. sind Arrays in PHP sehr mächtig und können im Einzelfall einen expliziten Gruppenwechsel ersetzen).

Hilfe, das geht nicht!

Dass der Gruppenwechsel "nicht funktioniert" kann viele Ursachen haben. Hier ein mögliches Vorgehen zur Fehlersuche:

  1. Debugging, Debugging, Debugging!
  2. Überprüfen, ob die Daten überhaupt anhand eines oder mehrerer Kriterien gruppiert werden können
  3. Überprüfen, ob die Daten nach dem Gruppenkriterium sortiert[1] vorliegen
  4. Überprüfen, ob die Daten der Reihe nach verarbeitet werden
  5. Die Reihenfolge der einzelnen Schritte (Vorlauf – Durchlauf – Nachlauf, vgl. Vorgehensweise) überprüfen, insbesondere bei mehreren Kriterien kann hier schnell einiges durcheinander kommen
  6. Falls die erste oder letzte Gruppe nicht richtig verarbeitet wird:
    1. Variableninitialisierung prüfen
    2. Prüfen, ob die letzte(n) Gruppe(n) (nochmal) abschließend behandelt werden
  7. Im SelfHTML-Forum nach Hilfe fragen

Weblinks

Fußnoten

  1. 1,0 1,1 1,2 1,3 1,4 1,5 1,6 1,7 Sortierung bedeutet in diesem Zusammenhang, dass die zur selben Gruppe gehörenden Datensätze direkt aufeinanderfolgend vorliegen. Die Datensätze müssen darüber hinaus nicht weiter sortiert werden, bspw. ist eine Sortierung von A-Z, 0-9 oder Vergleichbares nicht nötig.
  2. Fahrtverlauf des ICE 681 (15. September 2015)
  3. Zum Zeitpunkt der Beitragserstellung (2015) die häufigsten Schulnamen in Deutschland
  4. Zum Zeitpunkt der Beitragserstellung (2015) die beliebtesten Vornamen in Deutschland