Questo helper deriva naturalmente dalla funzione descritta nel precedente post.P4A mette a disposizione un widget chiamato P4A_DB_Navigator che si basa su archivi di tipo albero a liste di adiacenza e fornisce un output grafico della lista dei nodi, con un buon grado di interattività. Esiste, per esempio, un metodo che si chiama getPath() che restituisce in un array, il percorso dalla root al nodo fornito in input attraverso la chiave primaria. Purtroppo però, non esiste nessun metodo per estrarre l’insieme di nodi figli da un certo nodo parentale, così ho pensato di adottare la funzione presentata nel precedente post.
I parametri di input sono:
$navigator – l’oggetto da cui discende (implicito)
$id – l’identificativo del nodo da cui partire
$table – il nome della tabella che rappresenta l’albero
$pk – il nome della chiave primaria
$recursor – il nome del campo che identifica il nodo parentale
// File: P4A_db_navigator_getBranch.php
<?phpfunction P4A_db_navigator_getBranch ($navigator,$params){list($id,$table,$pk,$recursor)=$params;$arr= p4a_db::singleton()->getAll("SELECT * FROM $table");$pksArr=array();foreach($arras$rec){$pksArr[$rec[$pk]]=$rec[$recursor];}$branchIds=array($id);$i=0;while($i<count($branchIds)){$newKeys=array_keys($pksArr,$branchIds[$i]);if(!empty($newKeys)){foreach($newKeysas$newKey){array_push($branchIds,$newKey);}}++$i;}$res=array();foreach($arras$child){if(in_array($child[$pk],$branchIds)){$res[]=$child;}}return$res;}?>
// File: P4A_db_navigator_getBranch.php
<?php
function P4A_db_navigator_getBranch ($navigator, $params)
{
list($id, $table, $pk, $recursor) = $params;
$arr = p4a_db::singleton()->getAll("SELECT * FROM $table");
$pksArr = array();
foreach ($arr as $rec) {
$pksArr[$rec[$pk]]=$rec[$recursor];
}
$branchIds = array($id);
$i=0;
while ($i<count($branchIds)) {
$newKeys = array_keys($pksArr,$branchIds[$i]);
if (!empty($newKeys)) {
foreach ($newKeys as $newKey){
array_push($branchIds, $newKey);
}
}
++$i;
}
$res = array();
foreach ($arr as $child) {
if (in_array($child[$pk], $branchIds)) {
$res[] = $child;
}
}
return $res;
}
?>
Come esempio ho costruito una semplicissima maschera nella quale, ogni volta che viene selezionato un nodo, viene mostrato un messaggio con l’array dei nodi figli.
Supponiamo di avere un archivio con una struttura ad albero di tipo a liste di adiacenza. Ogni record sarà necessariamente caratterizzato da un identificativo univoco e da un attributo che serve a riconoscere il proprio genitore. Per esempio, nella figura accanto, l’elemento 4 avrà id = 4, e parentId = 1, l’elemento 9 avrà id = 9, e parentId = 4, e così via. Questo modello offre alcuni vantaggi, che sono principalmente la semplicità della struttura e la velocità negli inserimenti. D’altro canto, risultano piuttosto onerosi processi come l’estrazione o la cancellazione di interi rami. Le query risultano quindi, complesse e spesso ricorsive.
Sebbene l’utilizzo di una struttura di tipo nested tree model ideata da Joe Celko sia quasi sempre preferibile, alle volte è necessario cimentarsi con gli algoritmi per la manipolazione delle liste di adiacenza.
Se si deve salire da una foglia fino alla root, non è troppo difficile, basta risalire, attraverso il parentId, il genitore finché parentId = NULL. Il procedimento naturalmente ricorsivo, per questo motivo spesso si indica l’attributo parentId anche con il nome di ricorsore.
Il gioco si fa duro quando si deve estrarre un ramo a partire da un certo genitore… ma come disse John Belushi: “When the going gets tough, the toughs get going!”. Vediamo come fare evitando di farci del male con stored procedure, query ricorsive o tabelle temporanee. La soluzione che ho adottato prende spunto dagli algoritmi di attraversamento dei grafi: Breadth-first search e Depth-first search. Questi sono procedimenti non ricorsivi che utilizzano stack di tipo FIFO e LIFO per l’esplorazione dei nodi.
L’operazione di fetching da un archivio che rappresenta l’albero della figura in alto produrrà un’array di questo tipo:
Ammettiamo ora di voler estrarre l’intero ramo che ha come radice l’elemento 4. La funzione seguente utilizza un nuovo array che viene costruito utilizzando come chiave gli id e come valori i parent_id. Questo ci permette di utilizzare la funzione PHP: array_keys() per le ricerche dei figli, che è piuttosto efficiente.
function getBranch ($arr,$pk,$recursor,$idBranch){$pksArr=array();foreach($arras$rec){$pksArr[$rec[$pk]]=$rec[$recursor];}$branchIds=array($idBranch);$i=0;while($i<count($branchIds)){$newKeys=array_keys($pksArr,$branchIds[$i]);if(!empty($newKeys)){foreach($newKeysas$newKey){array_push($branchIds,$newKey);}}++$i;}$res=array();foreach($arras$child){if(in_array($child[$pk],$branchIds)){$res[]=$child;}}return$res;}
function getBranch ($arr,$pk,$recursor,$idBranch)
{
$pksArr = array();
foreach ($arr as $rec) {
$pksArr[$rec[$pk]]=$rec[$recursor];
}
$branchIds = array($idBranch);
$i=0;
while ($i<count($branchIds)) {
$newKeys = array_keys($pksArr,$branchIds[$i]);
if (!empty($newKeys)) {
foreach ($newKeys as $newKey){
array_push($branchIds, $newKey);
}
}
++$i;
}
$res = array();
foreach ($arr as $child) {
if (in_array($child[$pk], $branchIds)) {
$res[] = $child;
}
}
return $res;
}
Certo, il codice di questa funzione non è troppo ottimizzato: utilizza tre array (anche se $pksArr e $branchIds sono monodimensionali e quindi “leggeri”) ed esegue tre cicli. In particolare il ciclo (righe 19-23) è piuttosto lento, ma è necessario se si vuole recuperare tutti gli attributi degli elementi estratti oltre agli indispensabili id già presenti in $branchIds. Dalle prove che ho fatto, per archivi fino a 10-15000 records risulta comunque abbastanza efficiente.
Questo è il risultato dell’estrazione del ramo con radice 4 (si nota che l’andamento è di tipo “breadth-first-search” ricerca in ampiezza, seguendo i colori: rosso-verde-blu-giallo):
Personalmente, ho una certa ritrosia nell’uso di funzioni ricorsive, per questo ho perso del tempo a cercare soluzioni alternative e cicliche, ciò non toglie che, ad esempio per recuperare il percorso di una foglia (risalendo quindi verso la root), la ricorsione sia la soluzione più adatta.
Questo sito Web utilizza i cookie in modo che possiamo fornirti la migliore esperienza utente possibile. Le informazioni sui cookie sono memorizzate nel tuo browser ed eseguono funzioni come riconoscerti quando ritorni sul nostro sito Web e aiutare il nostro team a capire quali sezioni del sito Web trovi più interessanti e utili.
This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.
Cookie strettamente necessari
I cookie necessari contribuiscono a rendere fruibile il sito web abilitandone funzionalità di base quali la navigazione sulle pagine e l'accesso alle aree protette del sito. Il sito web non è in grado di funzionare correttamente senza questi cookie.
Se disabiliti questo cookie, non saremo in grado di salvare le tue preferenze. Ciò significa che ogni volta che visiti questo sito web dovrai abilitare o disabilitare nuovamente i cookie.
Cookie di terze parti
Questo sito web utilizza Google Analytics per raccogliere informazioni anonime come il numero di visitatori del sito e le pagine più popolari.
Mantenere questo cookie abilitato ci aiuta a migliorare il nostro sito web.
This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.
Keeping this cookie enabled helps us to improve our website.
Attiva i cookie strettamente necessari così da poter salvare le tue preferenze!