Traverzování kolem stromu #2
Traverzování kolem stromu je jeden ze způsobů, jak ukládat stromovou strukturu dat v relační databázi. V tomto článku si ukážeme jak lze z vytvořeného stromu mazat uzly a jak můžeme jednotlivé uzly mezi sebou přesouvat.
Článek volně navazuje na předchozí článek „Traverzování kolem stromu #1“, ve kterém byly vysvětleny základní principy algoritmu a ukázali jsme si základní principy vytváření stromu. Pokud jste článek nečetli, doporučuji vám si jej přečíst.
Máme tedy v databázi, ke které se připojujeme, vytvořenou tabulku, a v ní uloženou strukturu dat. U každého prvku (uzlu) máme uložený pravý a levý index a index vnoření ve stromu.
Mazání uzlů ze stromu
Traverzování kolem stromu je algoritmus velice příjemný, pokud se na něj podíváme z pohledu výběru dat. ovšem z pohledu editace stromu nelze téměř žádnou operaci zvládnout jedním SQL dotazem.
Stejně tak je tomu i u mazání uzlů ze stromu. Není možné jenom jednoduše vymazat jeden řádek z tabulky, protože kdybychom tak učinili, nesouhlasili by pravé a levé index. Důležité tedy je, abychom po vymazání každého uzlu zase zkontrolovali index a přečíslovali strom.
Metoda, kterou si vytvoříme jako první, bude kontrolovat, zda v uzlu, který chceme vymazat, nejsou žádné další uzly. Samozřejmě by nebyl problém mazat i celé části stromu, ale je to naprosto zbytečné. Pokud chceme smazat jednu celou část stromu, můžeme ji odmazat po jednotlivých uzlech.
Naše třída bude tedy umožňovat mazat pouze ty uzly, které se již dále nevětví, to znamená, jenom ty, které jsou na konci stromu.
Metoda pro ověření, zda se uzel dále nevětví, bude velice jednouchá. Princip spočívá v tom, že budeme znát pravý a levý index uzlu, který mažeme, a musíme zjistit, zda neexistuje uzel, který má levý index v rozmezí těchto dvou indexů. V metodě tedy musíme znát jak pravý, tak levý index uzlu, který mažeme.
Levý index bude vstupní parametr celé metody pro mazání uzlu a pravý si zjistíme pomocí metody „select_parent“, kterou jsme vytvořili v minulém článku.
Známe tedy oba dva indexy a konečně můžeme vytvořit metodu:
1 2 3 4 5 6 7 8 9 10 11 |
table_name . " WHERE ( traverz_left BETWEEN '" . $left . "' AND '" . $this->traverz_right . "' ) AND lang = '" . $_SESSION['lang_prefix'] . "'"; $num = intval( @ mysql_result(mysql_query($select, $this->link), 0, 0) - 1 ); return ( $num == 0 ? 1 : 0 ); } |
Metoda _chekcTree má návratovou hodnotu 1 nebo 0, a to v případě nalezení, či nenalezení uzlů. Další metoda, kterou budeme potřebovat, bude upravovat pravý a levý index po vymazání.
V minulém článku o traverzování jsme si vytvořili metodu „_update_tree($left)“, která se starala o přečíslování indexů po přidání uzlu. Jistě již tušíte jak bude vypadat metoda pro přečíslování po vymazání. Metoda bude téměř identická, jenom s tím rozdílem, že namísto toho abychom indexy zvyšovali je budeme nyní snižovat:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function _renumberTree($left){ $update[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left - 2) WHERE ( traverz_left > '" . intval( $left ) . "') AND lang = '" . $_SESSION['lang_prefix'] . "'"; $update[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right - 2) WHERE ( traverz_right > '" . intval( $left + 1 ) . "') AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($update as $value){ mysql_query( $value, $this->link ); if( mysql_affected_rows( $this->link ) < 0 ) return 0; } return 1; } |
Zase potřebujeme v metodě znát oba indexy, abychom mohli přečíslovat jenom správné uzly. Přečíslujeme jenom ty, které mají větší index než smazaný uzel.
Nyní, když máme vytvořeny metody pro kontrolu a přečíslování stromu, můžeme konečně mazat uzly. Před mazáním budeme kontrolovat, zda se uzel dále nevětví a těsně před mazáním přečíslujeme strom. Tuto operaci provedeme před mazáním z toho důvodu, že kdyby přečíslování neproběhlo správně (metoda _renumberTree vrací 0), nedojde ke smazání:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function deleteTree( $left ){ $left = intval( $left ); $parent = self::select_parent($left, 1); if(self::_checkTree( $left ) == 0) return -1; if( self::_renumberTree($left) == 0 ) return 0; $delete = "DELETE FROM " . $this->table_name . " WHERE id_traverz = '" . $this->id_traverz . "' LIMIT 1;"; mysql_query($delete, $this->link); return ( mysql_affected_rows( $this->link ) == 0 ? 0 : 1 ); } |
Metoda vrací v případě úspěchu hodnotu 1. V případě, že existují další uzly, které jsou vnořené v mazaném uzlu, vrací hodnotu -1. V ostatních případech vrací hodnotu 0, která symbolizuje neúspěch.
Přesouvání uzlů ve stromu
Přesouvání uzlů je asi ta nejsložitější operace, kterou budeme ve stromu provádět. Pro přesouvání dvou uzlů budeme muset znát jejich levé indexy. Levé indexy budou přímo vstupními parametry metody pro přesouvání. Tyto vstupní parametry musíme zkontrolovat, zda mají stejnou úroveň vnoření. Pokud nebude tato skutečnost splněna, nebude se přesouvat. Budeme tedy zase omezeni na to, že dva uzly, které přesouváme, musí mít stejnou úroveň vnoření.
Levý index známe, neznáme ovšem pravý index a úroveň vnoření. Jako první si tedy vytvoříme metodu pro zjištění těchto hodnot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function _select_parent_check($left1, $left2){ $select = "SELECT traverz_parent, traverz_right FROM " . $this->table_name . " WHERE traverz_left IN ('" . $left1 . "', '" . $left2 . "') AND lang = '" . $_SESSION['lang_prefix'] . "' ORDER BY traverz_left;"; if( ( $data = mysql_query($select, $this->link) ) == FALSE ){ return 0; } return array( @mysql_result($data, 0, 0), @mysql_result($data, 1, 0), @mysql_result($data, 0, 1), @mysql_result($data, 1, 1) ); } |
Metoda vrací pole, ve kterém jsou uloženy pravý index a úroveň vnoření obou uzlů. Tuto metodu voláme další metodou, kterou potřebujeme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function _checkingTree($left1, $left2){ $parents = self::_select_parent_check($left1, $left2); if( sizeof($parents) != 4 ) return 0; else if( $parents[0] != $parents[1] ) return 0; $select = "SELECT traverz_parent, traverz_left, traverz_right FROM " . $this->table_name . " WHERE traverz_left < '" . ( $left1 < $left2 ? $left1 : $left2) . "' AND traverz_parent = '" . ($parents[0] - 1) . "' AND lang = '" . $_SESSION['lang_prefix'] . "' ORDER BY traverz_left DESC LIMIT 1;"; $data = mysql_query($select, $this->link); if( mysql_error() != NULL ) return 0; $h1 = @mysql_result($data, 0, 1); $h2 = @mysql_result($data, 0, 2); //test left1 if( !( ( $h1 < $left1 ) && ( $h2 > $left2 ) ) ){ return 0; } //test left2 else if( !(($h1 < $left2) && ($h2 > $left2)) ){ return 0; } else { return $parents; } } |
Metoda _checkingTree kontroluje hodnoty vnoření a navíc kontroluje indexy. Návratová hodnota je pole, které obsahuje hodnoty z metody _select_parent_check.
Nyní známe všechny potřebné hodnoty pro to, abychom mohli dva uzly mezi sebou přesunout. Při přesouvání nám může vzniknou několik situací:
První z nich nastane v případě že jsou dva uzly hned vedle sebe a neobsahují další vnořené uzly. Tento případ je nejjednodušší a stačí pouze přehodit indexy u těchto uzlů.
Další případ nastane v případě, že dva uzly jsou vedle sebe, ale mají v sobě vnořené další uzly. V tomto případě musíme navíc změnit indexy i na uzlech, které jsou uvnitř uzlů, které přesouváme. Je to logické, protože pravý a levý uzel nemusí obsahovat stejný počet vnořených uzlů a tím pádem musí být změněny všechny indexy.
Nejsložitější případ nastane v situaci, kdy oba uzly které přesouváme obsahují další uzly a navíc jsou další uzly mezi nimi. V tomto případě musíme změnit indexy ve všech poduzlech pravého uzlu, ve všech poduzlech levého uzlu a nakonec ještě změnit indexy ve všech uzlech, které jsou mezi těmito dvěma uzly.
Na všechny tyto případy musíme brát ohled a všechny tyto případy řádně ošetři. Nyní máme vytvořeny pouze
metody pro zjištění informací, které potřebujeme pro přesun.
Ti chytřejší si přesun dodělají sami, ostatní si musí počkat na další článek.
Na závěr se ještě podívejme co už máme hotovo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
class traverz extends connect{ public $traverz_right = NULL; public $id_traverz = NULL; public $parametrs = array(); public $ids = array(); public $table = NULL; function __construct( $table_name ){ $this->table_name = $table_name; parent::__construct(); self::_check_num_of_rows(); } function _check_num_of_rows(){ $select = "SELECT COUNT(*) FROM " . $this->table_name . " WHERE lang = '" . $_SESSION['lang_prefix'] . "'"; if( mysql_result( mysql_query( $select, $this->link ), 0, 0) == 0 ){ self::_insert_first(); return 0; } else{ return 0; } } //Insert HOME into Traverz_table function _insert_first(){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( 'HOME', '0', '1', '2', '" . $_SESSION['lang_prefix'] . "', 'home' )"; mysql_query($insert, $this->link); return ( mysql_affected_rows($this->link) == 1 ? 1 : 0); } function _update_tree($id, $left){ $sql[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left + 2) WHERE ( traverz_left > '" . $left . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; $sql[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right + 2) WHERE ( traverz_right > '" . ($left - 1) . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($sql as $value){ mysql_query($value, $this->link); if(mysql_affected_rows($this->link) == 0 ) return 0; } } function select_parent($left, $info = 0){ $select = "SELECT traverz_parent, traverz_right, id_traverz FROM " . $this->table_name . " WHERE traverz_left = '" . $left . "' AND lang = '" . $_SESSION['lang_prefix'] . "' LIMIT 1;"; $data = mysql_query($select, $this->link); if( $info == 1 ){ $this->traverz_right = @mysql_result($data, 0, 1); $this->id_traverz = @mysql_result( $data, 0, 2 ); } return ( @mysql_result($data, 0, 0) + 1 ); } function _setup_position( $position ){ switch( strtolower( $position ) ){ case 'h' : return 0; break; case 'e' : return 1; break; default: return 0; } } function addTree ( $name, $left, $position = 0 ){ if( gettype( $position ) == 'string' ) $position = self::_setup_position( $position ); $position = abs( intval( $position) ); if( $position > 2 ) return 0; $parent = self::select_parent($left, 1); switch( $position ){ case 0 : { if( self::_addTree_h( $name, $left, $parent ) == 1 ) return 1; else return 0; break; } case 1 :{ if( self::_addTree_e( $name, $left, $parent, ( $this->traverz_right - 1 ) ) == 1 ) return 1; else return 0; break; } } return 1; } function _addTree_h( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( '" . trim( htmlspecialchars($name) ) . "', '" . intval( $parent ) . "', '" . ( intval( $left ) + 1 ) . "', '" . ( intval( $left ) + 2 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $left) == 1 ? 1 : 0 ); else return 0; } function _addTree_e( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang) VALUES( '" . trim( htmlspecialchars( $name ) ) . "', '" . intval( $parent ) . "', '" . intval( $this->traverz_right ) . "', '" . ( intval( $this->traverz_right ) + 1 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $this->traverz_right) == 1 ? 1 : 0 ); else return 0; } } |
Pekny clanek. Tesim se na pokracovani.
Jsem moc vdecny za tento clanek, resp za celou serii. Prave podle toho ted implementuji svou verzi.
Nicmene mam dotaz. Proc overujeme zda je prvek na konci stromu (u mazani) slozite pres SQL dotaz. Neplati copak pro vsechny koncove prvky, ze jejich hodnota RIGHT je prave o jedno vyssi nez hodnota LEFT (za predpokladu, ze strom ve strome neni chyba)? Mozna jsem neco prehledl.