I'm trying to build a prfix tree. So, using the following dictionary 'and','anna','ape','apple'
, graph should look like this:
I've tried 2 approaches: using associative array and using self-written tree/node classes.
Note: original dictionary is something about 8 MB and contains >600000 words.
Associative array aproach:
function letter_to_graph ($graph_path,$word,$letter_index) {
$letter=$word[$letter_index];
if (!isset($graph_path[$letter])) {
$graph_path[$letter]=array();
}
if ($letter_index == strlen($word)-1) {
$graph_path[$letter]['.']=array();
} else {
letter_to_graph(&$graph_path[$letter],$word,$letter_index+1);
}
}
function push_word_into_graph ($graph,$word) {
letter_to_graph(&$graph,$word,0);
}
$words_graph = array();
for ($i=0;$i<count($words);$i++) {
push_word_into_graph(&$words_graph,strtolower($words[$i]));
//if ($i % 300 == 0) {echo $i . "_<br/>";} //this is just for testing
}
It takes ~35 seconds to execute, eats up to ~350MB.
Node/tree approach:
class Node
{
public $_children;
public function __construct() {
$this->_children = array();
}
public function add_child ($val) {
if ($this->_children === null)
$this->_children=array();
$tmp = new Node();
$this->_children[$val] = &$tmp;
return $tmp;
}
public function has_child($val) {
if (isset($this->_children[$val])) {
return true;
}
return false;
}
public function get_child($val) {
//if ($this->has_child($val))
return $this->_children[$val];
//else
//return false;
}
}
class Tree
{
protected $root;
public function __construct() {
$this->root = new Node ();
}
public function insert_word ($word) {
$this->insert_letter_node(&$this->root,$word,0);
}
protected function insert_letter_node($node,$word,$letter_index) {
$letter=$word[$letter_index];
if (!$node->has_child($letter)) {
$new_node = $node->add_child ($letter);
} else {
$new_node = $node->get_child ($letter);
}
if ($letter_index < strlen($word)-1) {
$this->insert_letter_node(&$new_node,$word,$letter_index+1);
} else {
$new_node->add_child('.');
}
}
}
$result_tree= new Tree();
for ($i=0;$i<(count($words));$i++) {
$result_tree->insert_word(strtolower($words[$i]));
//if ($i % 300 == 0) {echo $i . "_<br/>";}//this is just for testing
}
This one takes ~240 seconds to execute (estimated; actually it stops at half-path due to timeout limit) and eats up to ~550MB while working (so, if there were no timeout, it could've eaten much more).
They both are building correct graphs (second was tested on a part of dictionary), however 2nd approach is ~7 times slower and "eats" at least twice as much memory. Main question is "why the difference is so big?" - they both seem to have identical complexity. And the second question is "how to make it faster/less memory consuming?". Am I using pointers wrong somewhere?
Some notes on my code :
I've "commented out" the unnecessary
has_child()
check, however it doesn't increase speed significantly.Currently I'm testing it on php 5.3.2 with Apache 2.2 on win7x32.
Current numbers are not quite accurate - the time/memory usage currently includes populating
$words
by reading file line-by-line, however this is not really the issue - it takes ~1 second and uses only a small amount of memory (not more than 7MB).- Using
class
approach on php7/win8x64 takes 124s and ~800mb.
UPDATE: I've followed some advices and did following (code in the question is a bit outdated right now):
Got rid of recursion (no significant improvement).
Got rid of not really necessary
.
nodes by replacing them withisEndOfAword
flag to aNode
(significant improvement, something about 30% both in time and memory usage)- Modified
functions
to accept variables by reference instead of using something likeletter_to_graph(&$graph,$word,0);
. This was done to test code on php7. - Switched to C++ for current task. Simple trie as from this tutorial http://www.sourcetricks.com/2011/06/c-tries.html takes ~3s and uses ~100 MB at most.
As long as I've switched to C++, I'm leaving this question open, untill someone explains me, why is php operating seven times slower on Node
with single Array
property and a couple of methods versus just Array
of Array
s (of Array
s of Array
s...).
Also I'm starting a bounty, so, probably, you will want to run my code on your machine and check, if the second approach is really 6-7 times slower (3-4 times slower in php7). There is a probability, that something can be wrong with my environment or php configuration. If you are willing to do so, you can take any large english dictionary (500-700 thousands of words) first and put it in $words
array;
.
as a terminating symbol (my dictionary doesn't contain this symbol). In case with 'text' and 'texture' the second 't' node will have two children - the first would be '.', the second - 'u'. \$\endgroup\$