Benutzer:TS/Arrays mal anders herum
Informationen zum Autor
- Name:
- Thomas Schmieder
- E-Mail:
- selfhtml@bitworks.de
- Homepage:
- bitworks.de
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.
Inhaltsverzeichnis
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)
#------------------------------------------------------------------------------
function get_record (&$_data, $recno)
{
$_rec = array();
foreach ($_data as $colname => $field)
{
$_rec[$colname] = isset($_data[$colname][$recno])?$_data[$colname][$recno]:NULL;
}
return $_rec;
}
#------------------------------------------------------------------------------
Delete-Record-Funktion (ähnlich DELETE)
#------------------------------------------------------------------------------
function delete_record (&$_data, $recno)
{
$cols_deleted = 0;
foreach ($_data as $colname => $field)
{
unset($_data[$colname][$recno]);
$cols_deleted++;
}
return $cols_deleted;
}
#------------------------------------------------------------------------------
- &$_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.
Update-Record-Funktion (ähnlich UPDATE)
#------------------------------------------------------------------------------
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)
#------------------------------------------------------------------------------
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;
}
#------------------------------------------------------------------------------
Zeilenarray zum Spaltenarray umbauen
$_spaltenarr = array();
foreach ($_zeilenarr as $key => $_record)
{
insert_record($_spaltenarr, $key, $_record, true)
}
Diverse Funktionen
Musterdaten
<?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
<?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.
<?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
<?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
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] =>
)
)
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.
#------------------------------------------------------------------------------
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.
#------------------------------------------------------------------------------
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()
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.