PHP recursive foreach with left, right and depth - php

I have a json file taken from geonames.org and I want to add the data from that file using php recursive foreach.
I only succeeded that I just did not understand the concept of left right depth. Depth is saved correctly.
My code is:
public function buildTree($elements, $count = 1, $depth = 0)
{
if (isset($elements->geonames)) {
foreach ($elements->geonames as $element) {
$left = $count++;
$elementDB = new \App\Geo();
$elementDB->id = $element->geonameId;
$elementDB->parent_id = NULL;
$elementDB->left = $left;
$elementDB->right = $right;
$elementDB->depth = $depth;
$elementDB->name = $element->name;
$elementDB->country = $element->countryCode;
$elementDB->level = $element->fcode;
$elementDB->lat = $element->lat;
$elementDB->long = $element->lng;
$elementDB->save();
$elements = $this->getList($element->geonameId, 'element');
if ($depth < 1) {
$this->buildTree($elements, $count, $depth + 1);
}
$right = $count++;
echo "Added element " . $element->name . "\n";
}
}
}
This should happen

It seems you want to understand how it works?
You use recursion to process every element. Variable $elements is a tree (or sub-tree) with nodes. Your code has to look through your tree from left to right. On every iteration you check if $elements has nodes. If $elements has (it might be ordered array or struct with fields) nodes you have to process those nodes. During every checking you detect every node if they have other children nodes or not. When you find the first (let's name it "A") node that has other children nodes you have to go into on next iteration recursion to process children nodes of current child node ("A").
The numbers represent the order in which the nodes are accessed in Left-Right depth algorithm.
Frankly speaking I don't understand for what you have added:
$this->buildTree($elements, $count, $depth + 1);
And it's a bad style reassignment for variable in foreach:
May be it will be interesting for you When is it practical to use Depth-First Search (DFS) vs Breadth-First Search (BFS)?

I'm assuming here you're making a binary search tree. Basically, a tree is a graph with a root, "regular" nodes, and leaves.
There's always one single root, at the top, which is a particular node.
Leaves don't have other nodes below, they are the "ends" of the tree.
Regular nodes have possibly two children, one smaller (on the left) and one bigger (on the right). This makes something like that:
As you can see, all nodes coming from the left child of the root are smaller than 8. All children on the right are bigger than 8. This way, when you search for "10", you immediatly know that you have to go through the right child of the root, no need the explore left side (that means less processing time).

A possible binary search tree search algorithm implementation is as follows:
function buildTree($elements, $left, $right, $needle){
if ($left > $right) return null;
$middle = floor(($left + $right) / 2);
$val = $elements[$middle];
if ($val === $needle) return $val;
else if ($val < $needle) return buildTree($elements, $left + 1, $right, $needle);
else if ($val > $needle) return buildTree($elements, $left, $right + 1, $needle);
}
echo buildTree([1, 2, 3, 4, 5], 0, 5, 4);
You just need to adapt this to your problem

Related

PHP Permutations with limited string length

I am using someones permutation solution in PHP that was given off stack and was wondering if there was a way for me to limit the character count in the string to be a fixed amount? Lets say I want to limit the string length to only 4 characters. To be honest, I'm not sure really whats going on in this formula and was hoping I could modify it, but I would also be interested in a new formula approach as well especially if its faster from another individual. Thanks
ini_set('memory_limit', '1024M');
function permutations(array $elements)
{
if (count($elements) <= 1) {
yield $elements;
} else {
foreach (permutations(array_slice($elements, 1)) as $permutation) {
foreach (range(0, count($elements) - 1) as $i) {
yield array_merge(
array_slice($permutation, 0, $i),
[$elements[0]],
array_slice($permutation, $i)
);
}
}
}
}
$list = ['a', 'b', 'c', 'd', 'e', 'f'];
$newlist = array();
foreach (permutations($list) as $permutation) {
//echo implode(',', $permutation) . PHP_EOL;
array_push($newlist, implode(',', $permutation));
}
echo sizeof($newlist);
Here is an adaptation of the function that allows you to put a limit on the number of characters as second argument.
You need to add an extra parameter to know how many remaining characters are allowed and decrement it on the recursive call :
function permutations(array $elements, $limit)
{
if( $limit == 1 )
{
// No need to go deeper, return a list of all remaining letters
foreach($elements as $element)
yield array($element);
}
foreach($elements as $i => $element)
{
// compute all the permutions, without the elements at index i
$sub_perms = permutations( array_merge(array_slice($elements, 0, $i), array_slice($elements, $i+1)), $limit-1);
// list all the permutations with the currently selected element + all the possible combinations of $limit-1 letters of the others elements
foreach($sub_perms as $sub_perm)
{
yield array_merge(array($element), $sub_perm);
}
}
}
You can then call permutations($list, 2) or permutations($list, 4) to have all the permutations of 2 or 4 characters.

convert tab/space delimited lines into nested array

I would like convert the below text into a nested array, something like you would get with MPTT database structure.
I am getting the data from a shell script and need to display it on a website. Don't have any control over the format :/
There is lots of information about array -> list, but not much going the other way.
Any input would be appreciated, thanks.
cat, true cat
=> domestic cat, house cat, Felis domesticus, Felis catus
=> kitty, kitty-cat, puss
=> mouser
=> alley cat
=> tom, tomcat
=> gib
=> Angora, Angora cat
=> Siamese cat, Siamese
=> blue point Siamese
=> wildcat
=> sand cat
=> European wildcat, catamountain, Felis silvestris
=> cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
=> ocelot, panther cat, Felis pardalis
=> manul, Pallas's cat, Felis manul
=> lynx, catamount
=> common lynx, Lynx lynx
=> Canada lynx, Lynx canadensis
You have got a sorted tree list here already. Each next line is either a child of the previous line or a sibling. So you can process over the list, get the name of an item, gain the level an item is in by it's indentation and create an element out of it.
1 Line <=> 1 Element (level, name)
So every element has a name and zero or more children. From the input it can also be said which level it belongs to.
An element can be represented as an array, in which it's first value is the name and the second value is an array for the children.
As the list is sorted, we can use a simple map, which per level is an alias to the children of a certain level. So with the level each element has, we can add it to the stack:
$self = array($element, array());
$stack[$level][] = &$self;
$stack[$level + 1] = &$self[1];
As this code-example shows, the stack/map for the current level is getting $self as children added:
$stack[$level][] = &$self;
The stack for the level one higher, get's the reference to the children of $self (index 1):
$stack[$level + 1] = &$self[1];
So now per each line, we need to find the level. As this stack shows, the level is sequentially numbered: 0, 1, 2, ... but in the input it's just a number of spaces.
A little helper object can do the work to collect/group the number of characters in a string to levels, taking care that - if a level yet does not exist for an indentation - it is added, but only if higher.
This solves the problem that in your input there is no 1:1 relation between the size of the indentation and it's index. At least not an obvious one.
This helper object is exemplary named Levels and implements __invoke to provide the level for an indent while transparently adding a new level if necessary:
$levels = new Levels();
echo $levels(''); # 0
echo $levels(' '); # 1
echo $levels(' '); # 1
echo $levels(' '); # 2
echo $levels(' '); # Throws Exception, this is smaller than the highest one
So now we can turn indentation into the level. That level allows us to run the stack. The stack allows to build the tree. Fine.
The line by line parsing can be easily done with a regular expression. As I'm lazy, I just use preg_match_all and return - per line - the indentation and the name. Because I want to have more comfort, I wrap it into a function that does always return me an array, so I can use it in an iterator:
$matches = function($string, $pattern)
{
return preg_match_all($pattern, $string, $matches, PREG_SET_ORDER)
? $matches : array();
};
Using on input with a pattern like
/^(?:(\s*)=> )?(.*)$/m
will give me an array per each line, that is:
array(whole_line, indent, name)
You see the pattern here? It's close to
1 Line <=> 1 Element (level, name)
With help of the Levels object, this can be mapped, so just a call of a mapping function:
function (array $match) use ($levels) {
list(, $indent, $name) = $match;
$level = $levels($indent);
return array($level, $name);
};
From array(line, indent, name) to array(level, name). To have this accessible, this is returned by another function where the Levels can be injected:
$map = function(Levels $levels) {
return function ...
};
$map = $map(new Levels());
So, everything is in order to read from all lines. However, this needs to be placed into the the tree. Remembering adding to the stack:
function($level, $element) use (&$stack) {
$self = array($element, array());
$stack[$level][] = &$self;
$stack[$level + 1] = &$self[1];
};
($element is the name here). This actually needs the stack and the stack is actually the tree. So let's create another function that returns this function and allow to push each line onto the stack to build the tree:
$tree = array();
$stack = function(array &$tree) {
$stack[] = &$tree;
return function($level, $element) use (&$stack) {
$self = array($element, array());
$stack[$level][] = &$self;
$stack[$level + 1] = &$self[1];
};
};
$push = $stack($tree);
So the last thing to do is just to process one element after the other:
foreach ($matches($input, '/^(?:(\s*)=> )?(.*)$/m') as $match) {
list($level, $element) = $map($match);
$push($level, $element);
}
So now with the $input given this creates an array, with only (root) child nodes on it's first level and then having an array with two entries per each node:
array(name, children)
Name is a string here, children an array. So this has already done the list to array / tree here technically. But it's rather burdensome, because you want to be able to output the tree structure as well. You can do so by doing recursive function calls, or by implementing a recursive iterator.
Let me give an Recursive Iterator Example:
class TreeIterator extends ArrayIterator implements RecursiveIterator
{
private $current;
public function __construct($node)
{
parent::__construct($node);
}
public function current()
{
$this->current = parent::current();
return $this->current[0];
}
public function hasChildren()
{
return !empty($this->current[1]);
}
public function getChildren()
{
return new self($this->current[1]);
}
}
This is just an array iterator (as all nodes are an array, as well as all child nodes) and for the current node, it returns the name. If asked for children, it checks if there are some and offers them again as a TreeIterator. That makes using it simple, e.g. outputting as text:
$treeIterator = new RecursiveTreeIterator(
new TreeIterator($tree));
foreach ($treeIterator as $val) echo $val, "\n";
Output:
\-cat, true cat
|-domestic cat, house cat, Felis domesticus, Felis catus
| |-kitty, kitty-cat, puss
| |-mouser
| |-alley cat
| |-tom, tomcat
| | \-gib
| |-Angora, Angora cat
| \-Siamese cat, Siamese
| \-blue point Siamese
\-wildcat
|-sand cat
|-European wildcat, catamountain, Felis silvestris
|-cougar, puma, catamount, mountain lion, painter, panther, Felis concolor
|-ocelot, panther cat, Felis pardalis
|-manul, Pallas's cat, Felis manul
\-lynx, catamount
|-common lynx, Lynx lynx
\-Canada lynx, Lynx canadensis
If you're looking for more HTML output control in conjunction with an recursive iterator, please see the following question that has an example for <ul><li> based HTML output:
How can I convert a series of parent-child relationships into a hierarchical tree? (Should as well have some other insightful information for you)
So how does this look like all together? The code to review at once as a gist on github.
In contrast to my previous answer that is quite a bit long and explains all the steps, it's also possible to do the same but more compressed.
The line splitting can be done with strtok
The preg_match then "on" the line making mapping more immanent
The Levels can be compressed into an array taken for granted that the input is correct.
This time for the output, it's a recursive function not iterator that spills out a nested <ul> list. Example code (Demo):
// build tree
$tree = $levels = array();
$stack[1] = &$tree;
for ($line = strtok($input, $token = "\n"); $line; $line = strtok($token)) {
if (!preg_match('/^(?:(\s*)=> )?(.*)$/', $line, $self)) {
continue;
}
array_shift($self);
$indent = array_shift($self);
$level = #$levels[$indent] ? : $levels[$indent] = count($levels) + 1;
$stack[$level][] = &$self;
$stack[$level + 1] = &$self[];
unset($self);
}
unset($stack);
// output
tree_print($tree);
function tree_print(array $tree, $in = '') {
echo "$in<ul>\n";
$i = $in . ' ';
foreach ($tree as $n)
printf("</li>\n", printf("$i<li>$n[0]") && $n[1] && printf($i, printf("\n") & tree_print($n[1], "$i ")));
echo "$in</ul>\n";
}
Edit: The following goes even one step further to completely drop the tree array and do the output directly. This is a bit mad because it mixes the reordering of the data and the output, which tights things together so not easy to change. Also the previous example already looks cryptic, this is beyond good and evil (Demo):
echo_list($input);
function echo_list($string) {
foreach ($m = array_map(function($v) use (&$l) {
return array(#$l[$i = &$v[1]] ? : $l[$i] = count($l) + 1, $v[2]);
}, preg_match_all('/^(?:(\s*)=> )?(.*)$/m', $string, $m, PREG_SET_ORDER) ? $m : array()) as $i => $v) {
$pi = str_repeat(" ", $pl = #$m[$i - 1][0]); # prev
$ni = str_repeat(" ", $nl = #$m[$i + 1][0]); # next
(($v[0] - $pl) > 0) && printf("$pi<ul>\n"); # is child of prev
echo ' ' . str_repeat(" ", $v[0] - 1), "<li>$v[1]"; # output self
if (!$close = (($nl - $v[0]) * -1)) echo "</li>\n"; # has sibling
else if ($close < 0) echo "\n"; # has children
else for (printf("</li>\n$ni" . str_repeat(" ", $close - 1) . "</ul>\n"); --$close;) # is last child
echo $ni, $nn = str_repeat(" ", $close - 1), " </li>\n",
$ni, $nn, "</ul>\n";
}
}
This drops strtok again and goes back to the idea to use preg_match_all. Also it stores all lines parsed, so that it's possible to look behind and ahead to determine how many <ul> elements need to be opened or closed around the current element.

loop through multiple arrays incrementing length of string

I have
$char=array("1","2","3","4","5","6","7","8","9","0","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","-");
$doma=array("aero","asia","biz","cat","com","coop","info","int","jobs","mobi","museum","name","net","org","pro","tel","travel","xxx","edu","gov","mil","co.uk","co.nr","co.au","au","ca","co.cc","cc","co","cn","co.jp","de","es","ie","in","it","jp","nl","nz","ru","co.tk","tk","tv","us")
and what I would like to do, is:
from a length of 1 up to a length of 32 arrange the chars into a string, echoing the string, then going back to the beginning again. So eventually my browser would look something like this:
0.aero
1.aero
2.aero
3.aero
....
x.aero
y.aero
z.aero
-.aero
00.aero
01.aero
02.aero
....
za.aero
zb.aero
zc.aero
zd.aero
....
50x90zx908.aero
50x90zx909.aero
50x90zx90a.aero
50x90zx90b.aero
....
50x90zx910.aero
50x90zx911.aero
ect; ect;
How would I create for loops to do this? to include the $doma ones to the end as well each loop?
I know this is huge, but when I've got an idea I gotta try it ;)
If you really want to do this with for loops, then make 33 of them, one for each desired length (1-32) and one for the $doma array.
But I would not do it that way. Instead, observe that the desired combinations of the characters in the $char array actually form a tree. The root node represents the empty string. Each child node of the root represents a 1-character combination ("1", "2", "3", ...). Each child node of those nodes represent a 2-character combination that has the prefix of its parent node (so the children of the "1" node would all start with "1"), and so on. The leaves of the tree would be all 32-character combinations of the characters in $char. If there were only three characters, a, b, and c, it would look something like this:
You can then make a recursive function that implements a depth-first traversal of such a tree, and instead of generating the tree in memory and then printing it, you can just have the function output the contents of each node as it reaches it. Throw in a parameter that allows you to place a suffix after the node contents, and wrap the function in a loop that iterates through all elements in $doma, and you're done.
function f($array, $limit, $suffix, $prefix = "", $counter = 0)
{
if ($counter > 0) {
print "$prefix.$suffix\n";
}
if ($counter < $limit) {
foreach ($array as $element) {
f($array, $limit, $suffix, $prefix . $element, $counter+1);
}
}
}
$char=array("1","2","3","4","5","6","7","8","9","0","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","-");
$doma=array("aero","asia","biz","cat","com","coop","info","int","jobs","mobi","museum","name","net","org","pro","tel","travel","xxx","edu","gov","mil","co.uk","co.nr","co.au","au","ca","co.cc","cc","co","cn","co.jp","de","es","ie","in","it","jp","nl","nz","ru","co.tk","tk","tv","us");
foreach ($doma as $d) {
f($char, 32, $d);
}
The order will not be exactly as you specified, but this ordering is logically consistent with the order of elements in the arrays and the depth-first traversal order.
The code basically treats the character combination like a base-35 number. It adds one each loop.
<?php
$char=array("1","2","3","4","5","6","7","8","9","0","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","-");
$doma=array("aero","asia","biz","cat","com","coop","info","int","jobs","mobi","museum","name","net","org","pro","tel","travel","xxx","edu","gov","mil","co.uk","co.nr","co.au","au","ca","co.cc","cc","co","cn","co.jp","de","es","ie","in","it","jp","nl","nz","ru","co.tk","tk","tv","us");
$combo = array(0,0,0,0,0,0,0,0,0,0); //this stores the ten "digits" of the characters
$pval = 9; //this is the active "place value", the one that you increment
$list = "";
for($i=0;$i<44;$i++) { //loops through doma
for($j=0;$j<2758547353515625;$j++) { //loops through character combos, that long number is the permutations
for($l=0;$l<10;$l++) { //loop displays combo
$list .= $char[$combo[$l]];
}
$list .= "." . $doma[$i] . "<br/>"; //add combo to list
/*This next part check to see if the active digit is 35. It is is, it sets it
equal to zero and looks to the digit on the left. It repeats this until it
no longer finds a 35. This is like going from 09 to 10 in the decimal system. */
if($combo[$pval] == 35) {
while($combo[$pval] == 35) {
$combo[$pval] = 0;
$pval--;
}
}
$combo[$pval]++; //whatever digit it left on gets incremented.
$pval = 9; //reset, go back to first place value
}
for($l=0;$l<10;$l++) {
$combo[$l] = 0; //reset combo for the next top level domain
}
}
echo $list; //print the compiled list.
?>

Split an array with a regular expression

I'm wondering if it is possible to truncate an array by using a regular expression.
In particular I have an array like this one:
$array = array("AaBa","AaBb","AaBc","AaCa","AaCb","AaCc","AaDa"...);
I have this string:
$str = "AC";
I'd like the slice of $array from the start to the last occurrence of a string matching /A.C./ (in the sample, "AaCc" at index 5):
$result = array("AaBa","AaBb","AaBc","AaCa","AaCb","AaCc");
How can I do this? I thought I might use array_slice, but I don't know how to use a RegEx with it.
Here's my bid
function split_by_contents($ary, $pattern){
if (!is_array($ary)) return FALSE; // brief error checking
// keep track of the last position we had a match, and the current
// position we're searching
$last = -1; $c = 0;
// iterate over the array
foreach ($ary as $k => $v){
// check for a pattern match
if (preg_match($pattern, $v)){
// we found a match, record it
$last = $c;
}
// increment place holder
$c++;
}
// if we found a match, return up until the last match
// if we didn't find one, return what was passed in
return $last != -1 ? array_slice($ary, 0, $last + 1) : $ary;
}
Update
My original answer has a $limit argument that served no purpose. I did originally have a different direction I was going to go with the solution, but decided to keep it simple. However, below is the version that implements that $limit. So...
function split_by_contents($ary, $pattern, $limit = 0){
// really simple error checking
if (!is_array($ary)) return FALSE;
// track the location of the last match, the index of the
// element we're on, and how many matches we have found
$last = -1; $c = 0; $matches = 0;
// iterate over all items (use foreach to keep key integrity)
foreach ($ary as $k => $v){
// text for a pattern match
if (preg_match($pattern, $v)){
// record the last position of a match
$last = $c;
// if there is a specified limit, capture up until
// $limit number of matches, then exit the loop
// and return what we have
if ($limit > 0 && ++$matches == $limit){
break;
}
}
// increment position counter
$c++;
}
I think the easiest way might be with a foreach loop, then using a regex against each value - happy to be proven wrong though!
One alternative could be to implode the array first...
$array = array("AaBa","AaBb","AaBc","AaCa","AaCb","AaCc","AaDa"...);
$string = implode('~~',$array);
//Some regex to split the string up as you want, guessing something like
// '!~~A.C.~~!' will match just what you want?
$result = explode('~~',$string);
If you'd like a hand with the regex I can do, just not 100% on exactly what you're asking - the "A*C*"-->"AaCc" bit I'm not too sure on?
Assuming incremental numeric indices starting from 0
$array = array("AaBa","AaBb","AaBc","AaCa","AaCb","AaCc","AaDa");
$str = "AC";
$regexpSearch = '/^'.implode('.',str_split($str)).'.$/';
$slicedArray = array_slice($array,
0,
array_pop(array_keys(array_filter($array,
function($entry) use ($regexpSearch) {
return preg_match($regexpSearch,$entry);
}
)
)
)+1
);
var_dump($slicedArray);
PHP >= 5.3.0 and will give a
Strict standards: Only variables should be passed by reference
And if no match is found, will still return the first element.

PHP function to match key in a range of values

I have a array with the lvl=>xp correspondance and I would make a function that return the lvl for a specific xp. like $lvl = getLvlOf(15084); //return 5
$lvl_correspondance = array(
1=>100,
2=>520,
3=>2650,
4=>6588,
5=>12061,
6=>23542,
...
n=>xxxxxx
);
I search the easyest and ressourceless way to do it.
Sorry for my poor english :(
Assuming the level values in the array are kept sorted, e.g. (it's 100,200,300, 400, etc... and not 200,500,100,300,400), then a simple scan will do the trick:
$xp = 15084;
$last_key = null;
foreach($lvl_correspondenance as $key => $val) {
if ($val < $xp) {
$last_key = $key;
} else {
break;
}
}
That'll iterate through the array, and jump out as soon as the XP level in the array is larger than the XP level you're looking for, leaving the key of that "last" level in $last_key
function getLvlOf($lvl, $int){
foreach($lvl as $level=>$exp){
if($exp > $int){
return $level-1;
}
}
}
it's O(n) so no magic there...
It looks like your array can be computed live -
XP = exp( 3 * ln(LVL) + 4 ) * 2
You can do the same in reverse, in O(1):
LVL = exp(ln(XP/2) - 4 / 3)
I rounded the equation, so there may be a +/- 1 issue
Good Luck!
Not good if you have very high level values, but:
$lvl = $lvl_correspondance[array_search(
max(array_intersect(
array_values($lvl_correspondance),
range(0,$points)
)
),
$lvl_correspondance
)];
You can use array_flip if the xp levels are distinct. Then you can simply access the level number using the xp as index:
$levels = array_flip($lvl_correspondance);
$lvl = $levels[15084];
EDIT: But maybe a function would be better to fetch even the xp levels in between:
function getLvlOf($xp) {
// get the levels in descending order
$levels = array_reverse($GLOBALS['lvl_correspondance'], true);
foreach ($levels as $key => $value) {
if ($xp >= $value)
return $key;
}
// no level reached yet
return 0;
}

Categories