4

I'd like to turn a simple multidimensional array into an even more simple array.

Turn this:

Array
(
    [0] => Array
        (
            [id] => 123
        )
    [1] => Array
        (
            [id] => 456
        )
    ...
    [999] => Array
        (
            [id] => 789
        )
)

Into an array like this:

Array
(
    [0] =>  123
    [1] => 456
    ...
    [999] => 789
)

I'd like to do so without foreach foreach. Is this possible in PHP?

Here's how I can already solve it with a foreach loop:

$newArr = array();
foreach ($arr as $a) {
    $newArr[] = $a['id'];
}
$arr = $newArr;

I'd like to do it without looping. Can you help?

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Ryan
  • 14,682
  • 32
  • 106
  • 179
  • 4
    Why would you like to do it without looping? – Waleed Khan Feb 18 '13 at 20:09
  • several people lately have posted about arrays and "Not looping" i don't understand why - they probably don't either –  Feb 18 '13 at 20:11
  • http://stackoverflow.com/questions/1319903/how-to-flatten-a-multidimensional-array – j08691 Feb 18 '13 at 20:11
  • If you don't have to loop, it's usually more efficient not to loop. – Ryan Feb 18 '13 at 20:12
  • 1
    avoiding a standard practice based on that 'premiss' is not efficient –  Feb 18 '13 at 20:13
  • 2
    An O(n) loop is the least of your worries as a PHP developer – Major Productions Feb 18 '13 at 20:14
  • look at how much more elegant array_map is than the foreach loop: `$arr = array_map('current',$arr);` – Ryan Feb 18 '13 at 20:17
  • while you avoid the foreach() in php, your just using several for() loops in C. There are 4 for() loops in the source for array_map() –  Feb 18 '13 at 20:18
  • 1
    @Ryan And you basically guarantee no one will be able to read your code at a glance. – Waleed Khan Feb 18 '13 at 20:20
  • 2
    Looking ahead, PHP *may* have an [`array_column()`](https://wiki.php.net/rfc/array_column) function (though probably not in PHP 5.5.0). `$ids = array_column($array, 'id');` – salathe Feb 18 '13 at 20:24
  • If don't have in this list http://www.php.net/manual/en/ref.array.php it don't exist – Heberfa Feb 18 '13 at 20:28
  • @Heberfa, correct. The function isn't even in the development source code at the moment. However, as you can see from my link above, the core developers voted in favour of including the function in the future. – salathe Feb 18 '13 at 20:34
  • I fixed the misleading title because, obviously, there is no way to flatten an array without a loop. – Your Common Sense Jul 22 '22 at 05:56

3 Answers3

8

I admire your desire to have something approaching functional programming and implicit looping, but PHP is the wrong language for you. It does not express itself naturally in a functional style.

The reset function returns the first element in an array, so you can map that function over the array:

array_map('reset', $array)

However in PHP the fastest method is a simple for loop (not foreach, for). Here are a bunch of different methods of flattening. Only the functions containing for and foreach perform explicit looping and are included for comparison.

function flatten_for($arr) {
    $c = count($arr);
    $newarr = array();
    for ($i = 0; $i < $c; $i++) {
        $newarr[] = $arr[$i][0];
    }
    return $newarr;
}


function flatten_for_inplace($arr) {
    $c = count($arr);
    for ($i = 0; $i < $c; $i++) {
        $arr[$i] = $arr[$i][0];
    }
}


function flatten_foreach($arr) {
    $newarr = array();
    foreach ($arr as $value) {
        $newarr[] = $value[0];
    }
    return $newarr;
}

function flatten_foreach_inplace($arr) {
    foreach ($arr as $k => $v) {
        $arr[$k] = $v[0];
    }
}

function flatten_foreach_inplace_ref($arr) {
    foreach ($arr as &$value) {
        $value = $value[0];
    }
}

function flatten_map($arr) {
    return array_map('reset', $arr);
}

function flatten_walk($arr) {
    array_walk($arr, function(&$v, $k){$v = $v[0];});
}

function visitor($v, $k, &$a) {
    return $a[] = $v;
}

function flatten_walk_recursive($arr) {
    $newarr = array();
    array_walk_recursive($arr, 'visitor', $newarr);
    return $newarr;
}

function reducer($result, $item) {
    return $item[0];
}

function flatten_reduce($arr) {
    return array_reduce($arr, 'reducer', array());
}

function flatten_merge($arr) {
    return call_user_func_array('array_merge_recursive', $arr);
}

Here is the timing code:

function buildarray($length) {
    return array_map(function($e){return array($e);}, range(0, $length));
}

function timeit($callable, $argfactory, $iterations) {
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        call_user_func($callable, call_user_func($argfactory));
    }
    return microtime(true) - $start;
}

function time_callbacks($callbacks, $argfactory, $iterations) {
    $times = array();
    foreach ($callbacks as $callback) {
        $times[$callback] = timeit($callback, $argfactory, $iterations);
    }
    return $times;
}

function argfactory() {
    return buildarray(1000);
}

$flatteners = array(
    'flatten_for', 'flatten_for_inplace', 'flatten_foreach',
    'flatten_foreach_inplace', 'flatten_foreach_inplace_ref',
    'flatten_map', 'flatten_walk', 'flatten_walk_recursive',
    'flatten_reduce', 'flatten_merge',
);

$results = time_callbacks($flatteners, 'argfactory', 1000);

var_export($results);

On an oldish MacBook Pro (Core 2 Duo, 2.66 GHz, 8GB, PHP 5.3.15 with Suhosin-Patch) I get these results:

array (
  'flatten_for' => 12.793387174606,
  'flatten_for_inplace' => 14.093497991562,
  'flatten_foreach' => 16.71691608429,
  'flatten_foreach_inplace' => 16.964510917664,
  'flatten_foreach_inplace_ref' => 16.618073940277,
  'flatten_map' => 24.578175067902,
  'flatten_walk' => 22.884744882584,
  'flatten_walk_recursive' => 31.647840976715,
  'flatten_reduce' => 17.748590946198,
  'flatten_merge' => 20.691106081009,
)

The difference between the for and foreach methods is smaller on longer arrays.

Surprisingly (to me, anyway) flatten_merge is still slower than a plain for loop. I expected array_merge_recursive to be at least as fast if not faster since it's basically handing the whole job off to a C function!

Francis Avila
  • 31,233
  • 6
  • 58
  • 96
  • You could just use `reset` instead of `current`, if you're worried about the status of the internal array pointers. – nickb Feb 18 '13 at 20:33
  • fascinating. This is extremely helpful. This answer needs more upvotes! I look forward to testing on my own box soon. – Ryan Feb 19 '13 at 01:33
  • `I admire your desire to have something approaching functional programming and implicit looping, but PHP is the wrong language for you.` Unfortunately, even in 2021 it is still true... – Alexander Kucheryuk Sep 30 '21 at 20:02
4

You could map it:

$arr = array_map(function($element) {
    return $element['id'];
}, $arr);

Since array_map probably internally loops, you could do it truly without looping:

$arr = array_reduce($arr, function($arr, $element) {
    $arr[] = $element['id'];
    return $arr;
});

But there's no reason to not loop. There's no real performance gain, and the readability of your code is arguably decreased.

salathe
  • 51,324
  • 12
  • 104
  • 132
Waleed Khan
  • 11,426
  • 6
  • 39
  • 70
2

For now, the easiest way is to use the array_column() function

$newArray = array_column($array, 'id');
Mahdi Shad
  • 1,427
  • 1
  • 14
  • 23