3

Given I have an array as follows:

$array = array('a', 'b', 0, 'c', null, 'd');

Now, I can iterate through it easily with foreach of course:

foreach($array as $value){
    var_dump($value);
}

And all is fine and well. However, if I want to do a "peek" to see if I'm on the last element, the following won't work:

reset($array);
while($value = current($array)){
    var_dump($value);
    if(next($array)){
        // this won't be accurate because of the 0 element
    }
}

Ok, so I do a stricter test:

if(null !== next($array)){
    // this STILL won't be accurate because of the null element
}

Is the only solution to use an indexed for loop with a arithmetic peeking? I don't see this as being viable for maintaining associative key integrity without a lot of goofing around. (I'm aware my example doesn't exemplify this caveat, but I'd swap current() for each(), and next() for current())

Is there no foolproof way to accurately determine whether the array pointer has moved past the end of the array, regardless of array element values (null, 0, etc.)

Caveat; While of course there are plenty of solutions that exist using temporary variables, it just seems both filthy and silly that such a thing would be necessitated for this operation. I'm surprised no concise solutions exists.


Well, this is by no means a perfect solution, since array_keys() is creating a new arrayNote but here goes:

$array = array('alpha', 'b' => 'beta', null, 'g' => 'gamma', false, 0, 'delta', null);
list($end) = array_keys(array_slice($array, -1, 1, true));
foreach($array as $key => &$value){
    // do important stuff on each element
    if($key !== $end){
        // do important stuff on all but last element
    }
}

NoteI swapped out array_slice() and array_keys() so a full key-copy isn't created. It was initially: array_slice(array_keys($array), -1);, seems like the revision would be better on memory.


Another edit for those who stumble here; these may be of use in similar situations:

// each returns the current element, but assigns to the referenced arguments
// the "peeked" values. they're missing checks, but it's a start.

function peek(Array &$array, &$value){
    $value = next($array);
    return prev($array);
}

function peek_key(Array &$array, &$key){
    next($array);
    $key = key($array);
    return prev($array);
}

function peek_each(Array &$array, &$key, &$value){
    next($array);
    list($key, $value) = array(key($array), current($array));
    return prev($array);
}
Dan Lugg
  • 20,192
  • 19
  • 110
  • 174

4 Answers4

5

http://www.php.net/next
Note: You won't be able to distinguish the end of an array from a boolean FALSE element. To properly traverse an array which may contain FALSE elements, see the each() function.

while (list($key, $val) = each($fruit)) {
    echo "$key => $val\n";
}

each returns an array or false. That's very uniquely distinguishable.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Thanks @deceze - I was working in an `error_reporting(-1)` environment and I could have sworn PHP was complaining about `list()` or something. It's been a long day, I need coffee or a bed. I'll get the beans. – Dan Lugg Aug 04 '11 at 05:44
  • Actually, (*I must be tired*) I don't know if that solves my problem altogether. `each()` advances the pointer, so now I'm checking `current()` in the iteration. The purpose is to perform a check **at the end of the second-last iteration**, typically to perform an operation only necessary between elements. `current()` in the iteration returns false on a `null`, `false`, or `0` value, so I'm still back at baseline, sort of. – Dan Lugg Aug 04 '11 at 05:49
  • @TomcatExodus , does COUNT solution help in any way ? – DhruvPathak Aug 04 '11 at 05:50
  • @Tom Indeed, there's no nice solution to check whether the current element is the last *without advancing the array pointer.* You could *rewind* the array pointer after your check, or write your loop in a way that you use the next value fetched by `each` in your next iteration. `current()` returns `false` if you're beyond the end of the array. You can check that uniquely with `=== false`. It'll only stumble if the current element is the value `false` as well. Honestly, I have never really encountered a situation where I need all this, maybe your algorithm could be written more elegantly? :o) – deceze Aug 04 '11 at 06:36
  • Yea, the arrays in question hold completely unpredictable data (*`null`, `0`, or any boolean `false` equivalencies are of great likelihood*) Because under some circumstances these data sets are quite large, I'm trying to keep memory consumption (*during iteration*) as low as possible, and try to work by reference as much as possible. If only there were an `end_key(&$array)` method that worked off the internal symbol tables, rather than having to create an inefficient userland version. Check my update for a proposed solution. – Dan Lugg Aug 04 '11 at 07:34
  • Blarg, now I need an additional check for `empty()` arrays though; `error_reporting(-1)` causes PHP to whine about **undefined offsets** on `list()` when it's passed an empty array. – Dan Lugg Aug 04 '11 at 07:58
4

Whats wrong with getting a count of number of elements ? And using that to detect if I am on last element ? http://codepad.org/zTRRjLdl

$myArr = $array = array('a', 'b', 0, 'c', null, 'd');
$count = count($myArr);
$index = 0;

foreach($myArr as $value)
{

   var_dump($value);
   $index++;
   if($index == $count)   
      echo "Its the last one here :)";
}
DhruvPathak
  • 42,059
  • 16
  • 116
  • 175
  • 1
    Thanks @DhruvPathak - It just seems silly to me that such a simple operation would necessitate arbitrary counters, to keep track of something that is already known in the interpreter. PHP knows I haven't reached the end of the array when I hit a `null`, or a `false`. Why there doesn't exist an `is_end(&$array)` function (*and `peek(&$array)`*) to compliment the existing `next()`, `prev()`, etc., I'll never understand. – Dan Lugg Aug 04 '11 at 05:53
  • This can help you more, http://stackoverflow.com/questions/665135/how-do-you-find-the-last-element-of-an-array-while-iterating-using-a-foreach-loop – DhruvPathak Aug 04 '11 at 05:56
  • Thanks @DhruvPathak - Unfortunately the answers therein are only variations of the one you provided; except for the one that uses `end(array_keys($array))`, but unfortunately that simply doesn't work, as `end()` takes the array by reference. – Dan Lugg Aug 04 '11 at 06:26
0

Feeding off of your answer, here is another way of going about this that allows independent testing outside of the actual loop. This test does not affect the internal array pointer.

$arr = array('alpha', 'b' => 'beta', null, 'g' => 'gamma', false, 0, 'delta', null);

if (!empty($arr)) {
   while (TRUE) {
       // advance and rewind the internal array pointer to do useful things
       // (note that this example doesn't cover rewinding the internal array
       // pointer too far)
       if (array_end($arr)) {
           break;
       }
   }
}

/**
 * Detect the end of an array
 * @return boolean TRUE if we've reached the end of the array or exceeded its bounds
 */
function array_end(array $arr) {
    $currentKey = key($arr);
    if (NULL === $currentKey) {
        // we've moved beyond the bounds of the array
        return TRUE;
    }
    $lastKey = key(array_slice($arr, -1, 1, TRUE));
    return ($currentKey === $lastKey);
}
JoBu1324
  • 7,751
  • 6
  • 44
  • 61
0

Well, this is what I ended up going with:

$array = array('alpha', 'b' => 'beta', null, 'g' => 'gamma', false, 0, 'delta', null);

if(!empty($array)){
    list($end) = array_keys(array_slice($array, -1, 1, true));
    foreach($array as $key => &$value){
        // do important stuff on each element
        if($key !== $end){
            // do important stuff on all but last element
        }
    }
}

Regardless, feel free to answer if you have a better solution. This works, but I'd gladly accept anything better.

Dan Lugg
  • 20,192
  • 19
  • 110
  • 174
  • I was looking for a solution myself that abstracted the test from the actual loop, and you set me on the right track - so I took you up on your offer – JoBu1324 Aug 15 '13 at 16:43