Benutzer:TS/Arrays mal anders herum

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Im SelfHTML-Forum wird immer wieder danach gefragt, wie man diverse unterschiedliche Arrays sortiert, wie man sie zum Speichern von Daten/Datensätzen verwendet, wie man doppelte Einträge vermeidet, wie man einen bestimmten Wert oder Schlüssel in einer tiefen Struktur findet u.v.m.

Dieser Artikel soll dazu anregen es einfach mal „anders herum“ anzufangen, meine Anregungen aus den vielen Threads des Forumsarchivs zu sammeln, neu zu strukturieren und damit allen aufgeschlossenen Programmierern verständlich zugänglich machen.

Und wenn sich ein weiterer Autor finden würde, der den "normalen Weg" im Gegensatz dazu darstellen mag, ist er herzlich eingeladen. Programmierung ist immer auch Philosophie.

Grundlagen

  • was bedeutet überhaupt 'Array' in PHP
  • Sortierung, Indexierung, Reihenfolge
  • iterieren per Index
  • iterieren per Pointer (Reihenfolge in der verketteten Liste)
  • rekursiver Durchlauf des Arrays

Arrays in PHP

PHP unterscheidet im Wesentlichen drei Datentyp-Klassen

  • Skalare (nebst boolescher Werte)
  • Arrays
  • Objekte

Mit Hilfe von Arrays können sowohl Datenstrukturen als auch Steuerflüsse abgebildet, geordnet und verwaltet werden.

Im Unterschied zu den klassischen speicherorientierten Arrays, bei denen gleiche oder gleichartige Elemente angereiht und über ihren Index direkt adressiert werden können, können wir uns PHP-Arrays eher als verkettete Listen und Baumstrukturen vorstellen. Jedes Element kann andere Eigenschaften haben. Der Index ist Bestandteil des Elementes und hat keinerlei Zusammenhang mit dem physischen Speicherort der zugehörigen Daten.

[Bild: verkettete Liste]

[Bild: Baumstruktur]


Für die Werte eines Arrays sind alle Datentypen erlaubt, die PHP kennt. Somit ergibt sich bei einem Array von Arrays auch die Baumstruktur. Wie die Datenorganisation intern geregelt ist, soll hier ausdrücklich nicht im Fokus der Betrachtung stehen.

An die eigentlichen Zeiger auf die Elemente lässt uns PHP nicht heran. Sie sind gekapselt. Durch Navigationsfunktionen kann man aber jedes Element der Struktur erreichen. Somit können wir uns auch nicht aus Versehen im Speicher verlaufen.

Datenstrukturen planen

  • 'lose' Variablen contra Arrays
  • Datensatz
  • Liste
  • Baum

einfache Variablen

PHP ist eine gewachsene Scriptsprache für aktive Internetangebote und hat am Anfang alle [?] Werte in einfachen Variablen im Script zur Verfügung gestellt. Einfache Variablen werden nur durch ein vorangestelltes Dollarzeichen vor einem Bezeichner gekennzeichnet. Untereinander haben sie keine Ordnung.

Es gibt keine benutzerdefinierten Datenstrukturen für Daten, die einen Bezug zueinander haben. 'Struct' oder 'Record' sind PHP fremd. Um Daten aneinander zu binden, stellt PHP den Datentyp 'Array' zur Verfügung.

Datensatz mit Array

Liste mit Array

Baumstruktur mit Array

PHP lässt dem Programmierer bei der Verwendung von Arrays jede Freiheit, was die Datentypen der Elemente und die Anzahl der Unterelemente betrifft. Die geplante Struktur muss in keiner Weise regelmäßig ausfallen.

Beim Umgang mit Massendaten hat man allerdings meistens regelmäßige Strukturen vorliegen. Diese können dann als Bäume abgebildet werden.

Für die hier gemeinten regelmäßigen Bäume kann man zwei Bauarten benutzen:

Baum in Satzstruktur

Baum in Spaltentruktur

Datenstrukturen umwandeln

  • Vom Satzarray zum Spaltenarray


Nützliche Funktionen

Die folgenden Funktionen stellen Denkansätze dar und sollten nach Belieben weiterentwickelt werden.

  • vier universelle Bearbeitungsfunktionen für Spaltenarrays
  • Beispiele zum Umgang mit Spaltenarrays
  • Linearisierung einer Arraystruktur (Erzeugung einer Pfadliste)
  • Bildung tiefer Arraystrukturen aus einer Pfad-Liste

Bearbeitungsfunktionen

Get-Record-Funktion (ähnlich SELECT)

Beispiel
#------------------------------------------------------------------------------
function get_record (&$_data, $recno)
{
    $_rec = array();

    foreach ($_data as $colname => $field)
    {
        $_rec[$colname] = isset($_data[$colname][$recno])?$_data[$colname][$recno]:NULL;
    }

    return $_rec;
}
#------------------------------------------------------------------------------
Die Funktion übernimmt als Argumente
  • &$_data eine Referenz auf das Daten-Array
  • $recno die Datensatznummer (Primary Key)

In der foreach()-Schleife werden die vorhandenen Spalten gesucht und dann festgestellt, ob es einen Feldwert zur Datensatznummer gibt. Die Treffer werden im Ergebnisarray $_rec gesammelt.

Für den Fall, dass es in einer Spalte keinen Eintrag für den Schlüssel $recno gibt, wird im Ergebnisdatensatz NULL als Wert im zugehörigen Datenfeld eingetragen.

Delete-Record-Funktion (ähnlich DELETE)

Beispiel
#------------------------------------------------------------------------------
function delete_record (&$_data, $recno)
{
    $cols_deleted = 0;

    foreach ($_data as $colname => $field)
    {
        unset($_data[$colname][$recno]);
        $cols_deleted++;
    }

    return $cols_deleted;
}
#------------------------------------------------------------------------------
Die Funktion übernimmt als Argumente
  • &$_data eine Referenz auf das Daten-Array
  • $recno die Datensatznummer (Primary Key)

In der foreach()-Schleife werden die vorhandenen Spalten gesucht und dann die Feldwerte zur Datensatznummer gelöscht. Die Funktion unset() liefert keinen Fehler, wenn das geforderte Element nicht existiert hat, ein if (isset(...)) ist daher nicht notwendig.

Der Rückgabewert zeigt hier lediglich die Anzahl der gefundenen Spalten an. Diese hat hier aber keinen Aussagewert darüber, ob die Spalte in diesem Datensatz auch belegt war.

Update-Record-Funktion (ähnlich UPDATE)

Beispiel
#------------------------------------------------------------------------------
function update_record(&$_data, $recno, $_record, $expand=false)
{
    $cols_updated = 0;

    foreach ($_record as $colname => $data)
    {
        if ($expand or isset($_data[$colname][$recno]))
        {
            $_data[$colname][$recno] = $data;
            $cols_updated++;
        }
    }

    return $cols_updated;
}
#------------------------------------------------------------------------------


Insert-Record-Funktion (ähnlich INSERT, REPLACE, ALTER)

Beispiel
#------------------------------------------------------------------------------
function insert_record(&$_data, $recno, $_record, $expand=false, $overwrite=false)
{
    $cols_inserted = 0;

    if (!$overwrite)
    {
        foreach($_record as $colname => $data)
        {
            if (isset($_data[$colname][$recno]))
            {
                return 0;
            }
        }
    }

    foreach ($_record as $colname => $data)
    {
        if ($expand or isset($_data[$colname]))
        {
            $_data[$colname][$recno] = $data;
            $cols_inserted++;
        }
    }

    return $cols_inserted;
}
#------------------------------------------------------------------------------
Die Funktion übernimmt in $_record ein Array mit einem Datensatz und trägt diesen unter der $recno in das Array $_data ein. Bleibt $overwrite==false, wird der Eintrag nur dann vorgenommen, wenn noch kein Index mit dem Wert $recno im Array $_data vorhanden ist. Setzt man $expand==true, werden diejenigen Spalten, die im Datensatz zusätzlich vorhanden sind, in $_data angelegt.

Zeilenarray zum Spaltenarray umbauen

Beispiel
$_spaltenarr = array();

foreach ($_zeilenarr as $key => $_record)
{
    insert_record($_spaltenarr, $key, $_record, true)
}
Erläuterungen


Diverse Funktionen

Musterdaten

Beispiel
<?php  ### musterdaten.php ### utf-8 ### ÄÖÜäöü

$_sparr = array();
$_sparr['vorname'] = array();
$_sparr['name'] = array();
$_sparr['strasse'] = array();
$_sparr['ort'] = array();
$_sparr['date'] = array();

$_sparr['vorname'][1] = 'Thomas';
$_sparr['name'][1] = 'Tollwut';
$_sparr['strasse'][1] = 'Tollkirschenweg';
$_sparr['ort'][1] = 'Torberg';

$_sparr['vorname'][2] = 'Michaela';
$_sparr['name'][2] = 'Mustermann';
$_sparr['strasse'][2] = 'Moorhuhnsiedlung';
$_sparr['ort'][2] = 'München';
$_sparr['telefon'[2] = '080/555333-0';

$_sparr['vorname'][3] = 'Paula';
$_sparr['name'][3] = 'Pingel';
$_sparr['strasse'][3] = 'Prachtallee';
$_sparr['ort'][3] = 'Petersburg';

$_sparr['vorname'][4] = 'Karel';
$_sparr['name'][4] = 'Gott';
$_sparr['strasse'][4] = 'An der Stadthalle';
$_sparr['ort'][4] = 'Prag';

$_sparr['vorname'][5] = 'Schlomo';
$_sparr['name'][5] = 'Freud';
$_sparr['strasse'][5] = 'Universitätspark';
$_sparr['ort'][5] = 'Wien';
$_sparr['telefon'][5] = '0815/4711-110';

?>

Spaltenarray zum Zeilenarray umbauen

Beispiel
<?php  ### col2row.php ### utf-8 ### ÄÖÜäöü

header('content-type: text/html; Charset=utf-8');
include musterdaten.php;

#===============================================================================
function array_col2row($_data)
{
    $_rows = Array();

    foreach ($_data as $col => $_values)
    {
        if (is_array($_values))
	{
            foreach($_values as $key => $param)
	    {
		$_rows[$key][$col] = $param;
	    }
	}
    }
	
    return $_rows;
}

#===============================================================================

echo "<pre>\r\n";
echo htmlspecialchars(print_r(array_col2row($_sparr),1)) . "\r\n";
echo "</pre>\r\n";	

?>


Sortierte Ausgabe

Sortier-Typen laut PHP-Handbuch [[1]]

  • SORT_REGULAR - vergleiche Einträge normal (ohne die Typen zu ändern)
  • SORT_NUMERIC - vergleiche Einträge numerisch
  • SORT_STRING - vergleiche Einträge als Strings
  • SORT_LOCALE_STRING - vergleiche Einträge als Strings, basierend auf den aktuellen Locale-Einstellungen. Wurde in PHP 4.4.0 und 5.0.2 hinzugefügt. Es wird die System-Locale benutzt, die mittels setlocale() geändert werden kann.
  • SORT_NATURAL - Einträge in natürlicher Ordnung sortieren, wie bei natsort()
  • SORT_FLAG_CASE - Kann kombiniert werden (|, bitweises ODER) mit SORT_STRING oder SORT_NATURAL um Strings ohne Beachtung von Versalien und Gemeinen zu sortieren.



Beispiel
<?php  ### sorted_output.php ### utf-8 ### ÄÖÜäöü

header('content-type: text/html; Charset=utf-8');
include musterdaten.php;

echo "<pre>\r\n";

asort($_sparr['name'],SORT_NATURAL);
asort($_sparr['ort'],SORT_NATURAL);
asort($_sparr['strasse'],SORT_NATURAL);
asort($_sparr['vorname'],SORT_NATURAL);


echo "-------------------------------------\r\n";
echo "Ausgabe sortiert nach [name]:\r\n";
foreach($_sparr['name'] as $key => $param)
{
    echo "\r\nSatz Nr $key:\r\n";
    foreach($_sparr as $name => $_col)
    {
        if (isset($_sparr[$name][$key]))
            echo htmlspecialchars("[$name] => {$_sparr[$name][$key]}") . "\r\n";
    }
}

echo "-------------------------------------\r\n";
echo "Ausgabe sortiert nach [vorname]:\r\n";
foreach($_sparr['vorname'] as $key => $param)
{
    echo "\r\nSatz Nr $key:\r\n";
    foreach($_sparr as $name => $_col)
    {
        if (isset($_sparr[$name][$key]))
            echo htmlspecialchars("[$name] => {$_sparr[$name][$key]}") . "\r\n";
    }
}

echo "-------------------------------------\r\n";
echo "Ausgabe sortiert nach [ort]:\r\n";
foreach($_sparr['ort'] as $key => $param)
{
    echo "\r\nSatz Nr $key:\r\n";
    foreach($_sparr as $name => $_col)
    {
        if (isset($_sparr[$name][$key]))
            echo htmlspecialchars("[$name] => {$_sparr[$name][$key]}") . "\r\n";
    }
}

echo "</pre>\r\n";	

?>

Funktion für die einfache Sortierung

Beispiel
<?php   ### array_get_sorted.php ### utf-8 ### ÄÖÜäöü

#===============================================================================
function array_get_sortet(&$_data, $sortkey, $sorttype = SORT_NATURAL)
{
    if (!isset($_data[$sortkey])) return false;
    asort($_data[$sortkey], $sorttype);
    $_sortet = false;
	
    foreach($_data[$sortkey] as $key => $param)
    {
	foreach($_data as $name => $_col)
	{
	    $_rec[$name] = (isset($_data[$name][$key])?$_data[$name][$key]:NULL);
	}
	$_sortet[$key] = $_rec;
    }
	
    return $_sortet; 
}

#===============================================================================
# php unit test
#===============================================================================

header('content-type: text/html; Charset=utf-8');

include musterdaten.php;

echo "<pre>\r\n";
echo htmlspecialchars(print_r(array_get_sortet($_sparr, 'telefon'),1));
echo "</pre>\r\n";	


?>


Ergebnis

Beispiel
Array
(
    [2] => Array
    (
        [vorname] => Michaela
        [name] => Mustermann
        [strasse] => Moorhuhnsiedlung
        [ort] => München
        [telefon] => 080/555333-0
        [date] => 
    )

    [5] => Array
    (
        [vorname] => Schlomo
        [name] => Freud
        [strasse] => Universitätspark
        [ort] => Wien
        [telefon] => 0815/4711-110
        [date] => 
    )
)
Führt man mit der Funktion array_get_sorted() eine Sortierung über eine Spalte durch, werden in das Ergebnisarray nur diejenigen Datensätze aufgenommen, die auch ein Element mit dem Bezeichner (Sortierspalte) enthalten. Einen Wert muss dieses Element nicht enthalten. Sortiert wird hier die angebene Spalte des Originalarrays, da es als Referenz an die Funktion übergeben wird. Wenn man das nicht will, muss man die Referenzanweisung & aus dem Funktionskopf entfernen.

Linearisierung tiefer Arrays

Manchmal ist es notwendig, alle Elemente tiefer Baumstrukturen zu kennen. Hierzu kann man die Pfade zu den Elementen auflösen und jeweils als einzeiligen String darstellen. Jedes Element des Baumes bekommt damit eine Zeile in einer Liste.

Beispiel
#------------------------------------------------------------------------------
    function linearize($_source, &$_target, $path='')
    {
        if (is_array($_source))
        {
            foreach($_source  as $key => $val)
            {
                linearize($val, &$_target, $path.'.'.$key);
            }    
        }
        else
        {
            $_target[] = ltrim($path,'.');
        }
    }
#------------------------------------------------------------------------------

Aus dem Beispielarray

    $_testarr = array();

    $_testarr['vorname'][0] = 'Hans';
    $_testarr['vorname'][1] = 'Klaus';
    $_testarr['vorname'][2] = 'Peter';
    $_testarr['name']       = 'Mustermann';
    $_testarr['plz']        = '37444';
    $_testarr['interessen']['privat'][0] = 'lesen';
    $_testarr['interessen']['privat'][1] = 'Fußball';
    $_testarr['interessen']['beruflich'] = 'PHP';

wird durch Anwendung der obigen Funktion die folgende Liste:

    [0] => vorname.0
    [1] => vorname.1
    [2] => vorname.2
    [3] => name
    [4] => plz
    [5] => interessen.privat.0
    [6] => interessen.privat.1
    [7] => interessen.beruflich

die wiederum als Array (jedoch nur als "eindimensionales") gespeichert wird.

Selbstverständlich kann man einen anderen "Trenner" benutzten und auch die Werte mit in die Liste aufnehmen. Der Fantasie sind da keine Grenzen gesetzt. Wichtig ist dabei nur, dass der Trenner nicht in den ursprünglichen Bezeichnern vorkommen darf und dass die Werte keine schädlichen Zeichen mehr enthalten. Diese Regeln gelten in ähnlicher Form bei CSV-Dateien.

Tiefes Array (Baumstruktur) aus Pfadliste erstellen

Aus einer solchen Zeile der Pfadliste kann mit mit der folgenden Funktion wieder ein tiefes Array erstellen. Durch eine Schleife über die Liste und Addition der tiefen Arrays kann das Gesamtarray wieder erzeugt werden.


Beispiel
#------------------------------------------------------------------------------
    function make_deep_array($array_pathstr, $value=NULL, $sep='.')
    {
        $_arr = array();
        $array_pathstr = trim($array_pathstr, $sep);
        $_ref =& $_arr;

        while (strlen($array_pathstr) > 0)
        {
            $path = strtok($array_pathstr, $sep); 
            $array_pathstr = substr($array_pathstr,strlen($path)+1);
            $_ref[$path] = NULL;
            $_ref =& $_ref[$path];
        }
     
        $_ref = $value;
     
        return $_arr;
    }
#------------------------------------------------------------------------------

Mit ein wenig Überlegung kann man sich die Funktionen selbstverständlich auch so umbauen, dass nicht nur die Pfade, sondern auch die Werte mit berücksichtigt werden.

Die "Name-Value"-Pärchen kann man dann auch bequem in einer Datei abscpeichern, und so eine incrementelle Serialisierung von Arrays realisieren. Im Gegensatz zu PHPs mit serialize()/unserialize() erzeugten Strukturen, kann man eine derartige Liste auch partiell einlesen und verarbeiten. Die Listen unterliegen aber auch festen Regeln, die man streng beachten muss, wenn man keine Datenverluste erleiden will.

Arrays und Dateien

Über das Holen und Wegschreiben von Daten

  • Von CSV zum Spaltenarray und zurück
  • Serialisierung mit serialize()