Τελικά, με την βοήθεια "pointers" η δουλειά μπορεί να γίνει με βρόχο. Το πρόβλημα, όμως, είναι ότι εν γένει δε θέλει κανείς πάντα να διαβάζει
όλα τα στοιχεία. Αντ' αυτού θα ήταν προτιμότερο να ζητάει κανείς έναν κατάλογο και να του επιστρέφεται μόνο ο κατάλογος και οι υποκατάλογοί του.
Αυτό, όμως, που σκέφτηκα είναι ότι μπορεί κανείς να γλιτώσει την δαπανηρή αναδρομή κάνοντας έναν μικρό μετασχηματισμό στα δεδομένα ώστε να μπορούν να διαβαστούν με βρόχο.
Η ιδέα είναι η εξής: Χρειάζομαι ένα virtual folder tree. Όλα κι όλα τα folders (σε όποια σχέση μεταξύ τους κι αν βρίσκονται) δεν ξεπερνάνε π.χ. τα 50, δηλ. μιλάμε για μικρό αριθμό, οπότε δεν θα υπάρχει μεγάλη επιβάρυνση σε μνήμη αν προστεθούν κάποια δεδομένα θέσης και διάταξης για κάθε κατάλογο. Επιπλέον, γίνεται περίπου 1 αλλαγή στον πίνακα (π.χ. καταχώρηση, τροποποίηση, διαγραφή directory) για κάθε 10.000 αναγνώσεις.
Συνεπώς αν η περίπτωση του βρόχου είναι ταχύτερη και ευκολότερη, αλλά η μετατροπή σε βρόχο στοιχείζει μέχρι μερικές χιλιάδες φορές περισσότερο από την αναδρομή, τότε νομίζω αξίζει.
Μετά απο μια εγγραφή/τροποποίηση/διαγραφή στο table των folders, η διαδικασία "γραμμικοποίησης" μοιάζει με την παρακάτω. Τουλάχιστον όπως την έχω σκεφτεί, αν γνωρίζετε κάτι καλύτερο είμαι ανοιχτός, αν και το κόστος είναι μικρό, γιατί αυτό εκτελείται σπάνια.
Παρατήρηση: Όπως αναφέρω παραπάνω, η σειρά με την οποία έρχονται τα folders από την είσοδο (π.χ. mysql) είναι τυχαία, συνεπώς δεν μπορεί κανείς να υποθέσει ότι θα έρχονται πρώτα τα πατρικά folders και μετά τα "παιδιά" τους. Γι' αυτό ο πρώτος βρόχος διαβάζει την είσοδο και προετοιμάζει μια στοιχειώδη δενδροειδή διάταξη.
Συγκεκριμένα, τα δεδομένα έρχονται ως εξής:
Κώδικας: Επιλογή όλων
(17,14)
(13,0)
(12,0)
(20,19)
(8,0)
(18,14)
(1,13)
(10,5)
(3,0)
(21,0)
(2,13)
(4,13)
(5,0)
(16,14)
(14,0)
(15,9)
(19,0)
(9,5)
όπου ο πρώτος αριθμό είναι το id του καταλόγου ($int_chlid) και ο δεύτερος είναι ο πατρικός κατάλογος ($int_parent). Τα folder με $int_parent=0 δεν έχουν πατέρα, είναι top-level.
Ο κώδικας είναι σε php
Κώδικας: Επιλογή όλων
$rows =& $tbl_f->rowsSelectAll('*', 'caption');
$arr_folders = array();
$arr_tree = array();
foreach ($rows as $row) {
$int_parent = $row->parent_ptr->intVal();
$int_child = $row->id->intVal();
// Initialize parent, info will be filled as child
if (!isset($arr_folders[$int_parent])) {
$arr_folders[$int_parent] = array('ch'=>array());
$arr_parent =& $arr_folders[$int_parent];
// Possible tree top
$arr_tree[$int_parent] =& $arr_parent;
// Parent is there, just fetch it
} else
$arr_parent =& $arr_folders[$int_parent];
// Create non existent child
if (!isset($arr_folders[$int_child])) {
$arr_folders[$int_child] = array(
'ch'=>array(),
'it'=>array('caption'=>$row->caption->strVal()));
$arr_child =& $arr_folders[$int_child];
// Child was initialized as a parent, fill info
} else {
$arr_child =& $arr_folders[$int_child];
if (!isset($arr_child['it']))
$arr_child['it'] = array('caption'=>$row->caption->strVal());
// Not a possible top
unset($arr_tree[$int_child]);
}
$arr_parent['ch'][$int_child] =& $arr_child;
}
Μετά απ' αυτόν τον κώδικα έχουν δημιουργηθεί τα παρακάτω:
1. Ένας πίνακας $arr_folder για κάθε κατάλογο όπου έχει τις πληροφορίες κάθε καταλόγου (id, caption)
2. Ένα δένδρο (σαν php hash) που περιέχει εμφωλευμένα τα folders ως εξής:
Κώδικας: Επιλογή όλων
[0]
[13]
[1]
[2]
[4]
[12]
[8]
[3]
[21]
[5]
[10]
[9]
[15]
[14]
[17]
[18]
[16]
[19]
[20]
Όπως φαίνεται μέχρι εδώ, καταφέραμε με έναν βρόχο να βάλουμε στη θέση τους όλα τα στοιχεία. Αλλά για να γίνει αυτό έπρεπε να διαβαστεί όλη η είσοδος. Το μείζον ερώτημα, εδώ, είναι το εξής: Τι δεδομένα θα μπορούσε να προσθέσει κανείς σε κάθε folder ώστε να μπορεί με ένα απλό ερώτημα να διαβάζει απ' ευθείας από την είσοδο μόνο αυτό και τα folder που βρίσκονται από κάτω τους;
Μια ιδέα που έχω είναι αυτή: Εκτός από το id και τον πατέρα (και όποια άλλα στοιχεία υπάρχουν, π.χ. caption, κτλ., μπορούν να προστίθενται τρεις ακόμη αριθμοί για κάθε folder:
1. Ένας αριθμός διάταξης (order)
2. ο αριθμός των υποκαταλόγων κάθε folder
3. Το επίπεδο nesting του κάθε καταλόγου
Για να προστεθούν, βέβαια, αυτά τα στοιχεία, καταλήγουμε εν τέλει σε αναδρομή, καθώς πρέπει να διαβαστεί το δένδρο που κατασκευάσαμε. Ο κώδικας είναι ο παρακάτω:
Κώδικας: Επιλογή όλων
function int_tree($arr_folder, $int_level = 0, $int_order = 0)
{
global $arr_chain;
// Normal folder, print it, then hangin nodes, next level
if (isset($arr_folder['it'])) {
$i = 1;
$arr_chain[] =& $arr_folder;
$arr_folder['it']['level'] = $int_level;
$arr_folder['it']['order'] = $int_order;
// Semi folder, print hanging nodes, same level
} else
$i = 0;
$int_order += $i;
// Count subfolders
foreach ($arr_folder['ch'] as $arr_subfolder) {
$int_order = int_tree($arr_subfolder, $int_level + $i, $int_order);
}
if (isset($arr_folder['it'])) {
$arr_folder['it']['sub'] = $int_order - $arr_folder['it']['order'] - 1;
// print(str_repeat(' ', $int_level));
// print($arr_folder['it']['caption']."\n");
}
return $int_order;
}
$arr_chain = array();
$int_order = 1;
foreach($arr_tree as $arr_topfolder) {
$int_order = int_tree($arr_topfolder, 0, $int_order);
}
Μετά απ' αυτό, το δένδρο που είχαμε προηγουμένως, έχει επιπλέον τα εξής στοιχεία:
Κώδικας: Επιλογή όλων
[0]
[13] (order: 1 / children: 3 / level: 1)
[1] (order: 2 / children: 0 / level: 2)
[2] (order: 3 / children: 0 / level: 2)
[4] (order: 4 / children: 0 / level: 2)
[12] (order: 5 / children: 0 / level: 1)
[8] (order: 6 / children: 0 / level: 1)
[3] (order: 7 / children: 0 / level: 1)
[21] (order: 8 / children: 0 / level: 1)
[5] (order: 9 / children: 3 / level: 1)
[10] (order:10 / children: 0 / level: 2)
[9] (order:11 / children: 1 / level: 2)
[15] (order:12 / children: 0 / level: 3)
[14] (order:13 / children: 3 / level: 1)
[17] (order:14 / children: 0 / level: 2)
[18] (order:15 / children: 0 / level: 2)
[16] (order:16 / children: 0 / level: 2)
[19] (order:17 / children: 1 / level: 1)
[20] (order:18 / children: 0 / level: 2)
Όπως φαίνεται ο αριθμός διάταξης (order) δείχνει την θέση στην οποία θα πρέπει να εμφανιστεί (ή να διαβαστεί, αν υπάρχει ταξινόμηση) κάθε folder. Π.χ. το [13] (order: 1) θα πρέπει να εμφανιστεί πριν από τα [1], [2], [4] (order: 2, 3, 4 αντίστοιχα).
Επιπλέον, ο αριθμός των παιδιών (children ή subfolders) δείχνει τον *συνολικό* αριθμό των subfolders, ανεξαρτήτως βάθους, και όχι τα αμέσως επόμενου επιπέδου παιδιά του. Π.χ., ο [13] έχει 3 children, προφανές. Από την άλλη ο [5] έχει 2 παιδιά να κρέμονται από κάτω του, αλλά το ένα από αυτά (το [9]) έχει και αυτό άλλο ένα. Οπότε συνολικά κάτω από τον [5] υπάρχουν 3 παιδιά.
Τέλος, το level δείχνει πόσο μέσα θα πρέπει να εμφανίζεται το κάθε folder. Εν ολίγοις προστέθηκαν στην δομή τα στοιχεία
προβολής. Και εφ' όσον αυτό που κάνεις συνέχεια είναι να
προβάλλεις, η πραγματική δομή δε σε ενδιαφέρει τελικά εκείνη την ώρα.
Υπέθεσα όταν το έγραφα ότι αυτό θα είχε ένα πλεονέκτημα. Π.χ., θέλω να ζητήσω από την MySQL τον κατάλογο 5 και όλους τους υποκαταλόγους του, ανεξαρτήτως βάθους. Ένα κριτήριο είναι το εξής:
Θέλω τον [5]. Έχει order 9. Θέλω επίσης όλους τους υποκαταλόγους του, δηλ. τους [10], [9], [15]. Αυτά έχουν orders 10, 11, 12. Δηλ. το order του [5] συν τον αριθμό των children του, δηλ. 3.
Οπότε το query θα ήταν "Δώσε μου όλα τα folders με order ανάμεσα σε 9 και 9+3".
Αυτό, όμως, προϋποθέτει ότι έχεις κάνει ένα ερώτημα για να διαβάσεις το order και το children του [5].
Οπότε το τελικό ερώτημα είναι: Πως γίνεται με ένα και μόνο query να πάρεις και τον [5] και τους υποκαταλόγους του;