Given the following array:
array(..) {
[1111]=>
&array(3) {
["category_id"]=>
int(1111)
["parent_id"]=>
int(0)
["children"]=>
array(2) {
[2222]=>
&array(3) {
["category_id"]=>
int(2222)
["parent_id"]=>
int(1111)
["children"]=>
array(2) {
[5555]=>
&array(1) {
["category_id"]=>
int(5555)
["parent_id"]=>
int(2222)
}
}
[3333]=>
&array(2) {
["category_id"]=>
int(3333)
["parent_id"]=>
int(1111)
}
}
Assuming that the array depth is unknown, How can I determine the path to the parent node?
For example, for category_id = 5555 I should get the following result: 0, 1111,2222. For category_id = 3333 it would be 0,1111
One approach:
function findParent($node, $array, &$parents) {
if (array_key_exists($node, $array)) {
$parents[] = $array[$node]['parent_id'];
}
else {
foreach ($array as $item) {
if (array_key_exists('children', $item)) {
$parents[] = $item['parent_id'];
findParent($node, $item['children'], $parents);
}
}
}
}
findParent(5555, $A, $parents);
Related
I am calling a REST Api to get data using curl in php. It gives me the list of data/contents in the api in Array php format.
I was able to get single element value using $resultArray[0]['nid'][0]['value'];. But my goal is to get elements in all contents in the api.
Say I want to get the following elements in the nested arrays.
$resultArray[0]['nid'][0]['value'];
$resultArray[0]['vid'][0]['value'];
$resultArray[0]['cid'][0]['value'];
And use these values in a loop too.
I am trying to search how I can do it loop, and if anyone can provide sample code, that would be appreciated.
Update:
This is the sample result of var_dump:
array(1) {
[0]=>
array(38) {
["nid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(1)
}
}
["vid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(2)
}
}
["cid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(3)
}
}
["field"]=>
array(1) {
[0]=>
array(4) {
["target_id"]=>
int(4)
}
}
}
[1]=>
array(38) {
["nid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(11)
}
}
["vid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(22)
}
}
["cid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(33)
}
}
["field"]=>
array(1) {
[0]=>
array(4) {
["target_id"]=>
int(44)
}
}
}
[2]=>
array(38) {
["nid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(111)
}
}
["vid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(222)
}
}
["cid"]=>
array(1) {
[0]=>
array(1) {
["value"]=>
int(333)
}
}
["field"]=>
array(1) {
[0]=>
array(4) {
["target_id"]=>
int(444)
}
}
}
}
And I want to use these elements values in a loop.
Say my expected result is.
Test1 = "1", "2", "3"
Test2 = "11", "22", "33"
Test3 = "111", "222", "333"
These equivalent numbers should be comming from the element nid, vid, cid.
I dont just want to assign/echo these values in the result as I have array[100s] in one api call.
Since your array is a multidimensional array, you need to have nested foreach loops:
$test = 1;
foreach ($resultArray as $items) {
// Echo the current test number
echo "Test{$test} = ";
$values = [];
foreach ($items as $item) {
// Get the correct value.
if (array_key_exists('value', $item[0])) {
$values[] = $item[0]['value'];
continue;
}
if (array_key_exists('target_id', $item[0])) {
$values[] = $item[0]['target_id'];
continue;
}
}
echo '"' . implode('", "', $values) . '"' . "\n";
$test++;
}
Demo: https://3v4l.org/fnHlV
I try to iterate through an array by using this function:
public static function getDataForChartAlexaDailyRank($data)
{
$asd = [];
$new = [];
$site = [];
for ($x = 0; $x < sizeof($data->data); $x++) {
foreach ((array)#$data->data->{$x} as $value) {
array_push($site, intval($value->{"Response"}->{"TrafficHistoryResult"}->{"Alexa"}->{"TrafficHistory"}->{"Site"}));
}
foreach ((array)#$data->data->{$x} as $value) {
array_push($new, intval($value->{'Response'}->{'TrafficHistoryResult'}->{'Alexa'}->{'TrafficHistory'}->{'HistoricalData'}->{'Data'}->{'Rank'}));
}
$asd[] = ['name' => $site, 'data' => $new];
$new = [];
}
return json_encode($asd);
}
The array looks like this:
["data"]=>
object(stdClass)#229 (2) {
["mainUrl"]=>
object(stdClass)#235 (1) {
["Response"]=>
object(stdClass)#238 (2) {
["OperationRequest"]=>
object(stdClass)#237 (1) {
["RequestId"]=>
string(36) "1d7824a5-dc09-4efb-9f2a-b35055abc04d"
}
["TrafficHistoryResult"]=>
object(stdClass)#241 (1) {
["Alexa"]=>
object(stdClass)#242 (1) {
["TrafficHistory"]=>
object(stdClass)#243 (4) {
["Range"]=>
string(2) "31"
["Site"]=>
string(25) "https://app.klipfolio.com"
["Start"]=>
string(10) "2016-07-01"
["HistoricalData"]=>
object(stdClass)#244 (1) {
["Data"]=>
array(31) {
[0]=>
object(stdClass)#245 (4) {
["Date"]=>
string(10) "2016-07-01"
["PageViews"]=>
object(stdClass)#246 (2) {
["PerMillion"]=>
string(3) "2.5"
["PerUser"]=>
string(4) "5.50"
}
["Rank"]=>
string(5) "30467"
["Reach"]=>
object(stdClass)#247 (1) {
["PerMillion"]=>
string(2) "30"
}
}
}
}
}
}
}
}
}
["competitorUrl1"]=>
object(stdClass)#338 (1) {
["Response"]=>
object(stdClass)#339 (2) {
["OperationRequest"]=>
object(stdClass)#340 (1) {
["RequestId"]=>
string(36) "ac7ba0b9-c789-5f6a-f1c8-714587b494e9"
}
["TrafficHistoryResult"]=>
object(stdClass)#341 (1) {
["Alexa"]=>
object(stdClass)#342 (1) {
["TrafficHistory"]=>
object(stdClass)#343 (4) {
["Range"]=>
string(2) "31"
["Site"]=>
string(23) "http://onlinesupport.io"
["Start"]=>
string(10) "2016-07-01"
["HistoricalData"]=>
object(stdClass)#344 (0) {
["Data"]=>
array(31) {
[0]=>
object(stdClass)#245 (4) {
["Date"]=>
string(10) "2016-07-01"
["PageViews"]=>
object(stdClass)#246 (2) {
["PerMillion"]=>
string(3) "2.5"
["PerUser"]=>
string(4) "5.50"
}
["Rank"]=>
string(5) "30467"
["Reach"]=>
object(stdClass)#247 (1) {
["PerMillion"]=>
string(2) "30"
}
}
}
}
}
}
}
}
}
}
The array is stored in the variable $data (hence the $data-data to access the array) and I have to iterate through both objects (["mainUrl"] and ["competitorUrl1"] to get the ["Site"] and ["Rank"] and store them into the variable $asd. The function I have written tries to do this. I say tries because I don't know how to properly iterate thorugh objects with different names (["mainUrl"] and ["competitorUrl1"]). I am sorry if I did not explain it well enough but I am a noob in php, so if you have any questions please ask. Thank you very much for your time.
foreach ((array)#$data->data[$x]->{'Response'}->{'TrafficHistoryResult'}->{'Alexa'}->{'TrafficHistory'}->{'HistoricalData'}->{'Data'} as $value) {
Did you forget ->{'mainUrl'} there? So it would be something like follows:
public static function getDataForChartAlexaDailyRank($data)
{
$asd = [];
$new = [];
for ($x = 0; $x < sizeof($data->data); $x++) {
$site = $data->data[$x]->{'mainUrl'}->{'Response'}->{'TrafficHistoryResult'}->{'Alexa'}->{'TrafficHistory'}->{'Site'};
foreach ($data->data[$x]->{'mainUrl'}->{'Response'}->{'TrafficHistoryResult'}->{'Alexa'}->{'TrafficHistory'}->{'HistoricalData'}->{'Data'} as $value) {
array_push($new, intval($value->{'Rank'}));
}
$asd[] = ['name' => $site, 'data' => $new];
$new = [];
}
return json_encode($asd);
}
My function is extremely slowly! Does anyone know what I did wrong or if it is possible to make it faster?
function explodeTree($array, $delimiter = "_", $baseval = false) {
if(!is_array($array)) return false;
$splitRE = "/" . preg_quote($delimiter, "/") . "/";
$returnArr = array();
foreach ($array as $key => $val) {
$parts = preg_split($splitRE, $val['path'], -1, PREG_SPLIT_NO_EMPTY);
$leafPart = array_pop($parts);
$parentArr = &$returnArr;
foreach ($parts as $part) {
if (!isset($parentArr[$part])) {
$parentArr[$part] = array();
} elseif (!is_array($parentArr[$part])) {
if ($baseval) {
$parentArr[$part] = array("__base_val" => $parentArr[$part]);
} else {
$parentArr[$part] = array();
}
}
$parentArr = &$parentArr[$part];
}
if (empty($parentArr[$leafPart])) {
$parentArr[$leafPart] = $val;
} elseif ($baseval && is_array($parentArr[$leafPart])) {
$parentArr[$leafPart]["__base_val"] = $val;
}
}
return $returnArr;
}
Array before:
array(4) {
[0]=>
array(3) {
["path"]=>
string(30) "Volumes/folder1/horse/fred"
["age"]=>
string(2) "12"
["name"]=>
string(4) "fred"
}
[1]=>
array(3) {
["path"]=>
string(28) "Volumes/folder1/cat/john"
["age"]=>
string(2) "10"
["name"]=>
string(4) "john"
}
[2]=>
array(3) {
["path"]=>
string(27) "Volumes/folder2/cat/sam"
["age"]=>
string(2) "11"
["name"]=>
string(3) "sam"
}
[3]=>
array(3) {
["path"]=>
string(32) "Volumes/folder2/cat/cat/john"
["age"]=>
string(2) "16"
["name"]=>
string(4) "john"
}
}
Array after using function:
array(1) {
["Volumes"]=>
array(2) {
["folder1"]=>
array(2) {
["horse"]=>
array(1) {
["fred"]=>
array(3) {
["path"]=>
string(30) "Volumes/folder1/horse/fred"
["age"]=>
string(2) "12"
["name"]=>
string(4) "fred"
}
}
["cat"]=>
array(1) {
["john"]=>
array(3) {
["path"]=>
string(28) "Volumes/folder1/cat/john"
["age"]=>
string(2) "10"
["name"]=>
string(4) "john"
}
}
}
["folder2"]=>
array(1) {
["cat"]=>
array(2) {
["sam"]=>
array(3) {
["path"]=>
string(27) "Volumes/folder2/cat/sam"
["age"]=>
string(2) "11"
["name"]=>
string(3) "sam"
}
["cat"]=>
array(1) {
["john"]=>
array(3) {
["path"]=>
string(32) "Volumes/folder2/cat/cat/john"
["age"]=>
string(2) "16"
["name"]=>
string(4) "john"
}
}
}
}
}
}
It seems your code produces the same output, no matter if $baseval is TRUE or FALSE. The following code produces the same output, runs fast and gracefully ignores the value of $baseval too:
function explodeTree(array $array, $delimiter = "_", $baseval = false)
{
# Build the output here
$returnArr = array();
foreach ($array as $item) {
# Split the path using the delimiter, drop the empty segments
$pieces = array_filter(explode($delimiter, $item['path']));
# Turn the path into a nested array
# Each component of the path is the only key on its level
# Build it from the leaf up to the root
$a = array_reduce(
array_reverse($pieces), # Start from the leaf
function (array $carry, $piece) { # Create parent node...
return array($piece => $carry); # ... use the path piece as key
},
$item # Put the item itself as leaf
);
# Combine the new path (nested arrays) into the existing tree
# array_merge_recursive() takes care of all the levels
$returnArr = array_merge_recursive($returnArr, $a);
}
# That's all
return $returnArr;
}
Adding $baseval
I think the purpose of $baseval is to put the original properties of an item into a new entry under the key __base_val if a subsequent path adds children to a leaf node. For example, if the last entry has 'Volumes/folder2/cat/sam/john' as path then the output of the current code ends with:
["folder2"]=>
array(1) {
["cat"]=>
array(1) {
["sam"]=>
array(4) {
["path"]=>
string(23) "Volumes/folder2/cat/sam"
["age"]=>
string(2) "11"
["name"]=>
string(3) "sam"
["john"]=>
array(3) {
["path"]=>
string(28) "Volumes/folder2/cat/sam/john"
["age"]=>
string(2) "16"
["name"]=>
string(4) "john"
}
}
}
}
but the expected output should be (I think):
["folder2"]=>
array(1) {
["cat"]=>
array(1) {
["sam"]=>
array(2) {
["__base_val"]=>
array(3) {
["path"]=>
string(23) "Volumes/folder2/cat/sam"
["age"]=>
string(2) "11"
["name"]=>
string(3) "sam"
}
["john"]=>
array(3) {
["path"]=>
string(28) "Volumes/folder2/cat/sam/john"
["age"]=>
string(2) "16"
["name"]=>
string(4) "john"
}
}
}
}
The above function cannot be modified to produce this output. array_merge_recursive() doesn't change the structure of the arrays it is provided to merge; it just combines them.
A complete rethink and rewrite of the function is needed:
function explodeTree(array $array, $delimiter = "_", $baseval = false)
{
# Build the output here
$returnArr = array();
foreach ($array as $item) {
# Split the path using the delimiter, drop the empty segments
$pieces = array_filter(explode($delimiter, $item['path']));
# Keep a reference to the current node; start from the root of the tree we build
$pos = &$returnArr;
foreach ($pieces as $piece) {
if (! array_key_exists($piece, $pos)) {
# The path component doesn't exist in the tree; add it
$pos[$piece] = array();
} elseif ($baseval && array_key_exists('path', $pos[$piece])) {
# The component exists, it is a leaf node (has 'path' property) and $baseval is TRUE
# Save the existing node content
$val = $pos[$piece];
# Replace it with a new level; store the old leaf in '__base_val'
$pos[$piece] = array('__base_val' => $val);
}
# Advance to the next level
$pos = &$pos[$piece];
}
# If $baseval is TRUE, make sure we don't mix leaf nodes with inner nodes
if ($baseval && ! empty($pos)) {
# The node already has children; put the item in '__base_val'
$pos['__base_val'] = $item;
} else {
# The node was just added; store $item in it
$pos = array_merge($pos, $item);
}
unset($pos);
}
return $returnArr;
}
You might be running into huge amounts of memory reallocations. If you add a $begin = microtime(true); at the top of the outer foreach, and then printf('%0.6f', microtime(true) - $begin); at the end of the loop, do you see the times staying at a roughly stable level or does it get slower as the execution advances?
It looks like you're building a trie. In which case https://github.com/ikcede/PHP-Trie may be of use.
I have this array:
array(5) {
[0]=>
array(4) {
["productCode"]=>
string(4) "X001"
["productUPC"]=>
string(3) "261"
["productTextSeq"]=>
string(1) "1"
["productTxtVal"]=>
string(5) "Text1"
}
[1]=>
array(4) {
["productCode"]=>
string(4) "X001"
["productUPC"]=>
string(3) "261"
["productTextSeq"]=>
string(1) "2"
["productTxtVal"]=>
string(5) "Text2"
}
[2]=>
array(4) {
["productCode"]=>
string(4) "X001"
["productUPC"]=>
string(3) "261"
["productTextSeq"]=>
string(1) "3"
["productTxtVal"]=>
string(5) "Text3"
}
[3]=>
array(4) {
["productCode"]=>
string(4) "X002"
["productUPC"]=>
string(3) "262"
["productTextSeq"]=>
string(1) "1"
["productTxtVal"]=>
string(5) "Text1"
}
[4]=>
array(4) {
["productCode"]=>
string(4) "X002"
["productUPC"]=>
string(3) "262"
["productTextSeq"]=>
string(1) "2"
["productTxtVal"]=>
string(5) "Text2"
}
}
With the above input, I want the output array to look like this:
array(2) {
[0]=>
array(3) {
["productCode"]=>
string(4) "X001"
["productUPC"]=>
string(3) "261"
["productTxtVal"]=>
string(17) "Text1 Text2 Text3"
}
[1]=>
array(3) {
["productCode"]=>
string(4) "X002"
["productUPC"]=>
string(3) "262"
["productTxtVal"]=>
string(11) "Text1 Text2"
}
}
The resulting array does not need the productTextSeq key, just the combined values of productTextVal, when the productCode is the same. I've searched SO for examples of this but it seems every example I've found are based on multiple input arrays. I know I can brute force this with nested foreach functions but would love a more elegant solution.
I ended up just doing it the brute force method, here is my solution if anyone's interested:
$productData = array();
$sortedData = array();
$comments = '';
$saveKey = '';
$appendComment = false;
$idx = 0;
foreach ($data as $key=>$value) {
foreach ($value as $k=>$v) {
if ($k == 'productCode') {
if ($v == $saveKey) {
$appendComment = true;
} else {
$appendComment = false;
$saveKey = $v;
if ($idx !== 0) { // Don't write to array on first iteration!
$productData['productTxtVal'] = $comments;
$sortedData[] = $productData;
}
}
}
if ($k == 'productTxtVal') {
if ($appendComment == true) {
$comments .= ' ' . trim($v);
} else {
$comments = trim($v);
}
}
}
$productData = $value;
$idx++;
}
Not "elegant" but it works. I also have a check after this logic in case only one productCode is in the original array, as it won't be written to the $sortedData array since the key never changes.
The following code assumes you control the contents of the original data array (due to risk of injection using extract() function) and that no 2 items with the same productCode have the same productTextSeq.
$products = [];
foreach ($data as $item) {
// extract contents of item array into variables
extract($item);
if (!isset($products[$productCode])) {
// create product array with code, upc, text as array
$products[$productCode] = compact('productCode', 'productUPC') + ['productTxtVal' => []];
}
// add text value to array with sequence as index
$products[$productCode]['productTxtVal'][$productTextSeq] = $productTxtVal;
}
$products = array_values( // ignore array keys
array_map(function($product) {
ksort($product['productTxtVal']); // sort text as array by index/ sequence
$product['productTxtVal'] = implode(' ', $product['productTxtVal']); // implode into string
return $product;
}, $products)
);
You can run the code here: https://repl.it/BWQL
I'm having trouble writing a recursive function to traverse this hierarchical structure
object(stdClass)#290 (6) {
["category_id"]=>
int(1)
["parent_id"]=>
int(0)
["name"]=>
string(4) "Root"
["position"]=>
int(0)
["level"]=>
int(0)
["children"]=>
array(2) {
[0]=>
object(stdClass)#571 (7) {
["category_id"]=>
int(2)
["parent_id"]=>
int(1)
["name"]=>
string(18) "Root MySite.com"
["is_active"]=>
int(0)
["position"]=>
int(0)
["level"]=>
int(1)
["children"]=>
array(11) {
[0]=>
object(stdClass)#570 (7) {
["category_id"]=>
int(15)
["parent_id"]=>
int(2)
["name"]=>
string(9) "Widgets"
["is_active"]=>
int(1)
["position"]=>
int(68)
["level"]=>
int(2)
["children"]=>
array(19) {
[0]=>
object(stdClass)#566 (7) {
["category_id"]=>
int(24)
["parent_id"]=>
int(15)
["name"]=>
string(16) "Blue widgets"
["is_active"]=>
int(1)
["position"]=>
int(68)
["level"]=>
int(3)
["children"]=>
array(0) {
}
}
<snip....>
As you can see this nested set can go on forever.. .
What I want to return is something like this
$categories("Root" => array("Root MySite.com" => array("Widgets" => array("Blue Widgets",...))))
[EDIT] : pasting my starting point for my recursive function that will simply "flatten out" an arry or object. I would think i could modify this to make get the data structure I'm looking for but haven't been able to get it quite right.
function array_flatten($array, $return)
{
// `foreach` can also iterate through object properties like this
foreach($array as $key => $value)
{
if(is_object($value))
{
// cast objects as an array
$value = (array) $value;
}
if(is_array($value))
{
$return = array_flatten($value,$return);
}
else
{
if($value)
{
$return[] = $value;
}
}
}
return $return;
}
The question is I can't quite figure out to build the structure I'm looking for recursively, or maybe there is a more elegant php way to do this?
Try this
function run($o) {
$return = array();
foreach ($o->children as $child) {
$return[$child->name] = run($child);
}
return empty($return) ? null : $return;
}
I don't have time to write a working answer, but here's some pseudo code to do it (half PHP, half JS)
This would create a flattened version of the tree by removing the children property of each element in your list.
$flattened = array();
function addElement(&$flattened, $list) {
for ($element in $list) {
$children = $element->children;
delete $element->children;
$flattened[] = $element;
if ($children) {
addElements($flattened, $children)
}
}
}
addElements($flattened, $treeHierarchy);
deep_order object or assoc_array
namespace a_nsp
class object_utils{
static function deep_order($IN, $desc = NULL) { // object or assoc_array
$O = new \stdClass;
$I = (array) $IN;
$keys = array_keys($I);
$desc ? rsort($keys) : asort($keys);
foreach ($keys as $k) {
$v = $I[$k];
//$v = json_decode($I[$k],1) ?: $I[$k]; // force conversion of json_string
if (is_array($v) || is_object($v)) {
$O->$k = self::deep_order($v, $desc);
}
else {
$O->$k=$v;
}
}
return $O; // object
}
}
use like
$ordered_obj = \a_nsp\object_utils::deep_order($orig_obj)