Reputation: 920
I have a table in a MySQL database that represents a genetic language tree. Each row is a language with an ID and a parent ID; for example:
id | language | parent
-----+---------------------+--------
1 | Proto-Indo-European | NULL
6 | Celtic | 1
8 | Insular Celtic | 6
9 | Goidelic | 8
14 | Irish | 9
16 | Manx | 9
21 | British | 8
22 | Welsh | 21
109 | Germanic | 1
115 | West Germanic | 109
117 | Anglo-Saxon | 115
118 | Anglic | 117
119 | Old English | 118
My goal is to turn these rows into a pretty nested HTML list with aggregate counters after each language that has any child languages, using a simple recursive PHP function, resulting in something like this (based on the languages in the example rows above):
- Proto-Indo-European (11)
- Celtic (6)
- Insular Celtic (5)
- Goidelic (2)
- Irish
- Manx
- British (1)
- Welsh
- Germanic (4)
- West Germanic (3)
- Anglo-Saxon (2)
- Anglic (1)
- Old English
I have the following PHP function (simplified here), which creates a multidimensional array with the proper structure:
function select_languages_hierarchical($parent = NULL) {
global $db;
$branch = array();
$query = "SELECT * FROM languages WHERE parent = $parent";
if ($q = $db->query($query)) {
while ($row = $q->fetch_assoc()) {
$element['id'] = $row['id'];
$element['name'] = $row['language'];
$children = select_languages_hierarchical($row['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
This produces an array that matches the nested list above, with nested arrays being in the children
element of each array.
However, I simply cannot for the life of me, despite much brain-racking, Googling, and looking through oodles of recursion, aggregation-counting, and array-counting questions here on SO, figure out a way to create the counters that describe the number of descendent languages for each language.
Whatever I do, wherever I create, modify, and use my counter variables, and whatever I try counting (whether doing a ˋ$count++ˋ every time I iterate over a language, do a ˋcount($children)ˋ, etc.), I always end up with results where the counter is not reset when the function reaches a ‘higher’ level, so that I get lists like this instead:
- Proto-Indo-European (12)
- Celtic (6)
- Insular Celtic (5)
- Goidelic (2)
- Irish
- Manx
- British (4)
- Welsh
- Germanic (9)
- West Germanic (12)
- Anglo-Saxon (14)
- Anglic (15)
- Old English
– Or something like that—the numbers and how they add up vary in the various different implementations I’ve attempted; but they all have in common that the counter keeps being increased throughout, without ever being reset. Or alternatively, if I do try to reset it at some point, naturally it gets reset on every iteration, leaving me with:
- Proto-Indo-European (2)
- Celtic (2)
- Insular Celtic (1)
- Goidelic (2)
- Irish
- Manx
- British (1)
- Welsh
- Germanic (1)
- West Germanic (1)
- Anglo-Saxon (1)
- Anglic (1)
- Old English
Clearly, logic is not my strong suit here.
Can someone help my brain from spontaneously combusting and/or imploding by suggesting a way for me to achieve the kind of ‘intelligent tallying’ that I’m looking for here?
Upvotes: 1
Views: 1277
Reputation: 360602
Thinking about it, instead of running a zillion recursive queries in PHP, you could just do a basic select *
and build your tree in PHP, and do the counting there. Of cousre, this'd only be useful if you want the entire language tree.
$lang = array();
$sql = "SELECT * FROM languages";
... run query ...
while($row = fetch results) {
// store this lang's node
$lang[$row['id']]['name'] = $row['name'];
// add this language to its parent's child array
$lang[$row['parent']]['children'][$row['id']] = $row['id'];
// increment its parent's counter
$lang[$row['parent']]['count']++;
}
Now, this will probably spew out a bunch of warnings about undefined arrays and whatnot, since I'm not testing for the existence of a parent before trying to updates it's counts. But that's just some basic if (!isset()) { initialize node }
type stuff.
Output into your nested <ul>
would be a recursive function, but since you've now got the child-node counts in your tree already, it'll be much simpler:
function output($id = 1) { // assuming "1" is your tree root node
echo '<li>' . $lang[$id]['name'] . '(' . $lang[$id]['count'] . ')';
if ($lang[$id]['count'] > 0) {
echo '<ul>';
foreach($lang[$id]['children'] as $childID) {
output($childID);
}
echo '</ul>';
}
echo '</li>';
}
Upvotes: 1