I need to turn each end-point in a multi-dimensional array (of any dimension) into a row containing the all the descendant nodes using PHP. In other words, I want to resolve each complete branch in the array. I am not sure how to state this more clearly, so maybe the best way is to give an example.
If I start with an array like:
$arr = array(
'A'=>array(
'a'=>array(
'i'=>1,
'j'=>2),
'b'=>3
),
'B'=>array(
'a'=>array(
'm'=>4,
'n'=>5),
'b'=>6
)
);
There are 6 end points, namely the numbers 1 to 6, in the array and I would like to generate the 6 rows as:
A,a,i,1
A,a,j,2
A,b,2
B,a,m,3
B,a,n,4
B,b,2
Each row contains full path of descendants to the end-point. As the array can have any number of dimensions, this suggested a recursive PHP function and I tried:
function array2Rows($arr, $str='', $out='') {
if (is_array($arr)) {
foreach ($arr as $att => $arr1) {
$str .= ((strlen($str)? ',': '')) . $att;
$out = array2Rows($arr1, $str, $out);
}
echo '<hr />';
} else {
$str .= ((strlen($str)? ',': '')) . $arr;
$out .= ((strlen($out)? '<br />': '')) . $str;
}
return $out;
}
The function was called as follows:
echo '<p>'.array2Rows($arr, '', '').'</p>';
The output from this function is:
A,a,i,1
A,a,i,j,2
A,a,b,3
A,B,a,m,4
A,B,a,m,n,5
A,B,a,b,6
Which apart from the first value is incorrect because values on some of the nodes are repeated. I have tried a number of variations of the recursive function and this is the closest I can get.
I will welcome any suggestions for how I can get a solution to this problem and apologize if the statement of the problem is not very clear.
You were so close with your function... I took your function and modified is slightly as follows:
function array2Rows($arr, $str='', $csv='') {
$tmp = $str;
if (is_array($arr)) {
foreach ($arr as $att => $arr1) {
$tmp = $str . ((strlen($str)? ', ': '')) . $att;
$csv = array2Rows($arr1, $tmp, $csv);
}
} else {
$tmp .= ((strlen($str)? ', ': '')) . $arr;
$csv .= ((strlen($csv)? '<br />': '')) . $tmp;
}
return $csv;
}
The only difference is the introduction of a temporary variable $tmp to ensure that you don't change the $str value before the recursion function is run each time.
The output from your function becomes:
This is a nice function, I can think of a few applications for it.
The reason that you are repeating the second to last value is that in your loop you you are appending the key before running the function on the next array. Something like this would work better:
function array2Rows($arr, &$out=[], $row = []) {
if (is_array($arr)) {
foreach ($arr as $key => $newArray) {
if (is_array($newArray)) {
$row[] = $key; //If the current value is an array, add its key to the current row
array2Rows($newArray, $out, $row); //process the new value
} else { //The current value is not an array
$out[] = implode(',',array_merge($row,[$key,$newArray])); //Add the current key and value to the row and write to the output
}
}
}
return $out;
}
This is lightly optimized and utilizes a reference to hold the full output. I've also changed this to use and return an array rather than strings. I find both of those changes to make the function more readable.
If you wanted this to return a string formatted similarly to the one that you have in your function, replace the last line with
return implode('<br>', $out);
Alternatively, you could do that when calling, which would be what I would call "best practice" for something like this; e.g.
$result = array2Rows($arr);
echo implode('<br>', $result);
Note, since this uses a reference for the output, this also works:
array2Rows($arr, $result);
echo implode('<br>', $result);
I'm generating an XML document with PHP and the elements have to be in a specific order. Most of them work fine, with the exception of three. The child elements looks like this:
<p>
<co>ABC</co>
<nbr>123456</nbr>
<name>short product description</name>
<desc>long product description</desc>
<kw>searchable keywords</kw>
<i-std>relative/path/to/image</i-std>
<i-lg>relative/path/to/large/image</i-lg>
<i-thmb>relative/path/to/thumbnail</i-thmb>
<mfg>manufacturer</mfg>
<a-pckCont>package contents</a-pckCont>
</p>
The code I'm using works fine, but the three image elements are out of order, which makes the content processor that consumes them choke. What I've tried most lately is this:
$newStd = 0;
foreach ($items as $row => $innerArray) {
$p = $domTree->createElement('p');
$xmlRoot->appendChild($p);
foreach ($innerArray as $innerRow => $value) {
if ($innerRow != 'key') {
if ($value != '') {
echo $innerRow . ' : ' . $value . '<br />';
if ($innerRow == 'i-std') {
$newStd = $domTree->createElement($innerRow, htmlspecialchars($value));
} else {
$p->appendChild($domTree->createElement($innerRow, htmlspecialchars($value)));
}
}
}
if ($newStd != 0) {
$thmb = $p->getElementsByTagName('i-thmb')->item(0);
$p->insertBefore($newStd, $thmb);
}
}
}
My thought was to have it write out all child elements before I have it write the element in using InsertBefore to ensure it appears before the i-thmb element, but it didn't make a difference. No matter what I do, the output I get has them in the order i-thmb, i-std, i-lg. All other elements appear in the proper order, after rearranging some of the variables in the arrays used to build the XML document. I haven't attempted to control the i-lg element yet, since i-std isn't working.
Ultimately, this will be used to combine to XML documents together, but in testing to be sure the XML processor wasn't going to choke, I found the fundamental issue is that the order of elements largely determines if it will work or not (the system I'm working with is undocumented and support is poor, to say the least).
Edit to add: echoing as I do in the inner foreach loop shows them in the correct order but they're out of order in the output file.
I think a couple of changes would make the code more robust (I've added comments to the code for specifics). This mainly involves checking of data types and resetting the replacement field in each loop...
foreach ($items as $row => $innerArray) {
$p = $domTree->createElement('p');
$xmlRoot->appendChild($p);
foreach ($innerArray as $innerRow => $value) {
$newStd = 0; // Make sure this is set each time
if ($innerRow != 'key') {
if ($value != '') {
echo $innerRow . ' : ' . $value . '<br />';
if ($innerRow == 'i-std') {
$newStd = $domTree->createElement($innerRow, htmlspecialchars($value));
} else {
$p->appendChild($domTree->createElement($innerRow, htmlspecialchars($value)));
}
}
}
// Check if a replacement element, checking type of element
if ($newStd instanceof DOMElement) {
$thmb = $p->getElementsByTagName('i-thmb')->item(0);
$p->insertBefore($newStd, $thmb);
}
}
}
I have a following function in php:
function addEntryHere($day, $numberId, $numberOfTexts, $entries) {
foreach($entries as $entry) {
if($entry->$day == $day) {
$entry->addToEntry($numberId, $numberOfTexts);
}
}
$newEntry = new Entry($day);
$newEntry->addToEntry($standId, $numberOfTexts);
array_push($entries, $newEntry);
//print_r($entries);
}
and when I invoke this function in a loop:
while($row = mysqli_fetch_array($result)) {
echo "count table before: " . count($entries);
for($i=23; $i<26; $i++) {
addEntryHere($i, $row[1], $row[2], $entries);
//print_r($entries);
}
echo "count table after: " . count($entries);
}
I see only:
count table before: 0
count table after: 0
My addToEntry method is quite simple:
function addToEntry($numberId, $numberOfTexts) {
switch($numberId) {
case 1: $this->number1+= $numberOfTexts; break;
case 2: $this->number2+= $numberOfTexts; break;
}
}
So why do I get constantly the output 0, even though there is some data in the $result? I've decided to pass the array $entries to the addEntryHere method because I couldn't refer to it in the method, even though I thought it has a global scope...
======= EDIT
after following the watcher's suggestion I modified my code, but then this line:
if($entry->$day == $day) {
throws me the error:
Notice: Undefined property: Entry::$23
and the browser prints such errors many, many times (since it's in the while loop). What might be the problem here?
You are only modifying the local variable inside the function. After every invocation of the function, that local variable disappears and is lost. What you want to do is return the value of your computation back to the calling context so that it can be used later:
array_push($entries, $newEntry);
return $entries;
}
And the call:
while($row = mysqli_fetch_array($result)) {
echo "count table before: " . count($entries);
for($i=23; $i<26; $i++) {
$entries = addEntryHere($i, $row[1], $row[2], $entries);
//print_r($entries);
}
echo "count table after: " . count($entries);
}
If you're trying to work with the global scope, note that in PHP you have to explicitly import the global scope inside of the function:
function addEntryHere($day, $numberId, $numberOfTexts) {
global $entries;
But realize that, in general, working with global variables is an anti-pattern and is advised against.
I want to sort a list from high value to low value. Its output value comes from voting in a poll.
here is the code that creates the list:
function votingScore($item, $itemvoted) {
$hector=count($itemvoted);$totalvotes=0;$in=0;$stepstr='';
$totalvotes=SumArray($itemvoted);
$in=0;
if ($totalvotes==0) { $totalvotes=0.0001; }
while ($in<$hector) {
$stepstr=$stepstr.'<li>'.stripslashes($item[$in]).(int)(($itemvoted[$in]/$totalvotes)*100).'% ';
$stepstr=$stepstr.'</li>';
$in++;
}
return '<ul>'.$stepstr.'</ul>'; }
The votes are saved in a text file that lokes like this:
optionOne:11:optionTwo:5:
I tried doing the following:
arsort($stepstr);
foreach ($stepstr as $key => $val) {
echo "stepstr[" . $key . "] = " . $val . "\n"; }
FIX:
function votingScore($item, $itemvoted) {
$combineSort = array_multisort($itemvoted, SORT_DESC, $item, SORT_DESC); //added this line and got it to work
$hector=count($itemvoted);$totalvotes=0;$in=0;$stepstr='';
If I understand your code correctly you just need to call arsort on $itemvoted such as:
function votingScore($item, $itemvoted) {
$success = arsort($itemvoted); //Success will either be True or False
....
However, depending on what $itemvoted looks like (is it associative or not) you may want a different sorting function as stated by #George_Cummins. See the link:
Imagine this exemplary code:
$the_url = the_variable(); // this function returns "www.site.com/post123" here
$items = generate_array();
foreach($items as $item) {
echo $the_url; // this function returns "www.site.com" here
}
Now, is it possible to store returned value of the_variable() as $the_url? Because it looks like it stores the function itself and runs it for every foreach iteration. So basically I want the foreach loop to return www.site.com/post123 every time.
I know this is basic and simple, although I can't find a solution.
I think you are mistaken. When you do this:
$var_name = function_name();
... $var_name is set to the return of function_name() - it is not a reference to that function.
Consider this example; if it were a reference, you would see a different number in each result:
function the_variable() {
return 'http://www.test.com/' . rand(0, 100);
}
$the_url = the_variable();
$items = range(1, 20);
foreach($items as $item) {
echo $the_url . PHP_EOL;
}
However, as you can see, it returns the first random number and that return is stored in $the_url until such a time as it may be redefined or unset.
As opposed to this example:
foreach($items as $item) {
echo the_variable() . PHP_EOL;
}
Which does output a random number on the end each time.