I have a hierarchical array in my project like this:
$Array = array(
array(
'Id' => 1,
'Title' => 'Some Text1',
'Children' => array(
array(
'Id' => 11,
'Title' => 'Some Text11',
'Children' => array(
array(
'Id' => 111,
'Title' => 'Some Text111',
),
array(
'Id' => 112,
'Title' => 'Some Text112',
'Children' => array(
array(
'Id' => 1121,
'Title' => 'Some Text1121',
)
)
)
)
),
array(
'Id' => 12,
'Title' => 'Some Text12',
'Children' => array(
array(
'Id' => 121,
'Title' => 'Some Text121',
)
)
)
)
),
array(
'Id' => 2,
'Title' => 'Some Text2',
)
);
I want to search my string (such as 'Some Text1121') in 'Title' index in this array and return it's path such as after search 'Some Text1121' I want to return this result:
"1 -> 11 -> 112 -> 1121"
Or when I Search 'Some' string, return all path in array.
please help me, thanks.
I've quickly written you something. It's not perfect, but you get the idea:
<?php
function searchRec($haystack, $needle, $pathId = Array(), $pathIndex = Array()) {
foreach($haystack as $index => $item) {
// add the current path to pathId-array
$pathId[] = $item['Id'];
// add the current index to pathIndex-array
$pathIndex[] = $index;
// check if we have a match
if($item['Title'] == $needle) {
// return the match
$returnObject = new stdClass();
// the current item where we have the match
$returnObject->match = $item;
// path of Id's (1, 11, 112, 1121)
$returnObject->pathId = $pathId;
// path of indexes (0,0,1,..) - you might need this to access the item directly
$returnObject->pathIndex = $pathIndex;
return $returnObject;
}
if(isset($item['Children']) && count($item['Children']>0)) {
// if this item has children, we call the same function (recursively)
// again to search inside those children:
$result = searchRec($item['Children'], $needle, $pathId, $pathIndex);
if($result) {
// if that search was successful, return the match-object
return $result;
}
}
}
return false;
}
// useage:
$result = searchRec($Array, "Some Text11");
var_dump($result);
// use
echo implode(" -> ", $result->pathId);
// to get your desired 1 -> 11 -> 112
EDIT: rewritten to make the function actually return something. It now returns an Object with the matching item, the path of Id's and the path of (array-) Indexes.
Related
I'm currently developing this code that traverse a hierarchical array which should compute the sub-total of a property called cur_compensation. My issue is that the changes I do is not getting save
private function computeSubTotal($hierarchy){
foreach($hierarchy["_children"] as $key => $value){
if(isset($value["_children"]))
{
static::computeSubTotal($value);
}
else{
foreach($hierarchy["_children"] as $employee){
$employee_cur_compensation = $employee["cur_compensation"] ?? 0;
if (!isset($hierarchy["cur_compensation"])) {
$hierarchy["cur_compensation"] = 0;
}
$hierarchy["cur_compensation"] += $employee_cur_compensation;
}
return $hierarchy;
}
}
return $hierarchy;
}
This is the function so what it does it goes to the deepest node, the deepest node is a value that does not have any _children which mean it doesn't have any sub department (the hierarchy is sorted that the sub department are always on top)
The issue I have, once it reaches the bottom it computes the cur_compensation by looping through the employees of that department and adding it on the department "cur_compensation" property.
The issue is that, it doesn't save any of my changes.
So the purpose of the function is to add up the 'cur_compensation' of each employee/sub-department.
For example ->
$rows = array(
array(
'name' => "Main",
'id' => 1,
'parent_id' => 0,
'cur_compensation' => 0,
'_children' => array(
array(
'name' => "Dept A",
'id' => 2,
'parent_id' => 1),
),
array(
'name' => "Dept B",
'id' => 3,
'parent_id' => 1,
'_children' => array(
array(
'name' => "Dept C",
'cur_compensation' => 30000,
'id' => 4,
'parent_id' => 3),
array(
'name' => "Employee C",
'cur_compensation' => 30000,
'id' => 7,
'parent_id' => 3
)
)),
array(
'name' => "Employee A",
'cur_compensation' => 20000,
'id' => 5,
'parent_id' => 1
),
array(
'name' => "Employee B",
'cur_compensation' => 30000,
'id' => 6,
'parent_id' => 1
)
)
)
);
The result I want to get would be:
$rows = array(
array(
'name' => "Main",
'id' => 1,
'parent_id' => 0,
'cur_compensation' => 120000,
'_children' => array(
array(
'name' => "Dept A",
'id' => 2,
'cur_compensation' => 0,
'parent_id' => 1),
),
array(
'name' => "Dept B",
'id' => 3,
'parent_id' => 1,
'cur_compensation' => 60000,
'_children' => array(
array(
'name' => "Dept C",
'cur_compensation' => 30000,
'id' => 4,
'parent_id' => 3),
array(
'name' => "Employee C",
'cur_compensation' => 30000,
'id' => 7,
'parent_id' => 3
)
)),
array(
'name' => "Employee A",
'cur_compensation' => 30000,
'id' => 5,
'parent_id' => 1
),
array(
'name' => "Employee B",
'cur_compensation' => 30000,
'id' => 6,
'parent_id' => 1
)
)
)
);
So you would notice that Main and Dept B got the cur_compensation based on the _children property
There's a few things to make note on here - so I'm going to add comments to your existing code, then provide an example of how you could change it.
(I've formatted the code in each case)
class Example {
// filler code so that we can call
public function process($array){
return $this->computeSubTotal($array);
}
private function computeSubTotal($hierarchy) {
// we're not checking whether "_children" property exists before looping on it
foreach ($hierarchy["_children"] as $key => $value) {
if (isset($value["_children"])) {
// we're calling the method, but not doing anything with the return value.
static::computeSubTotal($value);
// we can set the original array value instead which will provide a modified copy
// this can be resolved by uncommenting the line below
// $hierarchy["_children"][$key] = static::computeSubTotal($value);
// also note that if this "child" doesn't have any *grand*children
// then we won't get an updated value due to how this is structured
// to fix this, you could remove the else wrapping so that the code
// below runs always
} else {
// double looping - we're already looping this array
// this will cause the end value to increase exponentially
foreach ($hierarchy["_children"] as $employee) {
$employee_cur_compensation = $employee["cur_compensation"] ?? 0;
if (!isset($hierarchy["cur_compensation"])) {
$hierarchy["cur_compensation"] = 0;
}
$hierarchy["cur_compensation"] += $employee_cur_compensation;
}
// returning whole array inside the loop is not ideal
// we have already adjusted the main array
// comment out this return to prevent that from happening
return $hierarchy;
}
}
return $hierarchy;
}
}
$example = new Example;
// calling this on $rows won't give us anything back
// since $rows doesn't contain the property "_children"
$rows = $example->process($rows);
// in this case, you would want to process each array result
// only on this primary array
foreach($rows as $index => $value){
$rows[$index] = $example->process($value);
}
echo json_encode($rows, JSON_PRETTY_PRINT);
Taking those comments into account, you would end up with something like this:
private function computeSubTotal($hierarchy) {
// we're not checking whether "_children" property exists before looping on it
foreach ($hierarchy["_children"] as $key => $value) {
if (isset($value["_children"])) {
$hierarchy["_children"][$key] = static::computeSubTotal($value);
}
// double looping - we're already looping this array
// this will cause the end value to increase exponentially
foreach ($hierarchy["_children"] as $employee) {
$employee_cur_compensation = $employee["cur_compensation"] ?? 0;
if (!isset($hierarchy["cur_compensation"])) {
$hierarchy["cur_compensation"] = 0;
}
$hierarchy["cur_compensation"] += $employee_cur_compensation;
}
}
return $hierarchy;
}
That's closer but still, it's not quite correct due to the double looping.
I've made a simpler version that is hopefully easy to follow:
private function computeSubTotal($hierarchy) {
if (!isset($hierarchy["_children"])) {
return $hierarchy;
}
// define this outside the loop for clarity
if (!isset($hierarchy["cur_compensation"])) {
$hierarchy["cur_compensation"] = 0;
}
foreach ($hierarchy["_children"] as $key => $value) {
// don't need to check for "_children" property
// as it's now handled in this function
$updated = static::computeSubTotal($value);
// reference the $updated array to increment
// the "cur_compensation" field
$hierarchy["cur_compensation"] += $updated["cur_compensation"] ?? 0;
// update original array
$hierarchy["_children"][$key] = $updated;
}
return $hierarchy;
}
// call like
foreach ($rows as $index => $value) {
$rows[$index] = static::computeSubTotal($value);
}
You will still need to change how you're passing the $rows variable due to it now containing a "_children" property (as shown in the examples) - either pass each element or add additional logic in that function to handle that.
You need to pass the array as a reference.
https://www.php.net/manual/en/language.references.pass.php
PHP passes the array to the function as a pointer, but when you try to update the array, PHP first makes a full copy of the array and updates the copy instead of the original.
Change your function signature to the following and it should be good.
private function computeSubTotal(&$hierarchy){
P.S. You are calling computeSubTotal statically, but the function is not static itself.
how to check whether 4 exist or not in array at id key position
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
),
)
The array you provided is not a valid multi-dimensional array. You need to add commas after each array in the array. Below i use array_column and in_array without using foreach
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),//add comma
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
),
);
$filtered = array_column($arr, 'id');//return the id column in this array
if(in_array(4, $filtered)){//check if 4 exists
echo '4 exists';
} else {
echo '4 does not exist';
}
?>
Quite simple, loop through the array and check if the value exists:
$value = 4;
$exists = false;
foreach($arr as $innerArr){
if($innerArr['id'] == $value){
$exists = true;
break;
}
}
If $exists is now true, the value exists within the array.
Try this one, and let me know if you face any problem.
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
foreach ($arr as $key => $value) {
if (in_array("4", $value))
{
$sub_index = $value['id'];
echo "Match found at $sub_index";
break;
}
}
Just gotta loop through your array and check existence through in_array() function.
you need something like this
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
$exists_flag = false;
foreach($arr as $inside_arr)
{
if($inside_arr['other_data'] == 4) {
$exists_flag = true;
break;
}
}
print($exists_flag);
Hope this helps!
As it stand, your array is wrong, you need to separate multi array with commas, you need to not that in_array() will not work with multi array, however you may create a recursive function that will check a provided key does exists or not in an array try code below, hope it helps ,
<?php
$arr = array(
array(
'id' => 1,
'other_data' => 'ganesh'
),
array(
'id' => 2,
'other_data' => 'ramesh'
),
array(
'id' => 3,
'other_data' => '4'
)
);
function search_items($search_value, $array)
{
foreach ($array as $item) {
if (($item == $search_value) || (is_array($item) && search_items($search_value, $item))) {
return true;
}
}
return false;
}
echo search_items("4", $arr) ? 'item found' : 'item not found';
?>
$result = array_search(4, array_column($arr, 'id'));
If we split this into steps then it would be the following:
$allColumnsNamedId = array_column($arr, 'id'); // find all columns with key 'id'
$resultBoolean = array_search(4, $allColumnsNamedId); //search the array for value 4
if($resultBoolean) {
echo 'Exists';
} else {
echo 'Does not exist';
}
I have an array like this
$array:
{ name : xyz
version : Array[2]
{
0 : Array[2]
{
id : 1
batch : 1
}
1 : Array[2]
{
id : 2
batch : 2
}
}
}
How can I create an array like this:
$results[] =
name:xyz, version:0, id:1, batch:1
name:xyz, version:1, id:2, batch:2
I want an array where the common fields are repeated.
Do you mean:
$results = array();
$results[] = array('name' => 'xyz', 'version' => 0, 'id' => 1, 'batch' => 1);
$results[] = array('name' => 'xyz', 'version' => 1, 'id' => 1, 'batch' => 1);
Then access the first row by $results[0]['name']
Or second row by $results[1]['name']
EDIT
To convert from $array to $results, I have to assume your $array looks like this.
$array =
array('name' => 'xyz',
'version' => array(
0 => array(
'id' => 1,
'batch' => 1
),
1 => array(
'id' => 2,
'batch' => 2
)
)
);
then
$results = array();
$name = $array['name'];
foreach($array['version'] as $version => $idandbatch)
{
$results[] = array('name' => $name,
'version' => $version,
'id' => $idandbatch['id'],
'batch' => $idandbatch['batch']);
}
You can access the array
foreach($results as $values)
{
echo $values['name'];
echo $values['version'];
echo $values['id'];
echo $values['batch'];
}
Well, I am here again dealing with arrays in php. I need your hand to guide me in the right direction. Suppose the following array:
-fruits
--green
---limon
---mango
--red
---apple
-cars
--ferrari
---enzo
----blue
----black
---318
--lamborg
---spider
---gallardo
----gallado-96
-----blue
-----red
-----gallado-98
The - (hyphen) symbol only illustrates the deep level.
Well, I need to build another array (or whatever), because it should be printed as an HTML select as below:
-fruits
--green
---limon
---mango
--red
---apple
-cars
--ferrari
---enzo
----blue
----black
---318
--lamborg
---spider
---gallardo
----gallado-96
-----blue
-----red
-----gallado-98
Looks that for each level element, it should add a space, or hyphen to determinate that it belongs to a particular parent.
EDIT
The have provide an answer provideng my final code. The html select element will display each level as string (repeating the "-" at the begging of the text instead multi-level elements.
Here's a simple recursive function to build a select dropdown given an array. Unfortunately I'm not able to test it, but let me know if it works. Usage would be as follows:
function generateDropdown($array, $level = 1)
{
if ($level == 1)
{
$menu = '<select>';
}
foreach ($array as $a)
{
if (is_array($a))
{
$menu .= generateDropdown($a, $level+1);
}
else
{
$menu .= '<option>'.str_pad('',$level,'-').$a.'</option>'."\n";
}
}
if ($level == 1)
{
$menu = '</select>';
}
return $menu;
}
OK, I got it with the help of #jmgardhn2.
The data
This is my array:
$temp = array(
array(
'name' => 'fruits',
'sons' => array(
array(
'name' => 'green',
'sons' => array(
array(
'name' => 'mango'
),
array(
'name' => 'banana',
)
)
)
)
),
array(
'name' => 'cars',
'sons' => array(
array(
'name' => 'italy',
'sons' => array(
array(
'name' => 'ferrari',
'sons' => array(
array(
'name' => 'red'
),
array(
'name' => 'black'
),
)
),
array(
'name' => 'fiat',
)
)
),
array(
'name' => 'germany',
'sons' => array(
array(
'name' => 'bmw',
)
)
),
)
)
);
Recursive function
Now, the following function will provide an array with items like [level] => [name]:
function createSelect($tree, $items, $level)
{
foreach ($tree as $key)
{
if (is_array($key))
{
$items = createSelect($key, $items, $level + 1);
}
else
{
$items[] = array('level' => $level, 'text' => $key);
}
}
return $items;
}
Calling the funcion
Now, call the function as below:
$items = createSelect($temp, array(), 0);
Output
If you iterate the final $items array it will look like:
1fruits
2green
3mango
3banana
1cars
2italy
3ferrari
4red
4black
3fiat
2germany
3bmw
I have got a array in this format:
array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
I have to convert this array to json object with this style:
var json = {
id: "1",
name: "loreim ipsum",
data: {},
children: [{
id: "2",
name: "lorem ipsum1",
data: {},
children: [{
id: "3",
name: "lorem ipsum2",
data: {},
children: [{
..............
How can i do this? Thanks.
My solution:
$data = array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
$itemsByReference = array();
// Build array of item references:
foreach($data as $key => &$item) {
$itemsByReference[$item['id']] = &$item;
// Children array:
$itemsByReference[$item['id']]['children'] = array();
// Empty data class (so that json_encode adds "data: {}" )
$itemsByReference[$item['id']]['data'] = new StdClass();
}
// Set items as children of the relevant parent item.
foreach($data as $key => &$item)
if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
$itemsByReference [$item['parent_id']]['children'][] = &$item;
// Remove items that were added to parents elsewhere:
foreach($data as $key => &$item) {
if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
unset($data[$key]);
}
// Encode:
$json = json_encode($data);
Here's code to do what you need. It does not need the items to be in parent-children order in the array, but will finish faster if they are.
Please study the comments to understand what the code is doing and why; and if you still have questions, ask them too!
// Assume your array is $data
$root = new stdClass; // this is your root item
$objectMap = array(); // this holds objects indexed by their id
// Since we need to iterate over the array, but there may be no guarantee
// that an item's parent will be always encountered before the item itself,
// we loop as many times as needed, skipping items whose parent we have not
// yet seen. Hopefully we will see it later and be able to process these
// items in the next iteration.
while (!empty($data)) {
// Remember how many items there are when starting the loop
$count = count($data);
// Do the actual work!
foreach ($data as $key => $row) {
$parentId = $row['parent_id'];
if ($parentId === null) {
// We just met the root element
$current = $root;
}
else if (isset($objectMap[$parentId])) {
// We met an element with a parent that we have already seen
$current = new stdClass;
}
else {
// We met an element with an unknown parent; ignore it for now
continue;
}
// Put the current element on the map so that its children will
// be able to find it when we meet them
$objectMap[$row['id']] = $current;
// Add the item to its parent's children array
$objectMap[$parentId]->children[] = $current;
// Set the item's properties
$current->id = $row['id'];
$current->name = $row['name'];
$current->data = new stdClass; // always empty
$current->children = array();
// We successfully processed this, remove it (see why below!)
unset($data[$key]);
}
// OK, we looped over the array once. If the number of items has
// not been reduced at all, it means that the array contains only
// items whose parents do not exist. Instead of looping forever,
// let's just take what we are given and stop here.
if ($count == count($data)) {
break;
}
// If there are still items in $data, we will now iterate again
// in the hope of being able to process them in the next iteration
}
// All set! If $data is not empty now, it means there were items
// with invalid parent_ids to begin with.
$output = json_encode($root);
My take (I know an answer has been accepted, but I worked on this so I'm gonna post id =P)
// Test data
$data = array(
array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);
// Randomize, because the data may not be in a top-down order
shuffle( $data );
// Parse and inspect the result
$builder = new TreeBuilder( $data );
echo '<pre>', print_r( $builder->getTree() ), '</pre>';
class TreeBuilder
{
protected $leafIndex = array();
protected $tree = array();
protected $stack;
function __construct( $data )
{
$this->stack = $data;
while( count( $this->stack ) )
{
$this->branchify( array_shift( $this->stack ) );
}
}
protected function branchify( &$leaf )
{
// Root-level leaf?
if ( null === $leaf['parent_id'] )
{
$this->addLeaf( $this->tree, $leaf );
}
// Have we found this leaf's parent yet?
else if ( isset( $this->leafIndex[$leaf['parent_id']] ) )
{
$this->addLeaf( $this->leafIndex[$leaf['parent_id']]['children'], $leaf );
} else {
// Nope, put it back on the stack
$this->stack[] = $leaf;
}
}
protected function addLeaf( &$branch, $leaf )
{
// Add the leaf to the branch
$branch[] = array(
'id' => $leaf['id']
, 'name' => $leaf['name']
, 'data' => new stdClass
, 'children' => array()
);
// Store a reference so we can do an O(1) lookup later
$this->leafIndex[$leaf['id']] = &$branch[count($branch)-1];
}
protected function addChild( $branch, $leaf )
{
$this->leafIndex[$leaf['id']] &= $branch['children'][] = $leaf;
}
public function getTree()
{
return $this->tree;
}
}