Is it possible to "peek ahead" while iterating an array in PHP 5.2? For example, I often use foreach to manipulate data from an array:
foreach($array as $object) {
// do something
}
But I often need to peek at the next element while going through the array. I know I could use a for loop and reference the next item by it's index ($array[$i+1]), but it wouldn't work for associative arrays. Is there any elegant solution for my problem, perhaps involving SPL?
You can use the CachingIterator for this purpose.
Here is an example:
$collection = new CachingIterator(
new ArrayIterator(
array('Cat', 'Dog', 'Elephant', 'Tiger', 'Shark')));
The CachingIterator is always one step behind the inner iterator:
var_dump( $collection->current() ); // null
var_dump( $collection->getInnerIterator()->current() ); // Cat
Thus, when you do foreach over $collection, the current element of the inner ArrayIterator will be the next element already, allowing you to peek into it:
foreach($collection as $animal) {
echo "Current: $animal";
if($collection->hasNext()) {
echo " - Next:" . $collection->getInnerIterator()->current();
}
echo PHP_EOL;
}
Will output:
Current: Cat - Next:Dog
Current: Dog - Next:Elephant
Current: Elephant - Next:Tiger
Current: Tiger - Next:Shark
Current: Shark
For some reason I cannot explain, the CachingIterator will always try to convert the current element to string. If you want to iterate over an object collection and need to access properties an methods, pass CachingIterator::TOSTRING_USE_CURRENT as the second param to the constructor.
On a sidenote, the CachingIterator gets it's name from the ability to cache all the results it has iterated over so far. For this to work, you have to instantiate it with CachingIterator::FULL_CACHE and then you can fetch the cached results with getCache().
Use array_keys.
$keys = array_keys($array);
for ($i = 0; $i < count($keys); $i++) {
$cur = $array[$keys[$i]];
$next = $array[$keys[$i+1]];
}
You can use next and prev to iterate an array. current returns the current items value and key the current key.
So you could do something like this:
while (key($array) !== null) {
next($array); // set pointer to next element
if (key($array) === null) {
// end of array
} else {
$nextItem = current($array);
}
prev($array); // resetting the pointer to the current element
// …
next($array);
}
I know that this is an old post, but I can explain that current/next/prev thing better now.
Example:
$array = array(1,2,3,2,5);
foreach($array as $k => $v) {
// in foreach when looping the key() and current()
// is already pointing to the next record
// And now we can print current
print 'current key: '.$k.' and value: '.$v;
// if we have next we can print its information too (key+value)
if(current($array)) {
print ' - next key: '.key($array).' and value: '.current($array);
// at the end we must move pointer to next
next($array);
}
print '<br>';
}
// prints:
// current key: 0 and value: 1 - next key: 1 and value: 2
// current key: 1 and value: 2 - next key: 2 and value: 3
// current key: 2 and value: 3 - next key: 3 and value: 2
// current key: 3 and value: 2 - next key: 4 and value: 5
// current key: 4 and value: 5
I know I could use a for loop and reference the next item by its index ($array[$i+1]), but it wouldn't work for associative arrays.
Consider converting your associative array into an sequentially indexed one with array_values(), allowing you to use the simple for loop solution.
Old post but my two cents:
If you are trying to peek ahead, you really need to ask yourself "Am I solving this problem the best way possible."
You can solve all peek-ahead problems without ever doing a peek-ahead. All you need is a "$prevItem" reference declared before the collection and initialize it as null. Each time you go through the loop, at the end, set $prevItem to the current array item you just evaluated. Effectively, instead of peaking ahead, you start executing your real logic at the second item and use the $prevItem reference to do your operation. You skip the first item by noting that $prevItem is null.
$prevItem = null;
$prevKey = null;
foreach($collection as $key => $val)
{
if($prevItem != null)
{
//do your operation here
}
$prevItem = $val;
$prevKey = $key;
}
It's clean code and its a common pattern.
Stay away from poking around at underlying data structures while you are iterating through them... its never good practice, and extremely rare that you would need to do it.
Related
Need subarray difference of below array
$arr = array(
array('s'=>'1','e'=>'3'),
array('s'=>'6','e'=>'7'),
array('s'=>'8','e'=>'9'),
array('s'=>'10','e'=>'14'),
array('s'=>'16','e'=>'17'),
)
if(arr[$arr[$i+1][s] - $i][e] <= 1){
//join them
}
else {
//save them as it is
}
Desired result should
$arr = array(
array('s'=>'1','e'=>'3'),
array('s'=>'6','e'=>'14'),
array('s'=>'16','e'=>'17'),
)
No consecutive (next S-E) should be 1
http://codepad.org/V8omMdn6 is where im struck at
See its like
iteration 0
6-3 = 3
so save array('s'=>'1','e'=>'3'),
iteration 1
8-7 = 1
array('s'=>'6','e'=>'9'), => discade in 2 as it
iteration 2
10-9 = 1
array('s'=>'6','e'=>'10'), => discade in 3 as it
iteration 3
10-9 = 1
array('s'=>'6','e'=>'14'),
iteration 4
16-14 = 4
array('s'=>'16','e'=>'17'),
$result = [];
foreach ($arr as $pair) {
if (empty($result) || $pair['s'] - end($result)['e'] > 1) {
$result[] = $pair;
} else {
$result[key($result)]['e'] = $pair['e'];
}
}
You might also use $last as key instead end() & key() for readability.
Using array pointer functions on $result shortens the code but uses some ugly hidden effects. end($result) returns last element of array (using key bracket with function result is possible since php5.3 I guess), but also sets the pointer, so key($result) will return correct key if needed.
While iterating you process last element of result array - this element might not be valid right away, but you don't need to look ahead. There are two scenarios for last element (+initial state condition for empty $result):
invalid: set e value from current item and process further
valid: leave it and push current item into results for further validation (unless that was the last one).
I took a very brief look at your codepen, I think what you want to achieve is to find out if the start time of a new session is within a given period from the end time of the last session, if so you would like to combine those sessions.
I think you confused yourself by trying to subtract start time of new session from end time of last session, it should be the other way round.
The way you worded the question made it even more confusing for people to understand.
If my interpretation of your question is correct, the below code should work with the test case you posted here.
function combineSession($arr){
$arrCount=count($arr)-1;
for ($i=0; $i<$arrCount; $i++){
//if the difference between s and e is less than or equal to one, then there is a consecutive series
if($arr[$i+1]['s']-$arr[$i]['e'] <= 1){
//assign the value of s at the start of a consecutive series to $temp
if (!isset($temp)){
$temp=$arr[$i]['s'];
}
//if consecutive series ends on the last sub_array, write $temp e pair to output
if ($i==$arrCount-1){
$output[]= array('s'=> $temp, 'e' => $arr[$arrCount]['e']);
}
}
//end of a consecutive series, write $temp and e pair to output, unset $temp
else if (isset($temp) && $i<$arrCount-1){
$output[]=array('s'=> $temp, 'e' => $arr[$i]['e']);
unset($temp);
}
//consecutive series ended at the second last sub-array, write $temp and e pair to output and copy key value pair of the last sub-array to output
else if ($i==$arrCount-1){
$output[]=array('s'=> $temp, 'e' => $arr[$i]['e']);
$output[]=$arr[$arrCount];
}
//not in a consecutive series, simply copy s e key value pair to output
else {
$output[]=$arr[$i];
}
}//end of for loop
print_r($output);
}//end of function
else if ($i==$arrCount-1){ $output[]=!isset($temp) ? $arr[$i] : array('s'=> $temp, 'e' => $arr[$i]['e']); $output[]=$arr[$arrCount]; }
I have a foreach loop like below code :
foreach($coupons as $k=>$c ){
//...
}
now, I would like to fetch two values in every loop .
for example :
first loop: 0,1
second loop: 2,3
third loop: 4,5
how can I do ?
Split array into chunks of size 2:
$chunks = array_chunk($coupons, 2);
foreach ($chunks as $chunk) {
if (2 == sizeof($chunk)) {
echo $chunk[0] . ',' . $chunk[1];
} else {
// if last chunk contains one element
echo $chunk[0];
}
}
If you want to preserve keys - use third parameter as true:
$chunks = array_chunk($coupons, 2, true);
print_r($chunks);
Why you don't use for loop like this :
$length = count($collection);
for($i = 0; $i < $length ; i+=2)
{
// Do something
}
First, I'm making the assumption you are not using PHP 7.
It is possible to do this however, it is highly, highly discouraged and will likely result in unexpected behavior within the loop. Writing a standard for-loop as suggested by #Rizier123 would be better.
Assuming you really want to do this, here's how:
Within any loop, PHP keeps an internal pointer to the iterable. You can change this pointer.
foreach($coupons as $k=>$c ){
// $k represents the current element
next($coupons); // move the internal array pointer by 1 space
$nextK = current($coupons);
prev($coupons);
}
For more details, look at the docs for the internal array pointer.
Again, as per the docs for foreach (emphasis mine):
Note: In PHP 5, when foreach first starts executing, the internal array pointer is automatically reset to the first element of the
array. This means that you do not need to call reset() before a
foreach loop. As foreach relies on the internal array pointer in PHP
5, changing it within the loop may lead to unexpected behavior. In PHP
7, foreach does not use the internal array pointer.
Let's assume your array is something like $a below:
$a = [
"a"=>1,
"b"=>2,
"c"=>3,
"d"=>4,
"e"=>5,
"f"=>6,
"g"=>7,
"h"=>8,
"i"=>9
];
$b = array_chunk($a,2,true);
foreach ($b as $key=>$value) {
echo implode(',',$value) . '<br>';
}
First we split array into chunks (the parameter true preserves the keys) and then we do a foreach loop. Thanks to the use of implode(), you do not need a conditional statement.
well this'd be 1 way of doing it:
$keys=array_keys($coupons);
for($i=0;$i<count($keys);++$i){
$current=$coupons[$keys[$i]];
$next=(isset($keys[$i+1])?$coupons[$keys[$i+1]]:NULL);
}
now the current value is in $current and the next value is in $next and the current key is in $keys[$i] and the next key is in $keys[$i+1] , and so on.
Perhaps this has been asked several times but I can't find the right answer so here goes.
I have two arrays: one with ~135732 and the other one with ~135730 elements. I need to find which items are on the first but not on the second and viceverse and don't know is there is an easy way to achieve that.
This is what I would do it:
$countArr1 = count($arr1);
$countArr2 = count($arr2);
for($i=0; $i < $countArr1; $i++) {
// Check whether current element on $arr1 is on $arr2 or not
if (!in_array($arr1[$i], $arr2)) {
// if it doesn't then add it to $newArr
$newArr[] = $arr1[$i];
}
}
Then I would do the same but inverse for $arr2. In huge arrays could take a while and also could kill memory or server resources, even if it's executed from CLI so which is the best and the most efficient, regarding use of resources, way to achieve this?
EDIT
Let's clear this a bit. I get $arr1 from DB and $arr2 comes from other place. So the big idea is to find which items needs to be updated and which ones needs to be added also which ones needs to be marked as obsolete. In less and common words:
if element is on $arr1 but doesn't exists on $arr2 should be marked as obsolete
if element comes in $arr2 btu doesn't exists on $arr1 then needs to be added (created)
otherwise that element just need to be updated
Clear enough? Feel free to ask everything in order to help on this
EDIT 2
Based on #dakkaron answer I made this code:
// $arr1 and $arr2 are previously built
$sortArr1 = asort($arr1);
$sortArr2 = asort($arr2);
$countArr1 = count($sortArr1);
$countArr2 = count($sortArr2);
$i = $j = 0;
$updArr = $inactiveArr = $newArr = [];
echo "original arr1 count: ", count($arr1), "\n";
echo "original arr2 count: ", count($arr2), "\n";
echo "arr1 count: ", $countArr1, "\n";
echo "arr2 count: ", $countArr2, "\n";
while ( $i < $countArr1 && $j < $countArr2) {
if ($sortArr1[$i] == $sortArr2[$j]) {
//Handle equal values
$updArr[] = $sortArr1[$i];
$i++; $j++;
} else if ($sortArr1[$i] < $sortArr2[$j]) {
//Handle values that are in arr1 but not in arr2
$inactiveArr[] = $sortArr1[$i];
$i++;
} else {
//Handle values that are in arr2 but not in arr1
$newArr[] = $sortArr2[$j];
$j++;
}
}
echo "items update: ", count($updArr), "\n", "items inactive: ", count($inactiveArr), "\n", "items new: ", count($newArr), "\n";
And I got this output:
original arr1 count: 135732
original arr2 count: 135730
arr1 count: 1
arr2 count: 1
items update: 1
items inactive: 0
items new: 0
Why sort count returns 1?
You could take avantage of array_diff: http://php.net/manual/en/function.array-diff.php
Edit
A php function construct is more likely to perform better than an equivalent user-defined one. Searching I found this, but the size of your array is way smaller, and in the end I believe you should benchmark a prototype script with candidate solutions.
See my last comment.
The best solution I can think of would be to first sort both arrays and then compare them from the bottom up.
Start with the lowest element in both arrays and compare them.
If they are equal, take them and move up one element on both arrays.
If they are different, move up one element on the array with the lower value.
If you reached the end of one of the arrays you are done.
After the sorting this should take about O(n) complexity.
This is a bit of code in pseudocode:
arr1 = ...
arr2 = ...
arr1.sort();
arr2.sort();
i1 = 0;
i2 = 0;
while (i1<arr1.length() && i2<arr2.length()) {
if (arr1[i1]==arr2[i2]) {
//Handle equal values
i1++; i2++;
} else if (arr1[i1]<arr2[i2]) {
//Handle values that are in arr1 but not in arr2
i1++;
} else {
//Handle values that are in arr2 but not in arr1
i2++;
}
}
Other than that, if you don't want to implement it yourself, just use array_diff
The best solution i can think of is to sort the second array, and try to look for values from the first array using binary search,
this would take O(nLog(n)) complexity
Since your values are strings, you could take the advantage of PHP’s implementation of arrays using a hash-table internally with O(1) for key lookups:
$diff = [];
// A \ B
$lookup = array_flip($b); // O(n)
foreach ($a as $value) { // O(n)
if (!isset($lookup[$value])) $diff[] = $value;
}
// B \ A
$lookup = array_flip($a); // O(n)
foreach ($b as $value) { // O(n)
if (!isset($lookup[$value])) $diff[] = $value;
}
So in total, it’s O(n) in both space and time.
Of course, in the end you should benchmark it to see if it’s actually more efficient than other solutions here.
Fill hashtable-based dictionary/map (don't know how it is called in PHP) with the second array elements, and check whether every element of the first array presents in this dictionary.
Usual complexity O(N)
for A in arr2
map.insert(A)
for B in arr1
if not map.contains(B) then
element B is on $arr1 but doesn't exists on $arr2
note that this approach doesn't address all problems in your edited question
I have associative array such as:
$myArray = array(
'key1' => 'val 1',
'key2' => 'val 2'
...
);
I do not know the key values up front but want to start looping from the second element. In the example above, that would be from key2 onwards.
I tried
foreach(next(myArray) as $el) {
}
but that didnt work.
Alternatives may be array_slice but that seems messy. Am i missing something obvious?
There really is no "one true way" of doing this. So I'll take it as a benchmark as to where you should go.
All information is based on this array.
$array = array(
1 => 'First',
2 => 'Second',
3 => 'Third',
4 => 'Fourth',
5 => 'Fifth'
);
The array_slice() option. You said you thought this option was overkill, but it seems to me to be the shortest on code.
foreach (array_slice($array, 1) as $key => $val)
{
echo $key . ' ' . $val . PHP_EOL;
}
Doing this 1000 times takes 0.015888 seconds.
There is the array functions that handle the pointer, such as ...
current() - Return the current element in an array.
end() - Set the internal pointer of an array to its last element.
prev() - Rewind the internal array pointer.
reset() - Set the internal pointer of an array to its first element.
each() - Return the current key and value pair from an array and advance the array cursor.
Please note that the each() function has been deprecated as of PHP 7.2, and will be removed in PHP 8.
These functions give you the fastest solution possible, over 1000 iterations.
reset($array);
while (next($array) !== FALSE)
{
echo key($array) . ' ' . current($array) . PHP_EOL;
}
Doing this 1000 times, takes 0.014807 seconds.
Set a variable option.
$first = FALSE;
foreach ($array as $key => $val)
{
if ($first != TRUE)
{
$first = TRUE;
continue;
}
echo $key . ' ' . $val . PHP_EOL;
}
Doing this 1000 times takes 0.01635 seconds.
I rejected the array_shift options because it edits your array and you've never said that was acceptable.
This depends on whether you want to do this just once or many times, and on whether you still need the original array later on.
"First" pattern
$first = true;
foreach ($array as $key=>value) {
if($first) {
$first = false;
continue;
}
// ... more code ...
}
I personally use this solution quite often because it's really straight-forward, everybody gets this. Also, there is no performance hit of creating a new array and you can still operate on the original array after the loop.
However, if you have a couple of loops like this, it kind of starts looking a little unclean, because you need 5 extra lines of code per loop.
array_shift
array_shift($array);
foreach ($array as $key=>value) {
// .... more code ....
}
array_shift is a function tailored to this special case of not wanting the first element. Essentially it's a Perl-ish way of saying $array = array_slice($array, 1) which might not be completely obvious, especially since it modifies the original array.
So, you might want to make a copy of the original array and shift it, if you need both the shifted array multiple times and also the original array later on.
array_slice
And, of course, there is array_slice itself. I don't see anything wrong with array_slice if you want the original array to remain unchanged and you need the sliced array multiple times. However, if you're positive that you always want to slice just one element off, you might as well use the shorthand array_shift (and make a copy before if needed).
You can go with the obvious way:
$flag = false;
foreach($myArray as $el) {
if($flag) {
// do what you want
}
$flag = true;
}
Just another way of flexible iteration:
reset($myArray); // set array pointer to the first element
next($myArray); // skip first element
while (key($myArray) !== null) {
// do something with current($myArray)
next($myArray);
}
As far as I know foreach is just a kind of shortcut for this construction.
From Zend PHP 5 Certication study guide:
As you can see, using this set of functions [reset, next, key,
current, ...] requires quite a bit of work; to be fair, there are some
situations where they offer the only reasonable way of iterating
through an array, particularly if you need to skip back-and-forth
between its elements. If, however, all you need to do is iterate
through the entire array from start to finish, PHP provides a handy
shortcut in the form of the foreach() construct.
If your array was 0 based, it would be if($key>=1), but as your array starts at key 1, then this should work.
foreach ($array as $key=>$value){if($key>=2){
do stuff
}}
You could try:
$temp = array_shift($arr);
foreach($arr as $val) {
// do something
}
array_unshift($arr, $temp);
reset($myArray);
next($myArray);
while ($element = each($myArray))
{
//$element['key'] and $element['value'] can be used
}
My fairly simple solution when this issue pops up. It has the nice advantage of being easily being modified to be able to skip more than just the first element if you want it to.
$doomcounter = 0;
foreach ($doomsdayDevice as $timer){ if($doomcounter == 0){$doomcounter++; continue;}
// fun code goes here
}
I am writing a foreach that does not start at the 0th index but instead starts at the first index of my array. Is there any way to offset the loop's starting point?
Keep it simple.
foreach ($arr as $k => $v) {
if ($k < 1) continue;
// your code here.
}
See the continue control structure in the manual.
A Foreach will reset the array:
Note: When foreach first starts executing, the internal array pointer is automatically reset to the first element of the array. This means that you do not need to call reset() before a foreach loop.
Either use a for loop (only if this is not an associative array)
$letters = range('a','z');
for($offset=1; $offset < count($letters); $offset++) {
echo $letters[$offset];
}
or a while loop (can be any array)
$letters = range('a','z');
next($letters);
while($letter = each($letters)) {
echo $letter['value'];
}
or with a LimitIterator
$letters = new LimitIterator(new ArrayIterator(range('a','z')), 1);
foreach($letters as $letter) {
echo $letter;
}
which lets you specify start offset and count through the constructor.
All of the above will output the letters b to z instead of a to z
You can use the array_slice function:
$arr = array(); // your array
foreach(array_slice($arr, 1) as $foo){
// do what ever you want here
}
Of course, you can use whatever offset value you want. In this case, 1 'skip' the first element of the array.
In a foreach you cant do that. There are only two ways to get what you want:
Use a for loop and start at position 1
use a foreach and use a something like if($key>0) around your actual code
A foreach does what its name is telling you. Doing something for every element :)
EDIT: OK, a very evil solution came just to my mind. Try the following:
foreach(array_reverse(array_pop(array_reverse($array))) as $key => $value){
...
}
That would reverse the array, pop out the last element and reverse it again. Than you'll have a element excluding the first one.
But I would recommend to use one of the other solutions. The best would be the first one.
And a variation: You can use array_slice() for that:
foreach(array_slice($array, 1, null, true) as $key => $value){
...
}
But you should use all three parameters to keep the keys of the array for your foreach loop:
Seems like a for loop would be the better way to go here, but if you think you MUST use foreach you could shift the first element off the array and unshift it back on:
$a = array('foo','bar');
$temp = array_shift($a);
foreach ( $a as $k => $v ) {
//do something
}
array_unshift($a, $temp);
Well no body said it but if you don't mind altering the array and if we want to start from the second element of the given array:
unset($array[key($array)]);
foreach($array as $key=>$value)
{
//do whatever
}
if you do mind, just add,
$saved = $array;
unset($array[key($array)]);
foreach($array as $key=>$value)
{
//do whatever
}
$array = $saved;
Moreover if you want to skip a given known index, just subtitute
key($array)
by the given index
bismillah...
its simple just make own keys
$keys=1;
foreach ($namafile_doc as $value ) {
$data['doc'.$keys]=$value;
$keys++;
}
may this answer can make usefull