Walk array recursively and print the path of the walk - php

Can someone help me with some code or instructions on how to walk recursively an array and when reaching the last element print the full path to it? A simple echo will work because I will adapt the code to some other function I'm developing.
The function doesn't need to figure the array dimension because this param will be passed:
Example:
$depth = 8;
$array[1][3][5][6][9][5][8][9];
When function reachs the 8th element it print all the path to it:
//print path
'1 -> 3 -> 5 -> 6 -> 9 -> 5 -> 8 -> 9'
As I said, only printing in this format will work cause I will implement the code into some other function.
array keys can have the same value. Obviously not the same value in the same sequence for the entire arary.
Updated:
Walk recursively function:
$someArray[1][2][3] = 'end';
$someArray[1][2][6] = 'end';
$someArray[1][3][6] = 'end';
$someArray[4][3][7] = 'end';
function listArrayRecursive(&$array_name, $ident = 0){
if (is_array($array_name)){
foreach ($array_name as $k => &$v){
if (is_array($v)){
for ($i=0; $i < $ident * 10; $i++){ echo " "; }
echo $k . " : " . "<br>";
listArrayRecursive($v, $ident + 1);
}else{
for ($i=0; $i < $ident * 10; $i++){ echo " "; }
echo $k . " : " . $v . "<br>";
}
}
}else{
echo "Variable = " . $array_name;
}
}
listArrayRecursive($someArray);
Will print:
1 :
2 :
3 : end
6 : end
3 :
6 : end
4 :
3 :
7 : end
Now, how can I also print the path of the array everytime it reaches the end? For example:
1 :
2 :
3 : end : path -> 1,2,3
6 : end : path -> 1,2,6
3 :
6 : end : path -> 1,3,6
4 :
3 :
7 : end : path -> 4,3,7
EDITED CODE ADDING A THIRD PARAM TO RECORD THE PATH:
$someArray[1][2][3] = 'end';
$someArray[1][2][6] = 'end';
$someArray[1][3][6] = 'end';
$someArray[4][3][7] = 'end';
$someArray[3][2] = 'end';
function listArrayRecursive(&$array_name, $ident = 0, $path = null){
foreach ($array_name as $k => &$v){
if (is_array($v)){
for ($i=0; $i < $ident * 10; $i++){ echo " "; }
echo $k . " : " . "<br>";
$path .= $k . ', ';
listArrayRecursive($v, $ident + 1, $path);
}else{
for ($i=0; $i < $ident * 10; $i++){ echo " "; }
echo $k . " : " . $v . ' - path -> ' . $path . "<br>";
}
}
}
listArrayRecursive($someArray);
Will print:
1 :
2 :
3 : end - path -> 1, 2,
6 : end - path -> 1, 2,
3 :
6 : end - path -> 1, 2, 3,
4 :
3 :
7 : end - path -> 1, 4, 3,
3 :
2 : end - path -> 1, 4, 3,

You could employ a RecursiveIteratorIterator (docs) to take the hard work out of recursing through the arrays.
function listArrayRecursive($someArray) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($someArray), RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $k => $v) {
$indent = str_repeat(' ', 10 * $iterator->getDepth());
// Not at end: show key only
if ($iterator->hasChildren()) {
echo "$indent$k :<br>";
// At end: show key, value and path
} else {
for ($p = array(), $i = 0, $z = $iterator->getDepth(); $i <= $z; $i++) {
$p[] = $iterator->getSubIterator($i)->key();
}
$path = implode(',', $p);
echo "$indent$k : $v : path -> $path<br>";
}
}
}

This example is to give you idea, not to solve the actual task.
function recursiveSearch($array,$search){
foreach($array as $key=>$val){
if($val==$search)return $key;
$x=recursiveSearch($array[$key],$search);
if($x)return $key.' -> '.$x;
}
}
echo recursiveSearch($array,'search');
If no match is found, null is returned.

$a= array(1,2,3,4,5,6);
$val = end($a);
print_array($a,$val);
function print_array(&$arr, $val)
{
if ($val === false)
return;
$curr = prev($arr);
print_array($arr,$curr);
echo $val;
}

I just wrote a function that makes recursive looping a bit easier:
Similar to array_walk_recursive but with some extra functionality
public static function walk($array, $callback, $custom = null, $recursive = false, $info = [])
{
$r = $recursive;
if (gettype($r) === 'integer') {
$r--;
}
$info['depth'] = empty($info)?1:$info['depth'] + 1;
$info['count'] = count($array);
$info['i'] = 1;
foreach($array as $k => $v) {
if (is_array($v) && $r > 0) {
$array[$k] = static::walk($v, $callback, $custom, $r, $info);
} else {
$array[$k] = $callback($v, $k, $custom, $info);
}
$info['i'] ++;
}
return $array;
}
public static function walkable($v, $k, $custom, $info)
{
if (is_string($v)) {
return $v." [ custom: {$custom['key']} ] [ level: ".$info['depth'].' | No '.$info['i'].' of '.$info['count']." ]";
}
return $v;
}
Called like so:
$result = Namespace\ClassName::walk($array, ['Namespace\ClassName', 'walkable'], ['key'=>'value'], true);
Setting recursive to false will only evaluate the first level.
Setting recursive to true will cause it to traverse the entire array.
Setting recursive to an integer will cause it to only traverse to that depth.
Walkable functions can be referenced or passed to callback as anonymous function.
(expects: value, key, custom, info)
The returned value replace the current value.
Custom data can be passed and some additional info is provided for you.
You can expand on the walk function if you need additional info.

I had similar problem. Here is a Depth-First Search-ish solution(no path depth included, it reaches until the very end of the array). Comment the 'if' statement if u don't want to include the value:
$output = array();
retrievePath($someArray, $output);
function retrievePath($someArray, array &$pathKeeper)
{
if(!is_array($someArray)){ // $someArray == "end"
$element = array_pop($pathKeeper) ?? '';// if the array is empty pop returns null, we don't want that
array_push($pathKeeper, $element . '->'. $someArray);
} else{
end($someArray);//we want to get the last element from the array so we move the internal pointer to it's end
$endElKey = key($someArray);//take the key where the pointer is
reset($someArray);
foreach($someArray as $key=>$value){
$element = array_pop($pathKeeper);
array_push($pathKeeper, $element === null ? $key : $element . '->' . $key);// we don't want '->' at the beginning
retrievePath($value, $pathKeeper);
if($key != $endElKey) //we check whether this is not the last loop
array_push($pathKeeper, $element);
}
}
}

<?php
function printListRecursive($a, $var='', $i = 0) {
if (!is_array($a)) {
$var .= $a;
return $var;
}
$string = "";
foreach ($a as $k => $value) {
$string .= str_repeat(" ", $i) .' - '. $k . ':';
if (!is_array($value)) {
$string .= $value . '<br />';
} else {
$string .= '<br />';
$string .= printListRecursive($value, $var, $i + 1);
}
}
return $string;
}
$test_array = [
'America' => [
'Argentina' => 'Buenos Aires',
'Peru' => 'Lima'
],
'Europe' => [
'Ireland' => 'Dublin',
'France' => 'Paris',
'Italy' => 'Rome'
]
];
$result = printListRecursive($test_array);
echo $result;
?>
Check code here

I came up with the following function based on #salathe's one. It returns an array where each element is an array containing the leaf at index 0 and the array of the path keys at index 1:
function loosenMultiDimensionalArrayPathForEachVal($array) {
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::SELF_FIRST);
$iterator->rewind();
$res = [];
foreach ($iterator as $v) {
$depth = $iterator->getDepth();
for ($path = array(), $i = 0, $z = $depth; $i <= $z; $i++) {
$path[] = $iterator->getSubIterator($i)->key();
}
$leaf = $array;
foreach ($path as $pathKey) {
$leaf = $leaf[$pathKey];
}
if (!is_array($leaf)) {
$res[] = [
$v,
$path
];
}
}
return $res;
}
The main reason I implemented this one is that $iterator->hasChildren() returns true if the current iterated leaf is an object. Therefore, I wouldn't be able to get the path of it that way.

I found this solution, which also keeps into account if elements of the structure are arrays:
$file_contents=file_get_contents("data.json");
$json_dump=json_decode($file_contents);
printPath($json_dump, '', "" ,"");
function printPath($the_array, $path, $prevType) {
// Parse all elements of a structure
// and print full PHP path to each one.
foreach($the_array as $key => $value) {
if(is_array($value)) {
// Array element cannot be directly printed: process its items as objects:
printPath($value, $path . $key , "array");
} else {
if (!is_object($value)) { // If the element is not an object, it can be printed (it's a leaf)
if(is_string($value)) {
$finalValue = '"' . $value . '"';
} else {
$finalValue = $value;
}
if($prevType == "array") {
// If array element, add index in square brackets
echo($path . "[" . $key . "] = " . $finalValue . "<br>");
} else {
echo($path . $key . " = " . $finalValue . "<br>");
}
} else { // else store partial path and iterate:
if($prevType == "array") {
// Path of array element must contain element index:
printPath($value, $path . "[" . $key . "]->" , "dummy");
} else {
printPath($value, $path . $key . "->", "dummy");
}
}
}
}
}
Example output:
status->connections->DSS-25->band = "X"
status->connections->DSS-25->endAt = "2019-11-20T20:40:00.000Z"
status->connections->DSS-25->startAt = "2019-11-20T12:40:00.000Z"
geometry[0]->obs[0]->name = "UDSC64"
geometry[0]->obs[0]->hayabusa2->azm = 90.34
geometry[0]->obs[0]->hayabusa2->alt = -20.51
In case anybody is interested, here it is the port to Javascript:
function iterate(obj, stack, prevType) {
for (var property in obj) {
if ( Array.isArray(obj[property]) ) {
//console.log(property , "(L=" + obj[property].length + ") is an array with parent ", prevType, stack);
iterate(obj[property], stack + property , "array");
} else {
if ((typeof obj[property] != "string") && (typeof obj[property] != "number")) {
if(prevType == "array") {
//console.log(stack + "[" + property + "] is an object, item of " , prevType, stack);
iterate(obj[property], stack + "[" +property + "]." , "object");
} else {
//console.log(stack + property , "is " , typeof obj[property] , " with parent ", prevType, stack );
iterate(obj[property], stack + property + ".", "object");
}
} else {
if(prevType == "array") {
console.log(stack + "[" + property + "] = "+ obj[property]);
} else {
console.log(stack + property , " = " , obj[property] );
}
}
}
}
}
iterate(object, '', "File")

You can add a third parameter which holds the actual path as String. At the end you can output it then.

Related

How do you get the next key in an associative array php [duplicate]

I have a foreach loop and I want to see if there is a next element in the loop so I can compare the current element with the next. How can I do this? I've read about the current and next functions but I can't figure out how to use them.
A unique approach would be to reverse the array and then loop. This will work for non-numerically indexed arrays as well:
$items = array(
'one' => 'two',
'two' => 'two',
'three' => 'three'
);
$backwards = array_reverse($items);
$last_item = NULL;
foreach ($backwards as $current_item) {
if ($last_item === $current_item) {
// they match
}
$last_item = $current_item;
}
If you are still interested in using the current and next functions, you could do this:
$items = array('two', 'two', 'three');
$length = count($items);
for($i = 0; $i < $length - 1; ++$i) {
if (current($items) === next($items)) {
// they match
}
}
#2 is probably the best solution. Note, $i < $length - 1; will stop the loop after comparing the last two items in the array. I put this in the loop to be explicit with the example. You should probably just calculate $length = count($items) - 1;
You could probably use while loop instead of foreach:
while ($current = current($array) )
{
$next = next($array);
if (false !== $next && $next == $current)
{
//do something with $current
}
}
If the indexes are continuous:
foreach ($arr as $key => $val) {
if (isset($arr[$key+1])) {
echo $arr[$key+1]; // next element
} else {
// end of array reached
}
}
As php.net/foreach points out:
Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during or after the foreach without resetting it.
In other words - it's not a very good idea to do what you're asking to do. Perhaps it would be a good idea to talk with someone about why you're trying to do this, see if there's a better solution? Feel free to ask us in ##PHP on irc.freenode.net if you don't have any other resources available.
You could get the keys/values and index
<?php
$a = array(
'key1'=>'value1',
'key2'=>'value2',
'key3'=>'value3',
'key4'=>'value4',
'key5'=>'value5'
);
$keys = array_keys($a);
foreach(array_keys($keys) as $index ){
$current_key = current($keys); // or $current_key = $keys[$index];
$current_value = $a[$current_key]; // or $current_value = $a[$keys[$index]];
$next_key = next($keys);
$next_value = $a[$next_key] ?? null; // for php version >= 7.0
echo "{$index}: current = ({$current_key} => {$current_value}); next = ({$next_key} => {$next_value})\n";
}
result:
0: current = (key1 => value1); next = (key2 => value2)
1: current = (key2 => value2); next = (key3 => value3)
2: current = (key3 => value3); next = (key4 => value4)
3: current = (key4 => value4); next = (key5 => value5)
4: current = (key5 => value5); next = ( => )
if its numerically indexed:
foreach ($foo as $key=>$var){
if($var==$foo[$key+1]){
echo 'current and next var are the same';
}
}
The general solution could be a caching iterator. A properly implemented caching iterator works with any Iterator, and saves memory. PHP SPL has a CachingIterator, but it is very odd, and has very limited functionality. However, you can write your own lookahead iterator like this:
<?php
class NeighborIterator implements Iterator
{
protected $oInnerIterator;
protected $hasPrevious = false;
protected $previous = null;
protected $previousKey = null;
protected $hasCurrent = false;
protected $current = null;
protected $currentKey = null;
protected $hasNext = false;
protected $next = null;
protected $nextKey = null;
public function __construct(Iterator $oInnerIterator)
{
$this->oInnerIterator = $oInnerIterator;
}
public function current()
{
return $this->current;
}
public function key()
{
return $this->currentKey;
}
public function next()
{
if ($this->hasCurrent) {
$this->hasPrevious = true;
$this->previous = $this->current;
$this->previousKey = $this->currentKey;
$this->hasCurrent = $this->hasNext;
$this->current = $this->next;
$this->currentKey = $this->nextKey;
if ($this->hasNext) {
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
}
}
}
public function rewind()
{
$this->hasPrevious = false;
$this->previous = null;
$this->previousKey = null;
$this->oInnerIterator->rewind();
$this->hasCurrent = $this->oInnerIterator->valid();
if ($this->hasCurrent) {
$this->current = $this->oInnerIterator->current();
$this->currentKey = $this->oInnerIterator->key();
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
} else {
$this->current = null;
$this->currentKey = null;
$this->hasNext = false;
$this->next = null;
$this->nextKey = null;
}
}
public function valid()
{
return $this->hasCurrent;
}
public function hasNext()
{
return $this->hasNext;
}
public function getNext()
{
return $this->next;
}
public function getNextKey()
{
return $this->nextKey;
}
public function hasPrevious()
{
return $this->hasPrevious;
}
public function getPrevious()
{
return $this->previous;
}
public function getPreviousKey()
{
return $this->previousKey;
}
}
header("Content-type: text/plain; charset=utf-8");
$arr = [
"a" => "alma",
"b" => "banan",
"c" => "cseresznye",
"d" => "dio",
"e" => "eper",
];
$oNeighborIterator = new NeighborIterator(new ArrayIterator($arr));
foreach ($oNeighborIterator as $key => $value) {
// you can get previous and next values:
if (!$oNeighborIterator->hasPrevious()) {
echo "{FIRST}\n";
}
echo $oNeighborIterator->getPreviousKey() . " => " . $oNeighborIterator->getPrevious() . " -----> ";
echo "[ " . $key . " => " . $value . " ] -----> ";
echo $oNeighborIterator->getNextKey() . " => " . $oNeighborIterator->getNext() . "\n";
if (!$oNeighborIterator->hasNext()) {
echo "{LAST}\n";
}
}
You could get the keys of the array before the foreach, then use a counter to check the next element, something like:
//$arr is the array you wish to cycle through
$keys = array_keys($arr);
$num_keys = count($keys);
$i = 1;
foreach ($arr as $a)
{
if ($i < $num_keys && $arr[$keys[$i]] == $a)
{
// we have a match
}
$i++;
}
This will work for both simple arrays, such as array(1,2,3), and keyed arrays such as array('first'=>1, 'second'=>2, 'thrid'=>3).
A foreach loop in php will iterate over a copy of the original array, making next() and prev() functions useless. If you have an associative array and need to fetch the next item, you could iterate over the array keys instead:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = $items[array_keys($items)[$index + 1]];
}
Since the resulting array of keys has a continuous index itself, you can use that instead to access the original array.
Be aware that $next will be null for the last iteration, since there is no next item after the last. Accessing non existent array keys will throw a php notice. To avoid that, either:
Check for the last iteration before assigning values to $next
Check if the key with index + 1 exists with array_key_exists()
Using method 2 the complete foreach could look like this:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = null;
if (array_key_exists($index + 1, array_keys($items))) {
$next = $items[array_keys($items)[$index + 1]];
}
}
$next_data = $data;
$prev_key = null;
$prev_value = null;
foreach($data as $key => $value)
{
array_shift($next_data);
$next_key = key($next_data);
$next_value = $next_data[$next_key] ?? null;
// Do something here...
$prev_key = $key;
$prev_value = $value;
}
or if the array is associative then you could use current() similar to Andrei Krasutski's solution and key()
$values = [];
array_push($values, ["XYZ"=>100]);
array_push($values, ["ABC"=>10]);
array_push($values, ["XYZ"=>130]);
array_push($values, ["DEF"=>4]);
array_push($values, ["XYZ"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo key($currentValue) . "=" . current($currentValue) . " followed by " . key($nextValue) . "/" . current($nextValue) . "<br>\n";
} else {
echo key($currentValue) . "=" . current($currentValue);
}
}
See https://onlinephp.io/c/dc58d for a running example.
or if the array is using named pairs:
$values = [];
array_push($values, ["type"=>"XYZ", "value"=>100]);
array_push($values, ["type"=>"ABC", "value"=>10]);
array_push($values, ["type"=>"XYZ", "value"=>130]);
array_push($values, ["type"=>"DEF", "value"=>"Lorem"]);
array_push($values, ["type"=>"XYZ", "value"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo $currentValue['type'] . "=" . $currentValue['value']
. " followed by " . $nextValue['type'] . "/" . $nextValue['value'] . "<br>\n";
} else {
echo $currentValue['type'] . "=" . $currentValue['value'];
}
}

how to get each user list in array

public function getChildren($parent, $level=0,$getlevel=array()) {
$criteria = new CDbCriteria;
$criteria->condition='referred_by_user_id=:id';
$criteria->params=array(':id'=>$parent);
$count = array(0=>0);
$model = $this->findAll($criteria);
// $levelcount = array();
$user=User::model()->findbypk(Yii::app()->user->id);
if($user->level!=$level)
{
foreach ($model as $key) {
$count[0]++;
$index=1;
// echo str_repeat(' — ', $level) . $key->name . "<br />";
$children= $this->getChildren($key->id, $level+1);
// pr($children);die;
foreach ($children as $child)
{
// pr($child);
if ($child==0)
continue;
if (isset($count[$index]))
$count[$index] += $child;
else
$count[$index] = $child;
$index++;
}
}
}
return $count;
}
I can output only the number of users. How do I get the user id for each level in an array?
I think the problem is you must make $index variable outside of for
I made something similiar a while ago:
public static function getChildren($idParent) {
$elem = [];
$childrens = Arbol::model()->findAll("estado = 1 and id_parent = " . $idParent . ' ORDER BY level');
foreach ($childrens as $key => $child) {
$persona = Persona::model()->findbyPk($child->id_child);
if ((int) $persona->id_superior === $idParent) {
$nombre = explode(" ", $persona->nombres);
$elem[$key] = [
"id" => $persona->id_persona,
"image" => Yii::app()->baseUrl . '/images/user-icon' . ($persona->pago_inscripcion ? '-activo.png' : '.png'),
"text" => [
'title' => $nombre[0] . ' ' . substr($persona->apellido_paterno, 0, 1) . '.'
]
];
} else {
$keySUperior = array_search($persona->id_superior, array_column($elem, "id"));
if (is_int($keySUperior)) {
$elem[$keySUperior]["children"] = self::getChildren((int) $persona->id_superior);
}
}
}
return $elem;
}
I hope i give you any help

php evaluating multidimensional array from inner array to outer araray

How do I solve the following problem using PHP RecursiveIteratorIterator?
$arr = array(
"sum"=>array(2,4,6, "multiply" => array(1,3,5) ),
"multiply"=>array(3,3,3, "sum" => array(2,4,6) ),
);
I am expecting the following answers
(2 + 4 + 6 + ( 1 * 3 * 5) ) = 27;
(3 * 3 * 3 * (2 + 4 + 6)) = 324;
Partial code so far..
$calc = new ArrayObject($arr);
$MRI = new RecursiveIteratorIterator(new MyRecursiveIterator($calc), 1);
foreach ($MRI as $key=>$value) {
echo " Current Depth: ". $MRI->getDepth() . "\n";
echo $key . " : " . $value . "\n";
$currentDepth = $MRI->getDepth();
for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--)
{ echo "sub Depth: ". $subDepth . "\n";
$subIterator = $MRI->getSubIterator($subDepth);
// var_dump($subIterator); } }
You can do it like this, do the calculation from the innermost of the array. Check the demo.
<?php
function f(&$array)
{
foreach($array as $k => &$v)
{
if(is_array($v))
{
if(count($v) == count($v, 1))
{
unset($array[$k]);
if($k == 'sum')
{
$v = array_sum($v);
$array[] = $v;
}elseif($k == 'multiply'){
$v = array_product($v);
$array[] = $v;
}else{
foreach($v as $vv)
$array[] = $vv;
}
}else
f($v);
}
}
}
while(count($array) != count($array, 1))
{
f($array);
}
print_r($array);
Note:
traverse array from outer to inner
traverse array from inner to outer
Simple and easy solution :-
$arr = array("sum"=>array(2,4,6, "multiply" => array(1,3,5) ),"multiply"=>array(3,3,3, "sum" => array(2,4,6)));
foreach($arr as $key => $newarr){
if($key =="sum"){
$sum = array_sum($newarr);
$product = array_product($newarr['multiply']);
$finalarray[$key] = $sum+$product;
}elseif($key =="multiply") {
$product = array_product($newarr);
$sum = array_sum($newarr['sum']);
$finalarray[$key] = $sum*$product;
}
}
echo "<pre>"; print_r($finalarray);
Hope it helps!

Create Graphs from Array values : x-y , y-z , z-x

I have a main array containing the following arrays
(at least 50 different types) :
I'm currently programming in php but i can probably adapt the same functions in java , c# etc...
array('type'=>'x-y','value'=>'12');
array('type'=>'y-z','value'=>'6');
array('type'=>'y-x','value'=>'56');
array('type'=>'z-x','value'=>'19');
array('type'=>'z-y','value'=>'18');
array('type'=>'x-z','value'=>'67');
........
I want to create and traverse a graph for each key and run operations on them e.g :
x-y-z-x : 12 * 6 * 19
x-y-x : 12 * 56
x-z-x : 67 * 19
y-z-y : 6 * 18
.......
I have been trying to run each independently using foreach loops but it won't work for the size of dataset that i have .
Any help would be greatly appreciated. Thanks
The code is in php as required, you can test it as well..
function initData($final, $paths, $values) {
for($i=0; $i<sizeof($final);$i++) {
$path = explode('-', $final[$i]["type"]);
$value = $final[$i]["value"];
array_push($paths, $path);
array_push($values, $value);
}
return [$paths, $values];
}
function getFinalValue($finalPath, $paths, $values) {
$path = explode('-', $finalPath);
$finalValue = 1;
for($i=0;$i<sizeof($path)-1;$i++) {
$stop = $path[$i+1];
$start = $path[$i];
$smallPath = array($start, $stop);
$finalValue *= getValue($smallPath, $paths, $values);
}
return $finalValue;
}
function getValue($smallPath, $paths, $values) {
$value = 0;
for($i=0; $i<sizeof($paths);$i++) {
if($smallPath[0] == $paths[$i][0])
{
if($smallPath[1] == $paths[$i][1]) {
print_r($smallPath);
echo ' ';
$value = $values[$i];
print_r($value);
echo '<br>';
}
}
}
return $value;
}
//Test
function test() {
$a = array('type'=>'x-y','value'=>'12');
$b = array('type'=>'y-z','value'=>'6');
$c = array('type'=>'y-x','value'=>'56');
$d = array('type'=>'z-x','value'=>'19');
$e = array('type'=>'z-y','value'=>'18');
$f = array('type'=>'x-z','value'=>'67');
$paths = [];
$values = [];
$testString = 'x-y-z';
$final = array($a,$b,$c,$d,$e,$f);
$data = initData($final, $paths, $values);
$paths = $data[0];
$values = $data[1];
for ($i=0;$i<sizeof($paths);$i++) {
print_r($paths[$i]);
print_r($values[$i]);
echo '<br>';
}
echo '<br>';
echo '<br>';
$finalValue = getFinalValue($testString, $paths, $values);
echo $testString . '=' . $finalValue;
}
test();
?>

Get next element in foreach loop

I have a foreach loop and I want to see if there is a next element in the loop so I can compare the current element with the next. How can I do this? I've read about the current and next functions but I can't figure out how to use them.
A unique approach would be to reverse the array and then loop. This will work for non-numerically indexed arrays as well:
$items = array(
'one' => 'two',
'two' => 'two',
'three' => 'three'
);
$backwards = array_reverse($items);
$last_item = NULL;
foreach ($backwards as $current_item) {
if ($last_item === $current_item) {
// they match
}
$last_item = $current_item;
}
If you are still interested in using the current and next functions, you could do this:
$items = array('two', 'two', 'three');
$length = count($items);
for($i = 0; $i < $length - 1; ++$i) {
if (current($items) === next($items)) {
// they match
}
}
#2 is probably the best solution. Note, $i < $length - 1; will stop the loop after comparing the last two items in the array. I put this in the loop to be explicit with the example. You should probably just calculate $length = count($items) - 1;
You could probably use while loop instead of foreach:
while ($current = current($array) )
{
$next = next($array);
if (false !== $next && $next == $current)
{
//do something with $current
}
}
If the indexes are continuous:
foreach ($arr as $key => $val) {
if (isset($arr[$key+1])) {
echo $arr[$key+1]; // next element
} else {
// end of array reached
}
}
As php.net/foreach points out:
Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during or after the foreach without resetting it.
In other words - it's not a very good idea to do what you're asking to do. Perhaps it would be a good idea to talk with someone about why you're trying to do this, see if there's a better solution? Feel free to ask us in ##PHP on irc.freenode.net if you don't have any other resources available.
You could get the keys/values and index
<?php
$a = array(
'key1'=>'value1',
'key2'=>'value2',
'key3'=>'value3',
'key4'=>'value4',
'key5'=>'value5'
);
$keys = array_keys($a);
foreach(array_keys($keys) as $index ){
$current_key = current($keys); // or $current_key = $keys[$index];
$current_value = $a[$current_key]; // or $current_value = $a[$keys[$index]];
$next_key = next($keys);
$next_value = $a[$next_key] ?? null; // for php version >= 7.0
echo "{$index}: current = ({$current_key} => {$current_value}); next = ({$next_key} => {$next_value})\n";
}
result:
0: current = (key1 => value1); next = (key2 => value2)
1: current = (key2 => value2); next = (key3 => value3)
2: current = (key3 => value3); next = (key4 => value4)
3: current = (key4 => value4); next = (key5 => value5)
4: current = (key5 => value5); next = ( => )
if its numerically indexed:
foreach ($foo as $key=>$var){
if($var==$foo[$key+1]){
echo 'current and next var are the same';
}
}
The general solution could be a caching iterator. A properly implemented caching iterator works with any Iterator, and saves memory. PHP SPL has a CachingIterator, but it is very odd, and has very limited functionality. However, you can write your own lookahead iterator like this:
<?php
class NeighborIterator implements Iterator
{
protected $oInnerIterator;
protected $hasPrevious = false;
protected $previous = null;
protected $previousKey = null;
protected $hasCurrent = false;
protected $current = null;
protected $currentKey = null;
protected $hasNext = false;
protected $next = null;
protected $nextKey = null;
public function __construct(Iterator $oInnerIterator)
{
$this->oInnerIterator = $oInnerIterator;
}
public function current()
{
return $this->current;
}
public function key()
{
return $this->currentKey;
}
public function next()
{
if ($this->hasCurrent) {
$this->hasPrevious = true;
$this->previous = $this->current;
$this->previousKey = $this->currentKey;
$this->hasCurrent = $this->hasNext;
$this->current = $this->next;
$this->currentKey = $this->nextKey;
if ($this->hasNext) {
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
}
}
}
public function rewind()
{
$this->hasPrevious = false;
$this->previous = null;
$this->previousKey = null;
$this->oInnerIterator->rewind();
$this->hasCurrent = $this->oInnerIterator->valid();
if ($this->hasCurrent) {
$this->current = $this->oInnerIterator->current();
$this->currentKey = $this->oInnerIterator->key();
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
} else {
$this->current = null;
$this->currentKey = null;
$this->hasNext = false;
$this->next = null;
$this->nextKey = null;
}
}
public function valid()
{
return $this->hasCurrent;
}
public function hasNext()
{
return $this->hasNext;
}
public function getNext()
{
return $this->next;
}
public function getNextKey()
{
return $this->nextKey;
}
public function hasPrevious()
{
return $this->hasPrevious;
}
public function getPrevious()
{
return $this->previous;
}
public function getPreviousKey()
{
return $this->previousKey;
}
}
header("Content-type: text/plain; charset=utf-8");
$arr = [
"a" => "alma",
"b" => "banan",
"c" => "cseresznye",
"d" => "dio",
"e" => "eper",
];
$oNeighborIterator = new NeighborIterator(new ArrayIterator($arr));
foreach ($oNeighborIterator as $key => $value) {
// you can get previous and next values:
if (!$oNeighborIterator->hasPrevious()) {
echo "{FIRST}\n";
}
echo $oNeighborIterator->getPreviousKey() . " => " . $oNeighborIterator->getPrevious() . " -----> ";
echo "[ " . $key . " => " . $value . " ] -----> ";
echo $oNeighborIterator->getNextKey() . " => " . $oNeighborIterator->getNext() . "\n";
if (!$oNeighborIterator->hasNext()) {
echo "{LAST}\n";
}
}
You could get the keys of the array before the foreach, then use a counter to check the next element, something like:
//$arr is the array you wish to cycle through
$keys = array_keys($arr);
$num_keys = count($keys);
$i = 1;
foreach ($arr as $a)
{
if ($i < $num_keys && $arr[$keys[$i]] == $a)
{
// we have a match
}
$i++;
}
This will work for both simple arrays, such as array(1,2,3), and keyed arrays such as array('first'=>1, 'second'=>2, 'thrid'=>3).
A foreach loop in php will iterate over a copy of the original array, making next() and prev() functions useless. If you have an associative array and need to fetch the next item, you could iterate over the array keys instead:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = $items[array_keys($items)[$index + 1]];
}
Since the resulting array of keys has a continuous index itself, you can use that instead to access the original array.
Be aware that $next will be null for the last iteration, since there is no next item after the last. Accessing non existent array keys will throw a php notice. To avoid that, either:
Check for the last iteration before assigning values to $next
Check if the key with index + 1 exists with array_key_exists()
Using method 2 the complete foreach could look like this:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = null;
if (array_key_exists($index + 1, array_keys($items))) {
$next = $items[array_keys($items)[$index + 1]];
}
}
$next_data = $data;
$prev_key = null;
$prev_value = null;
foreach($data as $key => $value)
{
array_shift($next_data);
$next_key = key($next_data);
$next_value = $next_data[$next_key] ?? null;
// Do something here...
$prev_key = $key;
$prev_value = $value;
}
or if the array is associative then you could use current() similar to Andrei Krasutski's solution and key()
$values = [];
array_push($values, ["XYZ"=>100]);
array_push($values, ["ABC"=>10]);
array_push($values, ["XYZ"=>130]);
array_push($values, ["DEF"=>4]);
array_push($values, ["XYZ"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo key($currentValue) . "=" . current($currentValue) . " followed by " . key($nextValue) . "/" . current($nextValue) . "<br>\n";
} else {
echo key($currentValue) . "=" . current($currentValue);
}
}
See https://onlinephp.io/c/dc58d for a running example.
or if the array is using named pairs:
$values = [];
array_push($values, ["type"=>"XYZ", "value"=>100]);
array_push($values, ["type"=>"ABC", "value"=>10]);
array_push($values, ["type"=>"XYZ", "value"=>130]);
array_push($values, ["type"=>"DEF", "value"=>"Lorem"]);
array_push($values, ["type"=>"XYZ", "value"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo $currentValue['type'] . "=" . $currentValue['value']
. " followed by " . $nextValue['type'] . "/" . $nextValue['value'] . "<br>\n";
} else {
echo $currentValue['type'] . "=" . $currentValue['value'];
}
}

Categories