Unexpected behavior in recursive function - php

The task is to remove arrays recursively that have error => 4 (i.e key with that value) with their keys, and then turn remained arrays into objects.
The structure of incoming array might be different. Two examples of it are this ones:
// Example input #1
$ex_input_1 = array(
'files' => array(
0 => array(
'name' => 'file.jpg',
'size' => '244235',
'tmp_name' => '/usr/tmp/24ffds.tmp',
'error' => 0
),
1 => array(
'name' => '',
'size' => '',
'tmp_name' => '',
'error' => 4
)
),
'cover' => array(
'name' => '',
'size' => '',
'tmp_name' => '',
'error' => 4
),
'document' => array(
'name' => 'file.doc',
'size' => '244235',
'tmp_name' => '/usr/tmp/24ffds.tmp',
'error' => 0
)
);
// Example input #2
$ex_input_2 = array(
0 => array(
'name' => 'file.jpg',
'size' => '244235',
'tmp_name' => '/usr/tmp/24ffds.tmp',
'error' => 0
),
1 => array(
'name' => '',
'size' => '',
'tmp_name' => '',
'error' => 4
)
);
i.e an array that have name, size, tmp_name, error keys might be at any level down.
What I tried:
Tried to write a simple handler with two methods, where the first one is recursive handler and the second one is hydrator method. Here's it with relevant parts:
<?php
class FileInputParser
{
/**
* Recursively hydrate array entires skipping empty files
*
* #param array $files
* #return array
*/
public function hydrateAll(array $files)
{
foreach ($files as $name => $file) {
if (!is_array($file)) {
continue;
}
foreach ($file as $key => $value) {
if (is_array($value)) {
// Recursise call
$files[$name] = $this->hydrateAll($files[$name]);
} else {
$target = $this->hydrateSingle($file);
// Here I'm EXPLICTLY asking not to push an array, which has error = 4
// But it pushes anyway!!
if ($target !== false) {
unset($files[$name]);
}
}
}
}
return $files;
}
/**
* Hydrates a single file item
*
* #param array $file
* #return mixed
*/
private function hydrateSingle(array $file)
{
$entity = new stdclass;
$entity->name = $file['name'];
$entity->tmp_name = $file['tmp_name'];
$entity->error = $file['error'];
$entity->size = $file['size'];
if ($entity->error != 4) {
return $entity;
} else {
// Returning false to indicate, that this one should not be pushed in output
return false;
}
}
}
The problem
While at first glance it works, the problem is that, when I'm asking explicitly not to add an array that has error = 4 to output, but it continues to add!
You can run aforementioned code with input examples:
<?php
$parser = new FileInputParser();
$output = $parser->hydrateAll($ex_input_1);
echo '<pre>', print_r($output, true);
to see that it also returns unwanted arrays (i.e the ones that have error = 4).
The question
Why it continues to add arrays to output that have error = 4 ?
if you have a better idea on handling this, I'd love to hear it.

Here's a recursive function that will do the filtering you want. When it reaches the bottom of the tree, it checks for error == 4 and if it is, returns an empty array, otherwise it returns the current array. At the next level down any empty values returned are removed by array_filter:
function array_filter_recursive($array) {
if (isset($array['error'])) {
// bottom of tree
return $array['error'] == 4 ? array() : $array;
}
foreach ($array as $key => $value) {
$array[$key] = array_filter_recursive($value);
}
// remove any empty values
return array_filter($array);
}
Output from filtering your two input arrays:
Array (
[files] => Array (
[0] => Array (
[name] => file.jpg
[size] => 244235
[tmp_name] => /usr/tmp/24ffds.tmp
[error] => 0
)
)
[document] => Array (
[name] => file.doc
[size] => 244235
[tmp_name] => /usr/tmp/24ffds.tmp
[error] => 0
)
)
Array (
[0] => Array (
[name] => file.jpg
[size] => 244235
[tmp_name] => /usr/tmp/24ffds.tmp
[error] => 0
)
)
Demo on 3v4l.org

Related

PHP – Group output with same ID from within foreach loop

I have a user upload a .csv file from the backend of Wordpress through a custom file upload field (Advanced custom fields). I then call this field to retrieve the data from that file to create an array of said data:
$upload_cq = get_field('report_current_quarter');
$report_cq = array();
if(($handle_cq = fopen($upload_cq, "r")) !== FALSE)
{
while(($data_cq = fgetcsv($handle_cq, 1000, ",")) !== FALSE)
{
$report_cq[] = $data_cq;
}
$results_cq = array_slice($report_cq, 3); // remove unwanted row
fclose($handle_cq);
}
Once I create the array, I then create another array with a key and associate value, and ensure that the "team_id" column is present & not empty, as so (there is a teamName function which I won't get into):
foreach($results_cq as $results)
{
$team_id = $results[6];
if(!empty($team_id))
{
$team_totals_cq[] = array(
'id' => $team_id,
'team' => teamName($team_id),
'total_volume' => $results[41],
'total_closed' => $results[24],
'listings_closed' => $results[22],
'buyers_closed' => $results[23],
'total_agc' => $results[29],
'rental_agc' => $results[30],
);
}
}
echo '<pre>'; print_r($team_totals_cq); echo '</pre>';
When printing the array, I get the following. My question now is; How do I group results with the same team "id" and add up their results (results = total_volume, total_closed, listings_closed, buyers_closed, total_agc, and rental_agc):
Array
(
...
[6] => Array
(
[id] => 0011
[team] => Williamson Team
[total_volume] => $990,000
[total_closed] => 4
[listings_closed] => $0.00
[buyers_closed] => 1
[total_agc] => $20,812.50
[rental_agc] => $23,812.50
)
...
[9] => Array
(
[id] => 0011
[team] => Williamson Team
[total_volume] => $415,000
[total_closed] => 2
[listings_closed] => $0.00
[buyers_closed] => 0
[total_agc] => $12,450.00
[rental_agc] => $12,450.00
)
...
)
I have tried everything within my abilities, and nothing has worked.
One method is to use team_id as keys in $team_totals_cq then simply add up the values as you go through the loop.
(Note: The preg_replace function strips anything that aren't numbers or .)
$team_totals_cq = [];
foreach ($results_cq as $results) {
$team_id = $results[6];
if (!empty($team_id)) {
if (!isset($team_totals_cq[$team_id])) {
$team_totals_cq[$team_id] = [
'id' => $team_id,
'team' => teamName($team_id),
'total_volume' => preg_replace("/[^0-9\.]/", '', $results[41]),
'total_closed' => $results[24],
'listings_closed' => preg_replace("/[^0-9\.]/", '', $results[22]),
'buyers_closed' => $results[23],
'total_agc' => preg_replace("/[^0-9\.]/", '', $results[29]),
'rental_agc' => preg_replace("/[^0-9\.]/", '', $results[30])
];
} else {
$team_totals_cq[$team_id]['total_volume'] += preg_replace("/[^0-9\.]/", '', $results[41]);
$team_totals_cq[$team_id]['total_closed'] += $results[24];
$team_totals_cq[$team_id]['listings_closed'] += preg_replace("/[^0-9\.]/", '', $results[22]);
$team_totals_cq[$team_id]['buyers_closed'] += $result[23];
$team_totals_cq[$team_id]['total_agc'] += preg_replace("/[^0-9\.]/", '', $results[29]);
$team_totals_cq[$team_id]['rental_agc'] += preg_replace("/[^0-9\.]/", '', $results[30]);
}
}
}

Adaptive parent relative to children's attributes in a recursive array

I'm trying to make a simple tree structure where every task has a certain percentage of completion and its parent has to inherit average completion of its direct children, as seen conceptually on the picture below. (0s are percentages of completion, for example subtask2 could be 100% and subtask2 0%, which would give task1 50% completion and therefore stackoverflow would have 25%, given task2 is at 0)
The issue I'm having is that I need to start, apparently, from the deepest children, but I can't seem to figure out how to implement such reversal traversal from leafs to root.
I have tried with normal recursive as well as double for loop and both only achieve first level calculations (in the picture example task1 is calculated, but stackoverflow will remain 0).
Note: Only leafs can actually have completion percentage, since every other element, which is not a leaf, inherits percentage from its children. (how paradoxical)
If any of you have any ideas on how to implement such an algorithm, be it conceptually or actual code, I would very much appreciate any input.
Below is structure of this array (only kept relevant information):
[0] => Array
(
[title] => stackoverflow
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => task2
[completion] => 0
)
[1] => Array
(
[title] => task1
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => subtask2
[completion] => 100
)
[1] => Array
(
[title] => subtask1
[completion] => 0
)
)
)
)
)
I seem to be having a similar issue than the issue in this thread: Percentages and trees however, I need my task to have actual percentages, not only completed/non-completed. All math is completely linear, meaning that parent's percentage = (addition of all percentages of children) / (number of children)
Also var_export:
array (
0 =>
array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
)
Here is a recursive function that passes the parent by reference until it finds a leaf and updates totals working backward.
function completionTree(&$elem, &$parent=NULL) {
// Handle arrays that are used only as a container... if we have children but no uuid, simply descend.
if (is_array($elem) && !isset($elem['uuid'])) {
foreach($elem AS &$child) {
completionTree($child, $elem);
}
}
// This array has children. Iterate recursively for each child.
if (!empty($elem['children'])) {
foreach ($elem['children'] AS &$child) {
completionTree($child, $elem);
}
}
// After recursion to handle children, pass completion percentages up to parent object
// If this is the top level, nothing needs to be done (but suppress that error)
if (#$parent['completed'] !== NULL) {
// Completion must be multiplied by the fraction of children it represents so we always add up to 100. Since values are coming in as strings, cast as float to be safe.
$parent['completed'] = floatval($parent['completed']) + (floatval($elem['completed']) * (1/count($parent['children'])));
}
}
// Data set defined statically for demonstration purposes
$tree = array(array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '100',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
);
// Launch recursive calculations
completionTree($tree);
// Dump resulting tree
var_dump($tree);
Though this is answered, I'd like to leave a solution that seems a bit more intuitive (IMHO). Instead of passing down the parent, just handle the children first:
/**
* #param array $nodes
*
* #return array
*/
function calcCompletion(array $nodes): array {
// for each node in nodes
return array_map(function (array $node): array {
// if it has children
if (array_key_exists('children', $node) && is_array($node['children'])) {
// handle the children first
$node['children'] = calcCompletion($node['children']);
// update this node by *averaging* the children values
$node['completed'] = array_reduce($node['children'], function (float $acc, array $node): float {
return $acc + floatval($node['completed']);
}, 0.0) / count($node['children']);
}
return $node;
}, $nodes);
}
Well, this might be a little overhead, but you can use RecursiveArrayIterator. First, you must extend it to handle your tree structure:
class MyRecursiveTreeIterator extends RecursiveArrayIterator
{
public function hasChildren()
{
return isset($this->current()['children'])
&& is_array($this->current()['children']);
}
public function getChildren()
{
return new static($this->current()['children']);
}
}
Then using RecursiveIteratorIterator you can create an iterator which will process your tree starting from leaves:
$iterator = new RecursiveIteratorIterator(
new MyRecursiveTreeIterator($tasks),
RecursiveIteratorIterator::CHILD_FIRST
);
Then with this one, you can add your calculation logic:
$results = [];
$temp = [];
$depth = null;
foreach ($iterator as $node) {
if ($iterator->getDepth() === 0) {
// If there were no children use 'completion'
// else use children average
if (
is_null($depth)
|| !isset($temp[$depth])
|| !count($temp[$depth])
) {
$percentage = $node['completed'];
} else {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
}
$results[$node['title']] = $percentage;
continue;
}
// Set empty array for current tree depth if needed.
if (!isset($temp[$iterator->getDepth()])) {
$temp[$iterator->getDepth()] = [];
}
// If we went up a tree, collect the average of children
// else push 'completion' for children of current depth.
if ($iterator->getDepth() < $depth) {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
$temp[$depth] = [];
$temp[$iterator->getDepth()][] = $percentage;
} else {
$temp[$iterator->getDepth()][] = $node['completed'];
}
$depth = $iterator->getDepth();
}
Here is a demo.

Parse an "flat" array to a one that represents a directory structure

I have a list of "directory objects" that look something like this:
$directoryObjects = [
[
'type' => 'folder',
'name' => 'animals',
'path' => '/animals',
'path_array' => ['animals']
],
[
'type' => 'folder',
'name' => 'cat',
'path' => '/animals/cat',
'path_array' => ['animals', 'cat']
],
[
'type' => 'folder',
'name' => 'images',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'cat001.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'cat002.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
]
];
This is outputted by my SQL database but it has to be formatted in my API responseas a tree structure. I've decided the best way to represent this tree structure is outlined in the question of this thread: Convert a directory structure in the filesystem to JSON with Node.js. Doing a print_r(json_decode($jsonTreeStructure)) outputs this:
Array
(
[0] => stdClass Object
(
[type] => folder
[name] => animals
[path] => /animals
[children] => Array
(
[0] => stdClass Object
(
[type] => folder
[name] => cat
[path] => /animals/cat
[children] => Array
(
[0] => stdClass Object
(
[type] => folder
[name] => images
[path] => /animals/cat/images
[children] => Array
(
[0] => stdClass Object
(
[type] => file
[name] => cat001.jpg
[path] => /animals/cat/images/cat001.jpg
)
[1] => stdClass Object
(
[type] => file
[name] => cat001.jpg
[path] => /animals/cat/images/cat002.jpg
)
)
)
)
)
)
)
)
I'd like my $directoryObjects to be formatted to the output above. When I do a json_encode($output) it should output in the format shown in the thread linked above
[
{
type: "folder",
name: "animals",
path: "/animals",
children: [
{
type: "folder",
name: "cat",
path: "/animals/cat",
children: [
{
type: "folder",
name: "images",
path: "/animals/cat/images",
children: [
{
type: "file",
name: "cat001.jpg",
path: "/animals/cat/images/cat001.jpg"
}, {
type: "file",
name: "cat001.jpg",
path: "/animals/cat/images/cat002.jpg"
}
]
}
]
}
]
}
];
I'm having a hard time getting off the ground. I'm embarrassed to post what I have done so far, but this is it:
$jsonDir = [];
foreach($directoryObjects as $i => $dirObj)
{
$cur = &$jsonDir;
foreach ($dirObj['path_array'] as $i => $dirName)
{
$cur = &$jsonDir[$i];
}
$cur[] = $dirObj;
}
I'm having a difficult time getting to a child node and appending $dirObjthe correct place.
This is a tree walk. However, I realised that it would be slow for large lists of files. And, what about invalid paths? The end result of trying to address these issues is provided here.
Demonstration of the code and data at eval.in
Source code at pastebin
The code is documented to explain why and how it goes about doing things. Some of it is 'clumsy'. i.e. the FindParent as it has to recognize errors.
Explanation:
The issues:
1) There are no array keys, based on the data, used in the output tree. This means all the tree has to searched to find any particular parent. I suspect it could get slow for large trees. I thought it was worthwhile doing extra work to avoid that.
2) I wondered how much extra complexity there would be to identify trying to identify invalid nodes being added to the tree.
Folders:
Build the tree of folders first - recursively
Ok, need to ensure the folders are in the correct order and are first in the list.
sort the directories so that folders are first and sorted by path length.
Add the folders to the tree.
For each folder:
find the parent node - return an error if one cannot be found.
Add it to the tree and add it to a separate array ($nodeRefs) using the path as the array key. This is useful as we can find the parent of a file quickly later.
invalid parents get added to an $errors array for reporting later.
Files:
These are sorted as well but it actually isn't that important as we can find the parent quickly by using the 'path' as the key to lookup the parent in $nodeRefs.
Any files not found in $nodeRefs must be errors.
The BuildTree Class:
/* -------------------------------------------------------------------------------------------------------
* Build the tree folders first
*
* a) I am concerned about efficiency on trees with large numbers of files.
* To deal with this i keep a list of folder nodes so the file insert will be efficient.
*
* b) I am concerned about folder errors such as duplicates or invalid paths so I have attempted to deal with these.
*
* c) The root node is optional in the source data. It just creates one.
*
* To simplify the tree building logic I will sort the array into some useful order for me.
*
* Specifically:
*
* 1) Folders
* a) path length order
*
* 2) Files
* b) Path length order
*
* This will make the tree building easier but the cost is a sort plus the cost of the building the tree
*
*
*/
class BuildTree
{
/**
* The source data
*
* #var array
*/
private $directories = array();
/**
* References to the folders in the directory list.
* Makes adding files a lot more efficient
*
* #var array
*/
private $nodeRefs = array();
/**
* Invalid nodes - there is am error message as to why it failed
*
* #var array
*/
private $errors = array();
/**
* The tree - all the nodes are references rather than copies of the data
*
* #var array
*/
private $tree = null;
/**
* The root node to make the tree complete
*
* #var array
*/
private $rootNode = array(
'type' => 'folder',
'name' => 'root',
'path' => '/',
'path_array' => array(),
'children' => array(),
);
/**
* The start index of the first file in the sorted input
*
* #var integer
*/
private $firstFile = -1;
/**
* Sort the directory input in folders by length and then files
*
* #param array $directories
*
* #return void
*/
public function __construct($directories)
{
$this->sort($directories);
$this->directories = $directories;
$this->tree = &$this->rootNode; // make the tree a 'complete tree' - simplifies the addNode logic
}
/**
* Just executes the:
* 1) the process folders (build the tree of directory nodes
* 2) adds the files to the correct nodes
*
* #return boolean eorros or not
*/
public function buildTree()
{
$this->buildFolderTree();
$this->addFiles();
return count($this->errors) <= 0;
}
/**
* All the folders are at the top of the directories list
*
* Read through the list and add all the folders into the tree
*
* Foreach folder:
* 1) Find the parent
* 2) if valid add to the tree
* 3) record the reference to it so we can add files easily later
*
* #return void
*/
public function buildFolderTree()
{
// add rootnode to the list
$this->nodeRefs[$this->tree['path']] =& $this->tree;
foreach ($this->directories as $idx => &$node) {
if ($node['type'] !== 'folder') { // record the index of the first file
$this->firstFile = $idx; // needed for processing the files efficiently
break;
}
if (empty($node['path_array'])) { // you passed a root anyway - ignore it ;-/
continue;
}
$node['children'] = array(); // needed for adding nodes to the tree
$result = $this->findParent($node, $this->tree);
if ($result['isError'] || !$result['isParent']) { // ignore as invalid...
$this->errors[] = $result;
continue;
}
// add to the tree as a reference...
$parent =& $result['treeNode'];
$parent['children'][] =& $this->directories[$idx]; // reference to the original node
// fast lookup later... when we add files
$this->nodeRefs[$node['path']] =& $node;
}
unset($node); // magic - 'cos we used a reference in a foreach loop.
}
/**
* This does not need to do a treewalk to find the parent.
*
* All the parents are in the $nodeRefs array with a key of the `path`
*
* This makes inserting the files quick
*
* #return void
*/
public function addFiles()
{
foreach (array_slice($this->directories, $this->firstFile) as $idx => $file) {
if (isset($this->nodeRefs[$file['path']])) { // add to the children
$this->nodeRefs[$file['path']]['children'][] = $file;
}
else { // make an error and add to the reject pile.
$result = array('isError' => false,
'isParent' => false,
'treeNode' => null,
'node' => $file,
'message' => 'invalid folder path');
$this->errors[] = $result;
}
}
}
/**
* Get as close to the folder as you can
*
* Return the node as a reference as we want to update it in some way
*
* 1) folder:
*
* a) You get to the parent where the new folder will be
* i.e. treeNode depth is one less than the new node depth
*
* b) the node already exists so nothing to do
* i.e. treeNode depth = new node depth
*
* c) there is a node missing from the path - wrong name etc.
* i.e. treeNode depth is 2 or more less than the new node depth
*
* *
* #param array $node
* #param array $treeNode
* #param integer $level
*
* #return treeNode
*/
public function findParent(&$node, &$treeNode, $level = 0)
{
$nodePathLength = count($node['path_array']); // keep track of these for now to ease debugging
$treeNodeParentLevel = $nodePathLength - 1; // the tree must match to here the tree node to be a parent
// i.e. less or more than this and the node is an error
$treeNodePathLength = count($treeNode['path_array']); // this must be one less for it to be a true parent
// All the useful information you need about the parent and it's possible child
$result = array('isError' => false,
'isParent' => false,
'treeNode' => &$treeNode,
'node' => &$node,
'message' => '');
// these are always correct by definition
if ($nodePathLength <= 1) { // the root is always the parent of the first level
$result['isParent'] = true;
$result['message'] = 'root or first child';
return $result;
}
if ($level > $treeNodeParentLevel) { // this path already exists in the tree
$result['isError'] = true;
$result['isParent'] = false;
$result['message'] = 'duplicate';
return $result;
}
// we are looking for this in the current treeNode Children
$nodeDir = $node['path_array'][$level];
foreach ($treeNode['children'] as $idx => &$tnode) {
$tnodeDir = $tnode['path_array'][$level];
if ($nodeDir === $tnodeDir) { // match at this level - check the next one
return $this->findParent($node, $tnode, $level + 1);
}
}
unset($tnode); // do this when using references in foreach loops
// can only get to here if the current nodeDir was not found in the children
if ($level == $treeNodeParentLevel) {
$result['isParent'] = true;
$result['message'] = 'matched path';
}
else { // someone has gotten really confused...
$result['isError'] = true;
$result['message'] = 'i am confused';
}
return $result;
}
/**
* Sort folders to the top in increasing path length
* then files in increasing path length
*
* #return void
*/
public function sort(&$directories)
{
usort($directories, function ($node1, $node2) {
if ($node1['type'] === $node2['type']) { // same type - check path length
$n1pl = count($node1['path_array']);
$n2pl = count($node2['path_array']);
if ($n1pl > $n2pl) { // higher counts sort after
return +1;
}
if ($n1pl < $n2pl) { // lower counts sort earlier
return -1;
}
return $node1['path'] < $node2['path'] ? -1 : +1; // lower name - sort earlier
}
return $node1['type'] === 'folder' ? -1 : +1; // folders sort before files
});
}
public function getErrors()
{
return $this->errors;
}
public function getTree()
{
return $this->tree['children'];
}
public function getJsonTree()
{
return json_encode($this->getTree());
}
// get any of the properties directly if you wish
public function __get($name)
{
return $this->$name;
}
// Cheap and cheerful disply of the errors
public function printResult($result)
{
$node = $result['node'];
echo PHP_EOL, '---------', PHP_EOL;
echo 'type =>', $node['type'], PHP_EOL;
echo 'name =>', $node['name'], PHP_EOL;
echo 'path =>', $node['path'], PHP_EOL;
echo 'message =>', $result['message'], PHP_EOL;
if (!empty($node['path_array'])) {
echo 'path_array =>', implode(', ', $node['path_array']), PHP_EOL;
}
if (isset($node['children']) && count($node['children'])> 0) {
echo 'children count => ', count($node['children']), PHP_EOL;
};
}
}
Run the code:
$bt = new BuildTree($directoryObjects);
$allOk = $bt->buildTree();
$json = $bt->getJsonTree();
To see the tree and errors:
print_r($bt->tree);
foreach ($bt->errors as $result) {
$bt->printResult($result);
}
Sample errors
Errors
---------
type =>folder
name =>rubbish
path =>/rubbish/cat
message =>i am confused
path_array =>rubbish, cat
---------
type =>folder
name =>images - some
path =>/animals/cat/images
message =>duplicate
path_array =>animals, cat, images
---------
type =>file
name =>rubbishfile.png
path =>/crockery/foo/bar
message =>invalid folder path
path_array =>crockery, foo, bar
---------
type =>file
name =>rubbishAgain.png
path =>/foo/bar/baz
message =>invalid folder path
path_array =>foo, bar, baz
Sample tree:
Array
(
[type] => folder
[name] => root
[path] => /
[path_array] => Array
(
)
[children] => Array
(
[0] => Array
(
[type] => folder
[name] => animals
[path] => /animals
[path_array] => Array
(
[0] => animals
)
[children] => Array
(
[0] => Array
(
[type] => folder
[name] => cat
[path] => /animals/cat
[path_array] => Array
(
[0] => animals
[1] => cat
)
[children] => Array
(
[0] => Array
(
[type] => folder
[name] => images - pretty
[path] => /animals/cat/images
[path_array] => Array
(
[0] => animals
[1] => cat
[2] => images
)
[children] => Array
(
[0] => Array
(
[type] => file
[name] => AtlasX.png
[path] => /animals/cat/images
[path_array] => Array
(
[0] => animals
[1] => cat
[2] => images
)
)
[1] => Array
(
[type] => file
[name] => AtlasX.png
[path] => /animals/cat/images
[path_array] => Array
(
[0] => animals
[1] => cat
[2] => images
)
)
)
)
)
)
)
)
[1] => Array
(
[type] => folder
[name] => crockery
[path] => /crockery
[path_array] => Array
(
[0] => crockery
)
[children] => Array
(
[0] => Array
(
[type] => folder
[name] => cups
[path] => /crockery/cups
[path_array] => Array
(
[0] => crockery
[1] => cups
)
[children] => Array
(
[0] => Array
(
[type] => file
[name] => cup.png
[path] => /crockery/cups
[path_array] => Array
(
[0] => crockery
[1] => cups
)
)
)
)
[1] => Array
(
[type] => file
[name] => crockeryThumb.png
[path] => /crockery
[path_array] => Array
(
[0] => crockery
)
)
)
)
[2] => Array
(
[type] => file
[name] => unicorn.jpg
[path] => /
[path_array] => Array
(
)
)
)
)
Test Data- including errors
$directoryObjects = [
[
'type' => 'file',
'name' => 'unicorn.jpg',
'path' => '/',
'path_array' => []
],
[
'type' => 'folder',
'name' => 'animals',
'path' => '/animals',
'path_array' => ['animals']
],
[
'type' => 'folder',
'name' => 'cat',
'path' => '/animals/cat',
'path_array' => ['animals', 'cat']
],
[
'type' => 'folder',
'name' => 'images - pretty',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'folder',
'name' => 'images - some',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'AtlasX.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'AtlasX.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'folder',
'name' => 'crockery',
'path' => '/crockery',
'path_array' => ['crockery']
],
[
'type' => 'folder',
'name' => 'cups',
'path' => '/crockery/cups',
'path_array' => ['crockery', 'cups']
],
[
'type' => 'file',
'name' => 'cup.png',
'path' => '/crockery/cups',
'path_array' => ['crockery', 'cups']
],
[
'type' => 'file',
'name' => 'crockeryThumb.png',
'path' => '/crockery',
'path_array' => ['crockery']
],
[
'type' => 'folder',
'name' => 'rubbish',
'path' => '/rubbish/cat',
'path_array' => ['rubbish', 'cat']
],
[
'type' => 'folder', // will be ignored as I generate one
'name' => 'root',
'path' => '/',
'path_array' => []
],
[
'type' => 'file',
'name' => 'rubbishfile.png',
'path' => '/crockery/foo/bar',
'path_array' => ['crockery', 'foo', 'bar']
],
[
'type' => 'file',
'name' => 'rubbishAgain.png',
'path' => '/foo/bar/baz',
'path_array' => ['foo', 'bar', 'baz']
],
];
I like your question and I'd time to code something... but this assumes that your array of directories are ordered by path from top to bottom
<?php
$directoriesArray = [
[
'type' => 'folder',
'name' => 'animals',
'path' => '/animals',
'path_array' => ['animals']
],
[
'type' => 'folder',
'name' => 'cat',
'path' => '/animals/cat',
'path_array' => ['animals', 'cat']
],
[
'type' => 'folder',
'name' => 'images',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'AtlasX.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
],
[
'type' => 'file',
'name' => 'AtlasX.png',
'path' => '/animals/cat/images',
'path_array' => ['animals', 'cat', 'images']
]
];
class fileObj
{
public $type;
public $name;
public $path;
public function __construct( array $directoryArray )
{
$this->name = $directoryArray['name'];
$this->type = $directoryArray['type'];
$this->path = $directoryArray['path'];
}
}
class directoryObj
{
public $type;
public $name;
public $path;
public $children = array();
public function __construct( array $directoryArray )
{
$this->name = $directoryArray['name'];
$this->type = $directoryArray['type'];
$this->path = $directoryArray['path'];
}
public function addChild( $child, $directory = null ){
if( !count($this->children) ){
$this->createAndAddToChildren($child);
return;
}
$sameChild = array_filter(
$this->children,
function( $savedChild ) use ( $child ){
switch($savedChild->type){
case 'folder':
return array_search($savedChild->name, $child['path_array']) !== false;
break;
case 'file':
return $savedChild->name == $child['name'] ;
break;
}
}
);
if(count($sameChild)){
$myChild = array_shift($sameChild);
if( $myChild->type == 'folder' ){
$myChild->addChild($child);
}
}
else{
$this->createAndAddToChildren($child);
}
}
private function createAndAddToChildren($child){
switch($child['type']){
case 'folder':
echo 'addedDirectory <br/>';
$this->children[] = new directoryObj($child);
break;
case 'file':
echo 'addedFile <br/>';
$this->children[] = new fileObj($child);
break;
}
}
}
$mainDirectory = new directoryObj(array_shift($directoriesArray));
foreach( $directoriesArray as $directoryArray ){
$mainDirectory->addChild($directoryArray);
}
Hope this help :-)
Good luck

Array containing keys and associative array, how to make a relation

I have an array which contains the path to a specific value from an other array, to make it a bit more clear, here is an exemple.
My array containing the keys which I'll call $params
Array
(
[0] => paths
[1] => assets
[2] => js
)
And here is my associative array which I'll call $config
Array
(
[paths] => Array
(
[assets] => Array
(
[js] => /assets/js
[css] => /assets/css
)
)
[library] => Array
(
[js] => jQuery
)
)
So how could I use my array 1 to access the value in my array 2?
I tried $config[$params[0]][$params[1]][$params[2]], but it's not efficient at all.
You can try
$path = array(
0 => 'paths',
1 => 'assets',
2 => 'js',
);
$data = array(
'paths' => array(
'assets' => array(
'js' => '/assets/js',
'css' => '/assets/css',
),
),
'library' => array(
'js' => 'jQuery',
),
);
$temp = $data;
foreach($path as $key) {
$temp = $temp[$key];
}
var_dump($temp);
Output
string '/assets/js' (length=10)
A loop should solve your problem:
$c = $config;
foreach($params as $path) {
if(!array_key_exists($path, $c)) {
$c = null;
break;
}
$c = $c[$path];
}
This will iterate over every entry in $params and then access the subkey of the $config array. After finding it, $c will contain the current subarray. In the end, $c will contain the value you were looking for (NULL if the path was invalid/not found).
The same can be done in a functional way using the array_reduce function:
$path = array_reduce(function($current, $path) {
if($current == NULL || !array_key_exists($path, $current))
return NULL;
return $current[$path];
}, $params, $config);
Hi Jonathan here you have missed one brace in the end
try this "$config[$params[0]][$params[1]][$params[2]]".
It will work
I am posting a code which worked fine for me
<?php
$params = array(0 => 'paths',1 => 'assets',2 => 'js');
echo '<pre>';print_r($params);
$config = array
(
'paths' => array
(
'assets' => array
(
'js' => '/assets/js',
'css' => '/assets/css'
)
),
'library' => array
(
'js' => 'jQuery'
)
);
echo '<pre>';print_r($config);
echo $config[$params[0]][$params[1]][$params[2]];
?>

Check the 'form' of a multidimensional array for validation [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
recursive array_diff()?
I have a static multidimensional array which is always going to be in the same form. E.g. it will have the same keys & hierarchy.
I want to check a posted array to be in the same 'form' as this static array and if not error.
I have been trying various methods but they all seem to end up with a lot of if...else components and are rather messy.
Is there a succinct way to achieve this?
In response to an answer from dfsq:
$etalon = array(
'name' => array(),
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => array(),
);
$test = array(
'name' => array(),
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => array(),
);
// Tests
$diff = array_diff_key_recursive($etalon, $test);
var_dump(empty($diff));
print_r($diff);
And the results from that are
bool(false)
Array ( [name] => 1 [income] => Array ( [month] => 1 [year] => 1 ) [message] => 1 )
Author needs a solution which would test if the structure of the arrays are the same. Next function will make a job.
/**
* $a1 array your static array.
* $a2 array array you want to test.
* #return array difference between arrays. empty result means $a1 and $a2 has the same structure.
*/
function array_diff_key_recursive($a1, $a2)
{
$r = array();
foreach ($a1 as $k => $v)
{
if (is_array($v))
{
if (!isset($a2[$k]) || !is_array($a2[$k]))
{
$r[$k] = $a1[$k];
}
else
{
if ($diff = array_diff_key_recursive($a1[$k], $a2[$k]))
{
$r[$k] = $diff;
}
}
}
else
{
if (!isset($a2[$k]) || is_array($a2[$k]))
{
$r[$k] = $v;
}
}
}
return $r;
}
And test it:
$etalon = array(
'name' => '',
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => ''
);
$test = array(
'name' => 'Tomas Brook',
'income' => array(
'day' => 123,
'month' => 123,
'year' => array()
)
);
// Tests
$diff = array_diff_key_recursive($etalon, $test);
var_dump(empty($diff));
print_r($diff);
This will output:
bool(false)
Array
(
[income] => Array
(
[month] => Array()
)
[message] =>
)
So checking for emptiness of $diff array will tell you if arrays have the same structure.
Note: if you need you can also test it in other direction to see if test array has some extra keys which are not present in original static array.
You could user array_intersect_key() to check if they both contain the same keys. If so, the resulting array from that function will contain the same values as array_keys() on the source array.
AFAIK those functions aren't recursive, so you'd have to write a recursion wrapper around them.
See User-Notes on http://php.net/array_diff_key
Are you searching for the array_diff or array_diff_assoc functions?
use foreach with the ifs...if u have different tests for the different inner keys eg
$many_entries = ("entry1" = array("name"=>"obi", "height"=>10));
and so on, first define functions to check the different keys
then a foreach statement like this
foreach($many_entries as $entry)
{
foreach($entry as $key => $val)
{
switch($key)
{
case "name": //name function
//and so on
}
}
}

Categories