How to find leaf arrays in nested arrays? - php

I have a nested array in PHP:
array (
'0' => "+5x",
'1' => array (
'0' => "+",
'1' => "(",
'2' => "+3",
'3' => array (
'0' => "+",
'1' => "(",
'2' => array ( // I want to find this one.
'0' => "+",
'1' => "(",
'2' => "+5",
'3' => "-3",
'4' => ")"
),
'3' => "-3",
'4' => ")"
),
'4' => ")"
)
);
I need to process the innermost arrays, ones that themselves contain no arrays. In the example, it's the one with the comment: "I want to find this one." Is there a function for that?
I have thought about doing (written as an idea, not as correct PHP):
foreach ($array as $id => $value) {
if ($value is array) {
$name = $id;
foreach ($array[$id] as $id_2 => $value_2) {
if ($value_2 is array) {
$name .= "." . $id_2;
foreach ($array[$id][$id_2] as $id_3 => $value_3) {
if ($value_3 is array) {
$name .= "." . $id_3;
foreach ($array[$id][$id_2][$id_3] as $id_4 => $value_4) {
if ($value_4 is array) {
$name .= "." . $id_4;
foreach [and so it goes on];
} else {
$listOfInnerArrays[] = $name;
break;
}
}
} else {
$listOfInnerArrays[] = $name;
break;
}
}
} else {
$listOfInnerArrays[] = $name;
break;
}
}
}
}
So what it does is it makes $name the current key in the array. If the value is an array, it goes into it with foreach and adds "." and the id of the array. So we would in the example array end up with:
array (
'0' => "1.3.2",
)
Then I can process those values to access the inner arrays.
The problem is that the array that I'm trying to find the inner arrays of is dynamic and made of a user input. (It splits an input string where it finds + or -, and puts it in a separate nested array if it contains brackets. So if the user types a lot of brackets, there will be a lot of nested arrays.)
Therefore I need to make this pattern go for 20 times down, and still it will only catch 20 nested arrays no matter what.
Is there a function for that, again? Or is there a way to make it do this without my long code? Maybe make a loop make the necessary number of the foreach pattern and run it through eval()?

Definitions
simple:
Describes expressions without sub-expressions (e.g. "5", "x").
compound:
Describes expressions that have sub-expressions (e.g. "3+x", "1+2").
constness:
Whether an expression has a constant value (e.g. "5", "1+2") or not (e.g. "x", "3+x").
outer node:
In an expression tree, a node reachable by always traversing left or always traversing right. "Outer" is always relative to a given node; a node might be "outer" relative to one node, but "inner" relative to that node's parent.
inner node:
In an expression tree, a node that isn't an outer node.
For an illustration of "inner" and "outer" nodes, consider:
__1__
/ \
2 5
/ \ / \
3 4 6 7
3 and 7 are always outer nodes. 6 is outer relative to 5, but inner relative to 1.
Answer
The difficulty here lies more in the uneven expression format than the nesting. If you use expression trees, the example 5x+3=(x+(3+(5-3))) equation would parse to:
array(
'=' => array(
'+' => array( // 5x + 3
'*' => array(
5, 'x'
),
3
)
'+' => array( // (x+(3+(5-3)))
'x',
'+' => array( // (3+(5-3))
3,
'-' => array(
5, 3
) ) ) ) )
Note that nodes for binary operations are binary, and unary operations would have unary nodes. If the nodes for binary commutative operations could be combined into n-ary nodes, 5x+3=x+3+5-3 could be parsed to:
array(
'=' => array(
'+' => array( // 5x + 3
'*' => array(
5, 'x'
),
3
)
'+' => array( // x+3+5-3
'x',
3,
'-' => array(
5, 3
) ) ) )
Then, you'd write a post-order recursive function that would simplify nodes. "Post-order" means node processing happens after processing its children; there's also pre-order (process a node before its children) and in-order (process some children before a node, and the rest after). What follows is a rough outline. In it, "thing : Type" means "thing" has type "Type", and "&" indicates pass-by-reference.
simplify_expr(expression : Expression&, operation : Token) : Expression {
if (is_array(expression)) {
foreach expression as key => child {
Replace child with simplify_expr(child, key);
key will also need to be replaced if new child is a constant
and old was not.
}
return simplify_node(expression, operation);
} else {
return expression;
}
}
simplify_node(expression : Expression&, operation : Token) : Expression;
In a way, the real challenge is writing simplify_node. It could perform a number of operations on expression nodes:
If an inner grand-child doesn't match the constness of the other child but its sibling does, swap the siblings. In other words, make the odd-man-out an outer node. This step is in preparation for the next.
+ + + +
/ \ / \ / \ / \
\+ 2 ---> + 2 + y ---> + y
/ \ / \ / \ / \
1 x x 1 x 1 1 x
If a node and a child are the same commutative operation, the nodes could be rearranged. For example, there's rotation:
+ +
/ \ / \
\+ c ---> a +
/ \ / \
a b b c
This corresponds to changing "(a+b)+c" to "a+(b+c)". You'll want to rotate when "a" doesn't match the constness of "b" and "c". It allows the next transformation to be applied to the tree. For example, this step would convert "(x+3)+1" to "x+(3+1)", so the next step could then convert it to "x+4".
The overall goal is to make a tree with const children as siblings. If a commutative node has two const descendants, they can be rotated next to each other. If a node has only one const descendent, make it a child so that a node further up in the hierarchy can potentially combine the const node with another of the ancestor's const children (i.e. const nodes float up until they're siblings, at which point they combine like bubbles in soda).
If all children are constant, evaluate the node and replace it with the result.
Handling nodes with more than one compound child and n-ary nodes left as exercises for the reader.
Object-Oriented Alternative
An OO approach (using objects rather than arrays to build expression trees) would have a number of advantages. Operations would be more closely associated with nodes, for one; they'd be a property of a node object, rather than as the node key. It would also be easier to associate ancillary data with expression nodes, which would be useful for optimizations. You probably wouldn't need to get too deep into the OOP paradigm to implement this. The following simple type hierarchy could be made to work:
Expression
/ \
SimpleExpr CompoundExpr
/ \
ConstantExpr VariableExpr
Existing free functions that manipulate trees would become methods. The interfaces could look something like the following pseudocode. In it:
Child < Parent means "Child" is a subclass of "Parent".
Properties (such as isConstant) can be methods or fields; in PHP, you can implement this using overloading.
(...){...} indicate functions, with the parameters between parentheses and the body between brackets (much like function (...){...} in Javascript). This syntax is used for properties that are methods. Plain methods simply use brackets for the method body.
Now for the sample:
Expression {
isConstant:Boolean
simplify():Expression
}
SimpleExpr < Expression {
value:Varies
/* simplify() returns an expression so that an expression of one type can
be replaced with an expression of another type. An alternative is
to use the envelope/letter pattern:
http://users.rcn.com/jcoplien/Patterns/C++Idioms/EuroPLoP98.html#EnvelopeLetter
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Envelope_Letter
*/
simplify():Expression { return this }
}
ConstantExpr < SimpleExpr {
isConstant:Boolean = true
}
VariableExpr < SimpleExpr {
isConstant:Boolean = false
}
CompoundExpr < Expression {
operation:Token
children:Expression[]
commutesWith(op:Expression):Boolean
isCommutative:Boolean
isConstant:Boolean = (){
for each child in this.children:
if not child.isConstant, return false
return true
}
simplify():Expression {
for each child& in this.children {
child = child.simplify()
}
return this.simplify_node()
}
simplify_node(): Expression {
if this.isConstant {
evaluate this, returning new ConstExpr
} else {
if one child is simple {
if this.commutesWith(compound child)
and one grand-child doesn't match the constness of the simple child
and the other grand-child matches the constness of the simple child
{
if (compound child.isCommutative):
make odd-man-out among grand-children the outer child
rotate so that grand-children are both const or not
if grand-children are const:
set compound child to compound child.simplify_node()
}
} else {
...
}
}
return this
}
}
The PHP implementation for SimpleExpr and ConstantExpr, for example, could be:
class SimpleExpr extends Expression {
public $value;
function __construct($value) {
$this->value = $value;
}
function simplify() {
return $this;
}
}
class ConstantExpr extends SimpleExpr {
// Overloading
function __get($name) {
switch ($name) {
case 'isConstant':
return True;
}
}
}
An alternate implementation of ConstantExpr:
function Expression {
protected $_properties = array();
// Overloading
function __get($name) {
if (isset($this->_properties[$name])) {
return $this->_properties[$name];
} else {
// handle undefined property
...
}
}
...
}
class ConstantExpr extends SimpleExpr {
function __construct($value) {
parent::construct($value);
$this->_properties['isConstant'] = True;
}
}

Recursive foreach function, from comments at: http://php.net/manual/en/control-structures.foreach.php
/* Grab any values from a multidimensional array using infinite recursion. --Kris */
function RecurseArray($inarray, $result) {
foreach ($inarray as $inkey => $inval) {
if (is_array($inval)) {
$result = RecurseArray($inval, $result);
} else {
$result[] = $inval;
}
}
return $result;
}
Note that the above implementation produces a flattened array. To preserve nesting:
function RecurseArray($inarray) {
$result = array();
foreach ( $inarray as $inkey => $inval ) {
if ( is_array($inval) ) {
$result[] = RecurseArray($inval);
} else {
// process $inval, store in result array
$result[] = $inval;
}
}
return $result;
}
To modify an array in-place:
function RecurseArray(&$inarray) {
foreach ( $inarray as $inkey => &$inval ) {
if ( is_array($inval) ) {
RecurseArray($inval);
} else {
// process $inval
...
}
}
}

RecursiveIteratorIterator knows the current depth of any children. As you're interested only in children that have children, filter those with no children out and look for max-depth.
Then filter again based by depth for max-depth:
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr), RecursiveIteratorIterator::SELF_FIRST);
$cf = new ChildrenFilter($ritit);
$maxdepth = NULL;
foreach($cf as $v)
{
$maxdepth = max($maxdepth, $cf->getDepth());
}
if (NULL === $maxdepth)
{
throw new Exception('No children found.');
}
$df = new DepthFilter($cf, $maxdepth);
foreach($df as $v)
{
echo "Array with highest depth:\n", var_dump($v), "\n";
}
Demo / Source

Please, try the following code and let me know the results.
You just need to pass your array to the find_deepest function.
function find_deepest( $array )
{
$index = ''; // this variable stores the current position (1.2, 1.3.2, etc.)
$include = true; // this variable indicates if the current position should be added in the result or not
$result = array(); // this is the result of the function, containing the deepest indexes
$array_stack = array(); // this is a STACK (or LIFO) to temporarily store the sub-arrays - see http://en.wikipedia.org/wiki/LIFO_%28computing%29
reset( $array ); // here we make the array internal POINTER move to the first position
// each loop interaction moves the $array internal pointer one step forward - see http://php.net/each
// if $current value is null, we reached the end of $array; in this case, we will also continue the loop, if the stack contains more than one array
while ( ( $current = each( $array ) ) || ( count( $array_stack ) > 1 ) )
{
// we are looping $array elements... if we find an array (a sub-array), then we will "enter it...
if ( is_array( $current['value'] ) )
{
// update the index string
$index .= ( empty ( $index ) ? '' : '.' ) . $current['key'];
// we are entering a sub-array; by default, we will include it
$include = true;
// we will store our current $array in the stack, so we can move BACK to it later
array_push( $array_stack, $array );
// ATTENTION! Here we CHANGE the $array we are looping; here we "enter" the sub-array!
// with the command below, we start to LOOP the sub-array (whichever level it is)
$array = $current['value'];
}
// this condition means we reached the END of a sub-array (because in this case "each()" function has returned NULL)
// we will "move out" of it; we will return to the previous level
elseif ( empty( $current ) )
{
// if we reached this point and $include is still true, it means that the current array has NO sub-arrays inside it (otherwise, $include would be false, due to the following lines)
if ( $include )
$result[] = $index;
// ATTENTION! With the command below, we RESTORE $array to its precedent level... we entered a sub-array before, now we are goin OUT the sub-array and returning to the previous level, where the interrupted loop will continue
$array = array_pop( $array_stack );
// doing the same thing with the $index string (returning one level UP)
$index = substr( $index, 0, strrpos( $index, '.' ) );
// if we are RETURNING one level up, so we do NOT want the precedent array to be included in the results... do we?
$include = false;
}
// try removing the comment from the following two lines! you will see the array contents, because we always enter this "else" if we are not entering a sub-array or moving out of it
// else
// echo $index . ' => ' . $current['value'] . '<br>';
}
return $result;
}
$result = find_deepest( $my_array );
print_r( $result );
The most important parts of the code are:
the each command inside the while loop
the array_push function call, where we store the current array in the "array stack" in order to return back to it later
the array_pop function call, where we return one level back by restoring the current array from the "array stack"

Related

PHP class or function for eliminate redundancy in an ip block list

I have a list of ip blocks like this that I obtained by stitching together different sources.
160.11.0.0/16
160.11.14.0/20
160.12.0.0/14
160.16.5.0/15
160.16.1.0/14
160.18.0.0/16
160.20.0.0/14
160.24.0.0/16
160.26.0.0/15
160.28.0.0/15
160.74.0.0/16
...
I would like to have a php function that could take in input this list and optimize it's size by removing unnecessary redundancies because the very final output of all this will be given to a software able to compare IPs and it's performances will depend of how much lines of input it will get (the shorter the list, better will be software performances).
My code would be something like:
$input_array = file("list.txt");
$output_array = optimize_ipblocks($input_array);
file_put_contents(implode("\n", $output));
This optimize_ipblocks function should be able to:
Isolate all blocks and see if there are smaller blocks already contained inside bigger blocks, remove the smaller ones.
If there are duplicates of the same kind, remove them.
If there are blocks that can be joined together because they share partially the content or if they touch themselves merge them into a bigger block.
If some aggregation of blocks can be achieved, aggregate them into and so on.
My knowledge of ip blocks unpacking and comparing is kinda limited so for now the only part i can get rid of is a duplicate check modelling the function like:
function optimize_ipblocks($input_array) {
$blocks = array();
foreach($input_array as $key => $val) {
if(!in_array($val, $blocks)) $blocks[] = $val;
}
return $blocks;
}
I have no idea how to carry out comparisons of blocks and aggregation.
Helpful links for resolution:
This wikipedia article can help maybe https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing down to the section it talks about IPv4 CIDR blocks.
This perl script to aggregate cidr addresses http://www.zwitterion.org/software/aggregate-cidr-addresses/
http://www.perlmonks.org/?node_id=118346
I wrote a class for merging Ip ranges into an optimized superset of IP ranges. For your usage, you should first convert your CIDR addresses into from-to IP ranges, as in the $ranges array in this usage example:
$ranges=[
["from" => '192.168.0.1',"to" => '192.168.0.9'],
["from" => '192.168.0.3',"to" => '192.168.0.6'],
["from" => '192.168.0.1',"to" => '192.168.0.5'],
["from" => '192.168.0.13',"to" => '192.168.0.17'],
["from" => '192.168.0.2',"to" => '192.168.0.4'],
["from" => '192.168.0.2',"to" => '192.168.0.7'],
["from" => '192.168.0.12',"to" => '192.168.0.14'],
];
$rm=new C_RangeMerger($ranges,'ip');
$mergedRanges=$rm->getMergedRanges();
echo("<pre>".print_r($mergedRanges,1)."</pre>");
class C_RangeMerger
Output:
Array
(
[0] => Array
(
[from] => 192.168.0.1
[to] => 192.168.0.9
)
[1] => Array
(
[from] => 192.168.0.12
[to] => 192.168.0.17
)
)
Hope that helps!
This function should do what you want..
<?php
function merge_cidr(array $cidr_or_ipv4_list)
{ // Main function
$valid_ip='0*?((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))'; // Build the valid ipv4 regex
$valid_ip.=str_repeat(".$valid_ip",3); // Finalize the ipv4 regex accepting leading zeros for each part
$valid_routing_prefix='(?:0*?((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))'; // Build a regex for the routing prefix (accepting leading zeros)
foreach($cidr_or_ipv4_list as $a) // For each entry you pass to the function
if (is_string($a) && preg_match("#^[^0-9]*$valid_ip(?:/$valid_routing_prefix)?[^0-9]*$#", $a, $m))
{ // Extracting the valid ipv4 and optionnaly the routing prefix
$m[5] = ctype_digit($m[5]) ? ((int)$m[5]) : 32; // Initialize the valid routing prefix to the extracted value or 32 if mismatch
$c[$m[5]][] = ip2long("$m[1].$m[2].$m[3].$m[4]") & (-1 << (32 - $m[5])); // Initialize the working array with key (prefix) and value as subnet by bitwise the decimal ip
}
if ($c) // If any valid ipv4 with optional routing prefix matched
{
foreach($c as &$unique) $unique=array_unique($unique); //Make unique as possible before processing
$c = merge_cidr_summarize($c); // Pass the valid array to the slave function
foreach($c as $d => & $e) // For each result as routing prefix => Decimal value
$e = array_map(
function ($a) use($d)
{
return [$a, $a + (1 << (32 - $d)) - 1];
}
, $e); // Change it to an array containing the range of ip
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value
foreach($c as $h => $i) // For each result as routing prefix => array of decimal value
if ($f > $h) // If we are not in the same line and the second line have a lower routing prefix
foreach($g as $j => $k) // For each line as id => array of decimal values
foreach($i as $l) // For each line as decimal value in the second foreach
if ($k[0] >= $l[0] && $k[1] <= $l[1]) // If the block with lower routing prefix is totally including the first
unset($c[$f][$j]); // Unset the smaller block
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value
{
usort($g,
function (array $a, array $b)
{
return $b[0]>$a[0]?1:($b[0]<$a[0]?-1:0);
}); // Order the result "naturally" inversed
foreach($g as $h) // For each ordered result
$z[] = long2ip($h[0]) . '/' . $f; // Convert it to human readable
}
return array_reverse($z); // And return the reversed result (order by routing prefix DESC and ip DESC)
}
}
function merge_cidr_summarize(array $a)
{ // Slave function
$b = false; // Did we make a change ?
$c = []; // Initialize the result to an empty array
krsort($a); // Order the input by routing prefix DESC
foreach($a as $d => $e) { // For each entry as cidr => Array of decimal values
sort($a[$d]); // Order the values "naturally"
$k = count($a[$d]); // Count the values for the loop
for ($i = 0; $i < $k; $i++) // Loop to check all values with this routing prefix
if ($a[$d][$i] == $a[$d][$i + 1]) continue; // If the subnet is the same as the next, then directly goto the next
elseif (($a[$d][$i] & (-1 << 33 - $d)) == ($a[$d][$i + 1] & (-1 << 33 - $d))) { // Check if subnet of this line and the next line are equals
$c[$d - 1][] = $a[$d][$i++] & (-1 << 33 - $d); // If yes add the new subnet in result array and skip the next line
$b = true; // And tell the script to run again
}
else $c[$d][] = $a[$d][$i]; // Else don't make anything
}
return $b ? merge_cidr_summarize($c) : $a; // If any change run again else return the result
}
To try it
<?php
$your_array=['160.11.0.0/16','160.11.14.0/20','160.12.0.0/14','160.16.5.0/15','160.16.1.0/14','160.18.0.0/16','160.20.0.0/14','160.24.0.0/16','160.26.0.0/15','160.28.0.0/15','160.74.0.0/16'];
print_r(merge_cidr($your_array));
Output.. Check it in https://eval.in/745895
/*
Array
(
[0] => 160.16.0.0/13
[1] => 160.12.0.0/14
[2] => 160.28.0.0/15
[3] => 160.26.0.0/15
[4] => 160.74.0.0/16
[5] => 160.24.0.0/16
[6] => 160.11.0.0/16
)
*/

PHPUnit: expect method call with array as argument

I have a PHPUnit test case, in which I am puzzled by the following snippet. I want to check that the method actionUpload calls the function exposeAndSaveDataLines correctly, i.e. that the first argument is an array as I expect it to be.
public function test_actionUpload()
{
$sut = $this->getMockBuilder('MasterdataController')
->setMethods(array('exposeAndSaveDataLines', 'render'))
->disableOriginalConstructor()
->getMock();
$expectedLines = require_once ($this->dataDir . 'expectedLines.php');
$sut->expects($this->once())
->method('exposeAndSaveDataLines')
->with($this->equalTo($expectedLines),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything());
$sut->actionUpload();
}
The expected data is a printout of the current array, made with a temporary print_r (var_export($lines)) in the actual code. I return it in the file expectedLines.php, and when I manually print it, it is correct.
Now, when I run the test case with a single character deliberately misspelled in expectedLines, I get the following error (as expected).
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
## ##
3 => 'Colour Group Code'
- 4 => '{2F30E832-D3DB-447E-B733-7BC5125CBCCc}'
+ 4 => '{2F30E832-D3DB-447E-B733-7BC5125CBCCC}'
)
)
)
However, when I correct the mistake, it still mentions that the two arrays are not equal. However, it now prints the entire array (at least the start of it, it is a long array), but it doesn't show any differences (no - and + in front of any line). Why does the expects method not recognize that the two arrays are the same? How am I able to test this properly?
EDIT 1
I have shortened the array, such that it prints the entire array when they are not equal. Still no + or - signs in the comparison.
This is the end of my expectation PHP file.
'RetTarget Area' => array(
0 => array(
0 => '',
1 => '',
2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
'
),
1 => array(
0 => '1',
1 => '1',
2 => '{5E25C44F-C18A-4F54-B6B1-248955A82E59}'
)
)
);
This is the end of my comparison output in the console.
'RetTarget Area' => Array (
0 => Array (
0 => ''
1 => ''
2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
'
)
1 => Array (...)
)
)
I find it suspicious that the last Array is not fully shown in the comparison.
EDIT 2
I find here that the order of the arrays is important. I am pretty sure though I have all elements in the same order, if PHP is not doing something secret under the hood. The solution mentioned there I cannot copy, since I don't have a $this->assertEquals but a ->with($this->equalTo syntax.
EDIT 3
I read here about an undocumented parameter $canonicalize that orders arrays before comparing. When I use it like this:
$sut->expects($this->once())
->method('exposeAndSaveDataLines')
->with($this->equalTo($expectedLines, $delta = 0.0, $maxDepth = 10, $canonicalize = true, $ignoreCase = false),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything());
I see that the order of the arrays is indeed changed, but I still see the same error. Also, still one array is 'collapsed', which I suspect causes this failure. Besides, I don't want to order all my subarrays, they should be in the same order in the real and expected result.
--- Expected
+++ Actual
## ##
Array (
0 => Array (
0 => Array (
0 => ''
1 => ''
2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
'
)
1 => Array (...)
)
EDIT 4
When I use identicalTo instead of equalTo, I get a more elaborate error message, saying that the one array is not identical to the other array, while printing both of them. I copy-pasted them both into a text file, and used the command diff to check for any differences, but there were none. Still, PHPUnit claims that the two arrays are not equal/identical.
EDIT 5
When I use greaterThanOrEqual or even greaterThan instead of equalTo, then the test passes. This does not happen for lessThanOrEqual. This implies that there is a difference between the two arrays.
If I manually change the expected outcome into something with a string that is alphabetically before the correct string, I can lessThan pass as well, but then of course greaterThanOrEqual fails.
EDIT 6
I am getting convinced that the line ending of the strings in my array are making this comparison to fail, which doesn't show up in all comparisons.
I now have the following assertion.
public function test_actionUpload_v10MasterdataFile()
{
....
$sut->expects($this->once())
->method('exposeAndSaveDataLines')
->will($this->returnCallback(function($lines) {
$expectedLines = include ($this->dataDir . 'ExpectedLines.php');
$arrays_similar = $this->similar_arrays($lines, $expectedLines);
PHPUnit_Framework_Assert::assertTrue($arrays_similar);
}));
$sut->actionUpload();
}
private function similar_arrays($a, $b)
{
if(is_array($a) && is_array($b))
{
if(count(array_diff(array_keys($a), array_keys($b))) > 0)
{
print_r(array_diff(array_keys($a), array_keys($b)));
return false;
}
foreach($a as $k => $v)
{
if(!$this->similar_arrays($v, $b[$k]))
{
return false;
}
}
return true;
}
else
{
if ($a !== $b)
{
print_r(PHP_EOL . 'A: '. $a. PHP_EOL . 'Type: ' . gettype($a) . PHP_EOL);
print_r(PHP_EOL . 'B: '. $b. PHP_EOL . 'Type: ' . gettype($b) . PHP_EOL);
}
return $a === $b;
}
}
With the following result.
A: {72C2F175-9F50-4C9C-AF82-9E3FB875EA82}
Type: string
B: {72C2F175-9F50-4C9C-AF82-9E3FB875EA82}
Type: string
I finally got it to work, although it is a bit of a compromise. I am now removing newlines before I compare the arrays. This cannot be done in the with method, so I have made the following construction.
public function test_actionUpload_v10MasterdataFile()
{
/*
* Create a stub to disable the original constructor.
* Exposing data and rendering are stubbed.
* All other methods behave exactly the same as in the real Controller.
*/
$sut = $this->getMockBuilder('MasterdataController')
->setMethods(array('exposeAndSaveDataLines', 'render'))
->disableOriginalConstructor()
->getMock();
$sut->expects($this->once())
->method('exposeAndSaveDataLines')
->will($this->returnCallback(function($lines) {
$expectedLines = include ($this->dataDir . 'ExpectedLines.php');
PHPUnit_Framework_Assert::assertTrue($this->similar_arrays($lines, $expectedLines));
}));
// Execute the test
$sut->actionUpload();
}
...
private function similar_arrays($a, $b)
{
/**
* Check if two arrays have equal keys and values associated with it, without
* looking at order of elements, and discarding newlines.
*/
if(is_array($a) && is_array($b))
{
if(count(array_diff(array_keys($a), array_keys($b))) > 0)
{
return false;
}
foreach($a as $k => $v)
{
if(!$this->similar_arrays($v, $b[$k]))
{
return false;
}
}
return true;
}
else
{
$a = rtrim($a);
$b = rtrim($b);
$extended_output = false;
if ($extended_output && ($a !== $b))
{
print_r(PHP_EOL . 'A: '. $a. PHP_EOL . 'Type: ' . gettype($a) . PHP_EOL);
print_r(PHP_EOL . 'B: '. $b. PHP_EOL . 'Type: ' . gettype($b) . PHP_EOL);
}
return $a === $b;
}
}

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.
?>

PHP: Getting to a key in mulitdimensional array?

I have an array like
$myArray =array
(
"0"=>array("dogs",98),
"1"=>array("cats",56),
"2"=>array("buffaloes",78)
)
How can I get a key by providing a value?
e.g. if i search for "buffaloes" array_search may return "2".
Thanks
$myArray =array
(
"0"=>array("dogs",98),
"1"=>array("cats",56),
"2"=>array("buffaloes",78)
);
function findInArray($term, $array) {
foreach($array as $key => $val) {
if(in_array($term, $val, true)) {
return $key;
}
}
}
echo findInArray('buffaloes', $myArray); // 2
echo findInArray(78, $myArray); // 2
function asearch($key, $myArray) {
for ($i = 0; $i < sizeof($myArray); $i++) {
if ($myArray[$i][0] == $key) {
return $i;
}
}
return -1; # no match
}
Though, you'd probably want to restructure your array to:
$myarray = array(
'dogs' => 98,
'cats' => 56,
'buffaloes' => 78
);
And just do:
$myArray['buffaloes']; # 78
The only way you can do it is to iterate over every item and preform a Linear Search
$i = -1;
foreach ($myArray as $key => $item){
if ( $item[0] == 'buffaloes' ){
$i = $key;
break;
}
}
//$i now holds the key, or -1 if it doesn't exist
As you can see, it is really really inefficient, as if your array has 20,000 items and 'buffaloes' is the last item, you have to make 20,000 comparisons.
In other words, you need to redesign your data structures so that you can look something up using the key, for example a better way may be to rearrange your array so that you have the string you are searching for as the key, for example:
$myArray['buffaloes'] = 76;
Which is much much faster, as it uses a better data structure so that it only has to at most n log n comparisons (where n is the number of items in the array). This is because an array is in fact an ordered map.
Another option, if you know the exact value of the value you are searching for is to use array_search
I never heard of built in function. If you want something more general then above solutions you shold write your own function and use recursion. maybe array_walk_recursive would be helpful
You can loop over each elements of the array, testing if the first element of each entry is equal to "buffaloes".
For instance :
foreach ($myArray as $key => $value) {
if ($value[0] == "buffaloes") {
echo "The key is : $key";
}
}
Will get you :
The key is : 2
Another idea (more funny ?), if you want to whole entry, might be to work with array_filter and a callback function that returns true for the "bufalloes" entry :
function my_func($val) {
return $val[0] == "buffaloes";
}
$element = array_filter($myArray, 'my_func');
var_dump($element);
Will get you :
array
2 =>
array
0 => string 'buffaloes' (length=9)
1 => int 78
And
var_dump(key($element));
Gves you the 2 you wanted.

Categories