Simple PHP Recursion Test Failing - php

I am attempting a basic recursion to create multi-dimensional arrays based on the values of an inputed array.
The recursion works by checking for a value we shall call it "recursion" to start the loop and looks for another value we'll call it "stop_recursion" to end.
Basically taking this array
array('One', 'Two', 'recursion', 'Three', 'Four', 'Five', 'stop_recursion', 'Six', 'Seven')
And making this array
array('One', 'Two', array('Three', 'Four', 'Five'), 'Six', 'Seven')
The code I have for it so far is as follows
function testRecrusion($array, $child = false)
{
$return = array();
foreach ($array as $key => $value) {
if ($value == 'recursion') {
unset($array[$key]);
$new = testRecrusion($array, true);
$array = $new['array'];
$return[] = $new['return'];
} else {
if ($value == 'stop_recursion') {
unset($array[$key]);
if ($child) {
return array('return' => $return, 'array' => $array);
}
} else {
unset($array[$key]);
$return[] = $value;
}
}
}
return $return;
}
But the output from that is
Array
(
[0] => One
[1] => Two
[2] => Array
(
[0] => Three
[1] => Four
[2] => Five
)
[3] => Three
[4] => Four
[5] => Five
[6] => Six
[7] => Seven
)
I guess the real question is...will an array values continuously loop through the first values given from the initial call or once the new array is returned and set will it loop through that new array. I know the answer is basically right here saying that yes it will continue the old array value, but shouldn't this work vice-versa?
Any help will be appreciated :)
------------ edit -------------------
I might as well add that while I can perform this action using a much simpler method, this needs to be recursively checked since this will be ported to a string parser that could have a infinite number of child arrays.

When you return inside the recursion, you need to return both the inner array and the index from which to continue searching for elements so that you don't look at the same element twice. Try this instead:
function testRecursionImpl($array, $i)
{
$return = array();
for (; $i < sizeof($array); ++$i) {
if ($array[$i] == 'recursion') {
$new = testRecursionImpl($array, $i + 1);
$return[] = $new[0];
$i = $new[1];
} else if ($array[$i] == 'stop_recursion') {
return array($return, $i);
} else {
$return[] = $array[$i];
}
}
return array($return, $i);
}
function testRecursion($array)
{
$result = testRecursionImpl($array, 0);
return $result[0];
}

The problem you have in the code above is that you correctly detect when you should call this function recursively but once it finishes running and you append the results to output array you just pick next element (which is the first element that recursive call will get) and append it to output array. What you probably want to do is when you detect that you should run your function recursively you should skip all other characters until you find your stop word (stop_recursion). Obviously the problem will become harder if you allow multi-level recursion then you may need to even skip some stopwords because they could be from the different level call.
Still I don't know why you want such a feature. Maybe you would explain what are you trying to achieve. I'm pretty sure there's another, simpler way of doing it.

Rather than helping with your homework, I would suggest you start with getting rid of this line:
foreach ($array as $key => $value) {
You should just pass in your array, and check for being at the end of the array, since it can't really be infinite, to know when you are done.

Let me give it a try
function testRecursion($arr){
$return = array();
$recurlevel = 0;
foreach ($arr as $v) {
if($v == 'stop_recursion'){
$recurlevel--;
}
if($recurlevel == 0){
if(isset($current)){
$return[] = testRecursion($current);
unset($current);
}else{
if($v != 'recursion'){
$return[] = $v;
}
}
}else{
if(!isset($current)){
$current = array();
}
$current[] = $v;
}
if($v == 'recursion'){
$recurlevel++;
}
}
return $return;
}
Alright nicely done. This will help even if the recursion and stop_recursion are nested in another. See example:
code.php:
<pre><?php
function testRecursion($arr){
$return = array();
$recurlevel = 0;
foreach ($arr as $v) {
if($v == 'stop_recursion'){
$recurlevel--;
}
if($recurlevel == 0){
if(isset($current)){
$return[] = testRecursion($current);
unset($current);
}else{
if($v != 'recursion'){
$return[] = $v;
}
}
}else{
if(!isset($current)){
$current = array();
}
$current[] = $v;
}
if($v == 'recursion'){
$recurlevel++;
}
}
return $return;
}
$a = array('One', 'Two', 'recursion', 'Three', 'recursion', 'Four' , 'stop_recursion', 'Five', 'stop_recursion', 'Six', 'Seven');
var_dump(testRecursion($a));
?>
Browser output:
array(5) {
[0]=>
string(3) "One"
[1]=>
string(3) "Two"
[2]=>
array(3) {
[0]=>
string(5) "Three"
[1]=>
array(1) {
[0]=>
string(4) "Four"
}
[2]=>
string(4) "Five"
}
[3]=>
string(3) "Six"
[4]=>
string(5) "Seven"
}

Since your question for the recursive solution has already been answered ...might I offer a stack-based solution?
$x = array('a', 'b', 'recursion', 'cI', 'cII', 'cIII', 'recursion', 'cIV1', 'cIV2', 'cIV2', 'stop_recursion', 'stop_recursion', 'd', 'e');
$result = array();
$stack = array(&$result);
foreach($x as $e) {
if ( 'recursion'===$e ) {
array_unshift($stack, array());
$stack[1][] = &$stack[0];
}
else if ( 'stop_recursion'===$e ) {
array_shift($stack);
}
else {
$stack[0][] = $e;
}
}
var_dump($result);
prints
array(5) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
array(4) {
[0]=>
string(2) "cI"
[1]=>
string(3) "cII"
[2]=>
string(4) "cIII"
[3]=>
array(3) {
[0]=>
string(4) "cIV1"
[1]=>
string(4) "cIV2"
[2]=>
string(4) "cIV2"
}
}
[3]=>
string(3) "d"
[4]=>
string(5) "e"
}

Related

How to get a previous value by reference in a foreach loop

I've reduced my actual code to give a more accurate example. Here's what I have:
$result = [];
$entry = null;
$prev = null;
$arr = [1, 2, 3];
foreach ($arr as $val) {
$entry = [
'val' => $val,
'children' => []
];
if ($prev) {
array_push($prev['children'], $entry);
} else {
array_push($result, $entry);
}
$prev = $entry;
}
var_dump($result);
When this code runs, it outputs this:
array(1) {
[0]=>
array(2) {
["val"]=>
int(1)
["children"]=>
array(0) {
}
}
}
My desired result is this:
array(1) {
[0]=>
array(2) {
["val"]=>
int(1)
["children"]=>
array(1) {
[0]=>
array(2) {
["val"]=>
int(2)
["children"]=>
array(1) {
[0]=>
array(2) {
["val"]=>
int(3)
["children"]=>
array(0) {
}
}
}
}
}
}
}
I'm pretty sure this happens because on this line:
$prev = $entry;
...the value of $prev is set to a copy of $entry. So in the next iteration when I push to $prev['children'], I mutate the copied array, not the original one that was pushed to $result in the first iteration.
I tried using a reference:
$prev = &$entry;
...but I got the same result. Now, $prev is set to the contents of $entry, but when $entry is set to a new array in the next iteration, $prev points to that new array, instead of the previous one, which is useless.
I want $prev to point to the contents of $entry and not change on further mutations to the $entry variable. How would I do that?
The issue comes down to what is being pointed at each time. You need to use references to allow it to add the new item to the correct point in the current structure, but the reference will be different depending on the first time or not.
The first time it needs to point the the $result field as this is where the end result is going, then it will be updated relative to itself, so that the entry keeps track of where it is...
foreach ($arr as $val) {
$entry = [
'val' => $val,
'children' => []
];
if ($prev) {
$prev['children'] = $entry;
$prev = &$prev['children'];
} else {
$result = $entry;
$prev = &$result;
}
}

How to filter an array keeping the highest patch version in each minor version?

I have following array:
array(174) {
[0]=>
string(5) "3.0.3"
[1]=>
string(5) "3.0.2"
[2]=>
string(5) "3.0.1"
[3]=>
string(5) "3.0.0"
[9]=>
string(5) "2.9.5"
[10]=>
string(5) "2.9.4"
[11]=>
string(5) "2.9.3"
[12]=>
string(5) "2.9.2"
[13]=>
string(5) "2.9.1"
[14]=>
string(5) "2.9.0"
[18]=>
string(6) "2.8.11"
[19]=>
string(6) "2.8.10"
[20]=>
string(5) "2.8.9"
}
I need to find the highest 3rd number for unique pair of first two numbers x.x. With this example the expected result must be:
3.0.3, 2.9.5, 2.8.11
This is what I tried:
foreach ($array as $key => $value) {
$test = substr($value, 0, 3);
$a = strtr($value, array('.' => '', ',' => ''));
$b = (int) $a;
$c = substr($b, 0, 2);
$new_array = array($c);
$result = array_unique($new_array);
print_object($result);
}
First you must group the versions by the major version. After that you just need to sort the resulted groups using the version_compare function in a descending order and to return the first element of each group:
$versions = array("3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.9.5", "2.9.4",
"2.9.3", "2.9.2", "2.9.1", "2.9.0", "2.8.11", "2.8.10", "2.8.9"
);
$groupedVersions = array();
foreach ($versions as $version) {
preg_match('/^\d+\.\d+/', $version, $majorVersion);
if (!isset($groupedVersions[$majorVersion[0]])) {
$groupedVersions[$majorVersion[0]] = array($version);
} else {
$groupedVersions[$majorVersion[0]][] = $version;
}
}
$groupedVersions = array_map(function ($versions) {
usort($versions, 'version_compare');
return array_reverse($versions);
}, $groupedVersions);
$latestVersions = array_reduce($groupedVersions, function ($carry, $versions) {
$carry[] = $versions[0];
return $carry;
}, array());
echo '<pre>';
var_dump($latestVersions);
echo '</pre>';
The result would be:
array(3) {
[0]=>
string(5) "3.0.3"
[1]=>
string(5) "2.9.5"
[2]=>
string(6) "2.8.11"
}
When grouping array data in PHP, it is often most efficient to generate temporary keys based on the grouping criteria.
I'll use some version terminology based on the accepted answer from: What every digit means in software version (1.7.1.0, for example)?
In this case, the first two integers (Major & Minor release) are used (and they must remain separated by a delimiting character to avoid data collisions). As you iterate, check if the temporary key exists in the output collection.  If not, add it.  If so, check if the third integer (Maintenance release) is greater than the stored Maintenance release for that Major-Minor release group -- if so, replace the stored Major-Minor release group's value.
This is very concisely performed in my snippet below.  If you need the output to be indexed, call array_values(), otherwise omit that step.
Code: (Demo)
$versions = [
"3.0.1", "3.0.3", "3.0.2", "3.0.0",
    "2.9.4", "2.9.3", "2.9.2", "2.9.5", "2.9.1", "2.9.0",
    "2.8.1", "2.8.11", "2.8.10"
];
   
foreach ($versions as $version) {
    $ints = explode('.', $version, 3);
    $tempKey = "{$ints[0]}.{$ints[1]}";
    if (!isset($result[$tempKey]) || version_compare($version, $result[$tempKey], 'gt')) {
        $result[$tempKey] = $version;
    }
}
   
var_export(array_values($result));
Output:
array (
0 => '3.0.3',
1 => '2.9.5',
2 => '2.8.11',
)
Using version_compare() is an appropriate tool to compare versions, but it can be avoided if you wish to make a simple integer to integer comparison on the Maintenance release value.  This will cost a little more memory because you will also need to store an array of grouped Maintenance release integers to make this comparison.  I am recommending just comparing the full version strings instead.
You can use only explode() and compare last value for each major varsion, then to build associative array with major and minor version:
<?php
$arr = array(0 => "3.0.3", 1 => "3.0.2", 2 => "3.0.1", 3 => "3.0.0", 9 => "2.9.5", 10 => "2.9.4", 11 => "2.9.3", 12 => "2.9.2", 13 => "2.9.1", 14 => "2.9.0", 18 => "2.8.11", 19 => "2.8.10", 20 => "2.8.9");
$versions = array();
$final = array();
foreach ($arr as $version) {
$explode = explode('.', $version); // split all parts
$end = '';
$begin = '';
if (count($explode) > 0) {
$end = array_pop($explode); // removes the last element, and returns it
if (count($explode) > 0) {
$begin = implode('.', $explode); // glue the remaining pieces back together
}
}
if(!empty($versions[$begin])){
if($versions[$begin] < $end){
$versions[$begin] = $end;
}
}else{
$versions[$begin] = $end;
}
}
foreach($versions as $key=>$value){
$final[] = "$key.$value";
}
print_r($final);
?>
Output:
Array
(
[0] => 3.0.3
[1] => 2.9.5
[2] => 2.8.11
)

PHP Str_replace is not working in foreach loop

Here is my code.
I am trying to get inspect link for steam item. I have tried to use preg_replace but no luck either.
$API_link = sprintf("http://steamcommunity.com/id/*steamid*/inventory/json/730/2?trading=1");
$json = file_get_contents($API_link);
$json_output = json_decode($json);
$result = $json_output;
$link = array();
$id = array();
foreach($result->rgDescriptions AS $item){
$empty = array();
$newstring = $item->actions[0]->link;
if($newstring == NULL){
continue;
} else {
$empty['link'] = $newstring;
array_push($link, $empty);
}
}
foreach($result->rgInventory AS $inventory){
$empty = array();
if($inventory->instanceid == 0){
continue;
} else {
$empty['id'] = $inventory->id;
array_push($id, $empty);
}
}
$array = array_merge($id, $link);
foreach($array AS $final){
$assetid = "%assetid%";
echo str_replace($assetid, $final['id'], $final['link']);
}
}
But its not working. Please see if you can help.
As I can see you have array of arrays:
// bracket squares equivalent of array() keyword PHP >=v5.4
// here is
// $link = array(['link'=>'url'],['link'=>'url'])
// $id = array(['id'=>'id'],['id'=>'id'])
// result will be:
// array(['link']=>'url'],['link'=>'url'],['id'=>'id'],['id'=>'id'])
$array = array_merge($id, $link);
foreach($array AS $final){
// here is the first $final
// array('link'=>'url')
$assetid = "%assetid%";
// but here is we try to get
// 'id' and 'link'
echo str_replace($assetid, $final['id'], $final['link']);
}
I think it's some kind of mistake.
Ok, some test script:
<?php
$a = array( array('link'=>'hello1'), array('link'=>'hello2'));
$b = array( array('id'=>'id0'), array('id'=>'id1'));
$c = array_merge($a, $b);
var_dump($c);
result:
array(4) {
[0] =>
array(1) {
'link' =>
string(6) "hello1"
}
[1] =>
array(1) {
'link' =>
string(6) "hello2"
}
[2] =>
array(1) {
'id' =>
string(3) "id0"
}
[3] =>
array(1) {
'id' =>
string(3) "id1"
}
}
array_merge doesn't mix your associative arrays between them nether all nor particular item (I hope I explain it correct)
of course
foreach ($c as $item) {
var_dump($item);
}
will enumerate all the items one by one
array(1) {
'link' =>
string(6) "hello1"
}
array(1) {
'link' =>
string(6) "hello2"
}
array(1) {
'id' =>
string(3) "id0"
}
array(1) {
'id' =>
string(3) "id1"
}
and there is no array that has both of them (link and id) in the item
This script can't associate link and id properly, cause some of links can be skipped by continue, some of id also can be skipped. And it will be just a random list of available information. You can stuck in the next situation:
- $links has first 10 links
- $id has 3,4,5,7,9,11
It's just a list. Even if you have only this pure info (no other details), you can't properly associate it between of them by using shown source.
Here is minimum 1 simple solutions:
don't skip, don't merge, just add an empty array, and your final loop will be like this:
$assetid = "%assetid%";
for ($link as $key=>$final) {
if (count($final) && count($id[$key])) {
echo str_replace($assetid, $id[$key]['id'], $final['link']);
} else {
// some of needed arguments absent
}
}
Description not enough. Maybe try dump some variables?
foreach($array AS $final){
$assetid = "%assetid%";
//Check what is in $final
var_dump($final);
echo str_replace($assetid, $final['id'], $final['link']);
}

PHP array remove all arrays under keyword if found

I have 30 lines of text and explode into arrays separate by "\n". the result as follows:
[1]=> string(121) "In recent years, the rapid growth"
[2]=> string(139) "information technology has strongly enhanced computer systems"
[3]=> string(89) "both in terms of computational and networking capabilities"
[4]=> string(103) "-------------------------"
[5]=> string(103) "these novel distributed computing scenarios"
.
.
[30]=> string(103) "these computer safety applications. end"
in this case, i need to remove all arrays below "-------------" and produce output as follows:
[1]=> string(121) "In recent years, the rapid growth"
[2]=> string(139) "information technology has strongly enhanced computer systems"
[3]=> string(89) "both in terms of computational and networking capabilities"
any idea how to do this? thanks.
solution of the problem by Michael
$i = 0;
$new_arr = array();
while ($array[$i] != "-------------------------") {
// Append lines onto the new array until the delimiter is found
$new_arr[] = $array[$i];
$i++;
}
print_r($new_arr);
Best solution:
Use array_search() and then truncate the array with array_splice():
$key = array_search("-------------------------", $array);
array_splice($array, $key);
Obvious solution:
You can loop over it copying the output to a new array. First example that comes to mind:
$i = 0;
$new_arr = array();
while ($array[$i] != "-------------------------") {
// Append lines onto the new array until the delimiter is found
$new_arr[] = $array[$i];
$i++;
}
print_r($new_arr);
for example
function getMyArray( $array ){
$myArray = array();
foreach( $array as $item ){
if ( $item == '-------------------------' ){ return $myArray; }
$myArray[] = $line;
}
return $myArray'
}
array_search
and unset
you can also use array_slice
You can use array_search to find the key of where its located.
from PHP.net:
<?php
$array = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red');
$key = array_search('green', $array); // $key = 2;
?>
Once you have the key, you could do:
<?php
while($key < count($array) )
{
$array = unset($array[$key]);
$key++;
}
?>
foreach($array as $key => $value)
{
if($value == '-------------')
break;
else
$new_array[$key]=$value;
}

take indexes between repeated values

how can I get everything from a repeated index and the other in an array? see:
$Produtos = Array( 'Guitarra' , // repeats
'Bateria' , // repeats
'Piano' ,
'Baixo' ,
'Guitarra' , // repeats
'Violão' ,
'Caixas de Som' ,
'Bateria' , // repeats
'Sanfona' );
Reviewed are the indices that are repeated, as I do to get what's between them?
I wish to return:`
Array
(
[0] => Array(
[0] => Piano
[1] => Baixo
[1] => Array(
[0] => Violão
[1] => Caixas de Som
[2] => Array(
[0] => Sanfona
) )
It can be solved like this:
<?php
<?php
$Produtos = Array( 'Guitarra' , // repeats
'Bateria' , // repeats
'Piano' ,
'Baixo' ,
'Guitarra' , // repeats
'Violão' ,
'Caixas de Som' ,
'Bateria' , // repeats
'Sanfona' );
$countedProducts = array_count_values($Produtos);
$c = 0;
foreach ($Produtos as $product)
{
if ($countedProducts[$product] > 1)
{
if (count($novosProdutos))
{
$c++;
}
}else{
$novosProdutos[$c][] = $product;
}
}
print '<pre>';
var_dump($novosProdutos);
print '</pre>';
?>
Output:
array(3) {
[0]=>
array(2) {
[0]=>
string(5) "Piano"
[1]=>
string(5) "Baixo"
}
[1]=>
array(2) {
[0]=>
string(7) "Violão"
[1]=>
string(13) "Caixas de Som"
}
[2]=>
array(1) {
[0]=>
string(7) "Sanfona"
}
}
I have understood in the meantime, what you really wanted to have as an result. I changed it now, and it work also with multiple repeations and starts always from zero.
$finalProducts = array();
$currentKey = 0;
$wordCount = array_count_values($Produtos);
foreach($Produtos as $val) {
if($wordCount[$val] > 1) {
$currentKey++;
}
elseif(strlen($currentKey) > 0) {
$finalProducts[$currentKey][] = $val;
}
}
$finalProducts = array_values($finalProducts);
<?php
function array_between_duplicates($ary)
{
// first, tally up all the values
// we need to know how many times each value repeats
$count = array_count_values($ary);
// next, we want only the values that are not repeated.
// This can be done by filtering the array for values
// present 2+ times
$between = array_filter($count, create_function('$a','return $a==1;'));
// now that we have the unique values, swap the keys
// and value locations using array_keys
$swap = array_keys($between);
// and then intersect the new array with the original
// array so we can get back their original key values.
$intersect = array_intersect($ary, $swap);
var_dump($intersect);
// now, in order to get the nested groups we will use
// skipped keys as a sign that the in-between values
// were repeats. So, iterate over the array and break
// out these groups
$result = array(); $group = array();
foreach ($ary as $key => $value)
{
if (!array_key_exists($key, $intersect) && count($group) > 0)
{
$result[] = $group;
$group = array();
}
if (array_search($value,$intersect) !== false)
$group[] = $value;
}
if (count($group) > 0)
$result[] = $group;
// return the result
return $result;
}
var_dump(array_between_duplicates($Produtos));
Results in:
array(3) {
[0]=>
array(2) {
[0]=>
string(5) "Piano"
[1]=>
string(5) "Baixo"
}
[1]=>
array(2) {
[0]=>
string(7) "Violão"
[1]=>
string(13) "Caixas de Som"
}
[2]=>
array(1) {
[0]=>
string(7) "Sanfona"
}
}
DEMO

Categories