34

Is there an SPL Reverse array iterator in PHP? And if not, what would be the best way to achieve it?

I could simply do

$array = array_reverse($array);
foreach($array as $currentElement) {}

or

for($i = count($array) - 1; $i >= 0; $i--)
{

}

But is there a more elegant way?

Sebastian Hoitz
  • 9,343
  • 13
  • 61
  • 77

11 Answers11

77

Here is a solution that does not copy and does not modify the array:

for (end($array); key($array)!==null; prev($array)){
  $currentElement = current($array);
  // ...
}

If you also want a reference to the current key:

for (end($array); ($currentKey=key($array))!==null; prev($array)){
  $currentElement = current($array);
  // ...
}

This works always as php array keys can never be null and is faster than any other answer given here.

William George
  • 6,735
  • 3
  • 31
  • 39
linepogl
  • 9,147
  • 4
  • 34
  • 45
  • 4
    Best answer here. Thank you! – mpen Apr 13 '16 at 17:32
  • This is a correct solution for traversing an array, getting any key (numeric or not) and value (including nulls) in reverse order. – FrancescoMM Nov 03 '20 at 11:27
  • 1
    The OP asked for an iterator though. – Gordon Nov 04 '20 at 09:13
  • But PHP only supports "internal" iterator currently (hence the answer to OP is "impossible"), but that should be no problem, unless the internal iterator is changed (by something inside the loop) unintentionally. – Top-Master Mar 31 '22 at 03:09
  • However that still modifies the array, does it not? It changes the internal array pointer. For the record regular `foreach` also changes the internal pointer. – Danon Oct 17 '22 at 10:40
17
$item=end($array);
do {
...
} while ($item=prev($array));
Community
  • 1
  • 1
AbiusX
  • 2,379
  • 20
  • 26
13

There is no ReverseArrayIterator to do that. You can do

$reverted = new ArrayIterator(array_reverse($data));

or make that into your own custom iterator, e.g.

class ReverseArrayIterator extends ArrayIterator 
{
    public function __construct(array $array)
    {
        parent::__construct(array_reverse($array));
    }
}

A slightly longer implementation that doesn't use array_reverse but iterates the array via the standard array functions would be

class ReverseArrayIterator implements Iterator
{
    private $array;

    public function __construct(array $array)
    {
        $this->array = $array;
    }

    public function current()
    {
        return current($this->array);
    }

    public function next()
    {
        return prev($this->array);
    }

    public function key()
    {
        return key($this->array);
    }

    public function valid()
    {
        return key($this->array) !== null;
    }

    public function rewind()
    {
        end($this->array);
    }
}
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • 7
    I would suggest to do array_reverse($array, true) in order to preserve key association for numeric keys. – dader Feb 02 '13 at 21:00
  • 15
    Keep in mind that array_reverse copies array elements, hence not very performance wise – AbiusX Feb 16 '13 at 16:37
  • 1
    array_reverse is expensive, I wouldn't recommend doing that every time you need to iterate. better to store things in reverse order from the start or use @linepogl's solution. – Dan Bechard Jul 25 '19 at 17:58
  • Not elegant reversing the whole array, this reply does't actually change anything from the OP solutions, just added syntactic sugar (and even more overhead) to the original. – FrancescoMM Nov 03 '20 at 11:20
  • @FrancescoMM no one asked for an elegant solution. And while it doesn't add much over the procedural approach, the OP specifically asked for an SPL iterator. – Gordon Nov 04 '20 at 08:57
  • "But is there a more elegant way?", cit. the OP – FrancescoMM Nov 04 '20 at 10:53
  • @FrancescoMM I stand corrected. Still, I feel the emphasis should be on iterator and SPL and not on elegant, which is why I provided an additional iterator above. – Gordon Nov 04 '20 at 11:21
  • @Gordon yes but array_reverse is so inefficient on long arrays, using yield or iterating in reverse would be "logically speaking" more correct, I think – FrancescoMM Nov 04 '20 at 20:30
11

Based on linepogl's answer, I came up with this function:

/**
 * Iterate an array or other foreach-able without making a copy of it.
 *
 * @param array|\Traversable $iterable
 * @return Generator
 */
function iter_reverse($iterable) {
    for (end($iterable); ($key=key($iterable))!==null; prev($iterable)){
        yield $key => current($iterable);
    }
}

Usage:

foreach(iter_reverse($my_array) as $key => $value) {
    // ... do things ...
}

This works on arrays and other iterables without first making a copy of it.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Nice, it can even be optimized using [C.M.'s answer](https://stackoverflow.com/questions/5315539/iterate-in-reverse-through-an-array-with-php-spl-solution/48407440#48407440) to avoid the `current()` call. – Gras Double Nov 13 '22 at 06:32
  • Note that because inside the function we are altering the internal pointer, while PHP ensures it doesn't affect the pointer of the array outside the function, it actually creates a copy of the array. – Gras Double Nov 13 '22 at 06:42
10

Depending on what you are trying to do, you might want to look into the spl data structure classes, such as SplStack. SplStack implements Iterator, ArrayAccess and Countable, so it can mostly be used like an array, but by default, its iterator proceeds in FILO order. Ex:

$stack = new SplStack();
$stack[] = 'one';
$stack[] = 'two';
$stack[] = 'three';

foreach ($stack as $item)
{
    print "$item\n";
}

This will print

three
two
one
rr.
  • 6,484
  • 9
  • 40
  • 48
6

Note that if you want to preserve the keys of the array, you must pass true as the second parameter to array_reverse:

$array = array_reverse($array, true);
foreach ($array as $currentElement) {
    // do something here
}
Rob Allen
  • 12,643
  • 1
  • 40
  • 49
5

Based on linepogl's answer... You can make it even more efficient by avoiding current() call

for ($value = end($array); ($key = key($array)) !== null; $value = prev($array)) {
     // ... do something with $key => $value
}
C.M.
  • 397
  • 4
  • 5
5
$array = array_reverse($array);
foreach($array as $key => $currentElement) {}

This is better way to use. It will take care of keys also, if they are not sequential or integer.

Gaurav
  • 28,447
  • 8
  • 50
  • 80
  • 1
    This can be shortened to just `foreach(array_reverse($array, true) ...` if you don't need to keep the reversed array. Keep in mind the second argument to `array_reverse`, which keeps the keys. – Charles Mar 15 '11 at 17:50
1
$array=array(
    0 => 0,
    '1' => 1,
    2 => null,
    3 => false
);

$value=end( $array );                      // ← value for first iteration
while(($key=key( $array )) !== null) {
  echo "key=$key, value=$value\n";

  $value=prev( $array );                   // ← value for next iteration
}
Tadej Kobe
  • 19
  • 1
0

This could be a more performant way since it doesnt construct a new array. It also handles empty arrays well.

$item = end($items);
while($item)
{
    ...do stuff...
    $item = prev($items);
}
bitluni
  • 219
  • 1
  • 7
-2

$array1= array(10,20,30,40,50);

        for($i = count($array1) - 1; $i >= 0; $i--)
        {
            $array2[]  = $array1[$i];

        }

        echo "<pre>";
            print_r($array2);
        echo "</pre>";
Rupesh
  • 1