Why you should use array functions instead of basic loops
Loop is the basic construction in each programming language. All repeatable operations are being done using loops. I think you know how the basic loops like while, do and for work and when you should use one of them. But are you sure? Maybe should you use array functions?
How many of your operations in a loop rely on building objects based on the value? Or selecting only a few items from the array using a predicate? Maybe you use array values to calculate something? Whatever you do, read why you should replacing basic loops by array operations.
Update 2019-12-11
PHP 7.4 comes with support for short closures aka arrow functions. I enriched this article with examples that use this new functionality.
Working with for loop
Take a look at this example. Looks familiar?
$tags = ["php", "javascript", "programming", "loop"];
$entities = [];
for ($i = 0; $i < count($tags); $i++) {
$entities[] = new Tag($tags[$i]);
}
Everyone knows the for loop. It has three control statements: setup, condition, and instruction after each step. But what exactly this code means?
- Create an empty array called
$entities
. - Set the counter
$i
to 0 (zero). - Iterate until the counter is less than a number of items in
$tags
array and increment counter each time by one. - Inside the loop, create a new instance of Tag. The counter points the name of the tag from
$tags
array. - Add the new object to the
$entities
array.
There are a lot of operations which largely obfuscate the main goal. They are irrevelant. Why should you care about the number of elements, starting point, or the iteration process?
“A programming language is low level when its programs require attention to the irrelevant." — Alan Perlis
Working with foreach loop
You can clean up the previous snippet replacing for in favor of foreach loop.
$tags = ["php", "javascript", "programming", "loop"];
$entities = [];
foreach ($tags as $tag) {
$entities[] = new Tag($tag);
}
Much better. All unnecessary operations are hidden thanks to the foreach loop. It creates an abstraction over basic iteration process. Thanks to this, you can focus on your main goal instead of repeat the loop control logic again and again.
Foreach loop creates a tiny abstraction over basic iteration process. It allows you to focus on the main goal and gives more readable code in the outcome.
Look how more understandable this code is by reading it step by step:
- Create an empty array called
$entities
. - Iterate over
$tags
array and make the current element available by$tag
variable. - Inside the loop, create a new instance of
Tag
which$tag
as a name. - Add the new object to
$entities
array.
No details about the process of iteration, incrementation or even number of elements. Great! But both of these pieces of code have hidden side effect. Can you point out where?
Side effect in computer science is a situation when function or expression modifies variables or resources outside its scope.
The problem lies in loops. Both of loops leave extra variable in execution scope – $i
and $tag
. The variable $tag
contains the last element of $tags
array. Therefore it’s recommended to unset unnecessary variable directly behind the loop.
$tags = ["php", "javascript", "programming", "loop"];
$entities = [];
foreach ($tags as $tag) {
$entities[] = new Tag($tag);
}
echo $tag;
unset($tag);
You can improve this code, even more, by identifying your main goal. What is the main goal in above example? I defined an array of tags and I want to transform them to an array of Tag
objects. Each element from array should be mapped to the new one.
Array functions to the rescue
Introduce mapping
I revealed one of the array operations – mapping. This operation allows you to define a function which will be executed over each element in the array. They create a new array contains the mapped entries. In PHP there is a function called array_map
.
We’ll change the code a bit to use the array_map
function.
For PHP < 7.4:
$tags = ["php", "javascript", "programming", "loop"];
$entities = array_map(function ($tag) {
return new Tag($tag);
}, $tags);
Since PHP 7.4:
$tags = ["php", "javascript", "programming", "loop"];
$entities = array_map(fn($tag) => new Tag($tag), $tags);
No loop control logic, no side effects. I just defined a function that should be executed on each element. The returned value of supplied function should return the new element.
Now, let’s try to explain this code:
- Map each element of
$tags
array to the new one using a supplied callback function. This function defines how to map single element.
That’s it. There is no step 2. The code is easier to read and understand, and communicates proper intentions.
Some people don’t like the anonymous function. Just remember that array_map
expects a callable
. You can pass it by string (e.g. when you want to use the internal function), variable, array – the choice is yours.
Meet the filter function
Sometimes you have a need to select only elements from the array that satisfying defined criteria. To accomplish it you could create a loop which conditionally adds elements to the new array.
$stock = [
['name' => 'Phone', 'qty' => 0],
['name' => 'Notebook', 'qty' => 1],
['name' => 'SDD Drive', 'qty' => 0],
['name' => 'HDD Drive', 'qty' => 3]
];
$inStock = [];
foreach ($stock as $item) {
if ($item['qty'] > 0) {
$inStock[] = $item;
}
}
unset($item);
You already know that basic loops have a noticeable overhead. Instead, you should use the array_filter
function.
For PHP < 7.4:
$stock = [
['name' => 'Phone', 'qty' => 0],
['name' => 'Notebook', 'qty' => 1],
['name' => 'SDD Drive', 'qty' => 0],
['name' => 'HDD Drive', 'qty' => 3]
];
$inStock = array_filter($stock, function ($item) {
return $item['qty'] > 0;
});
Since PHP 7.4:
$stock = [
['name' => 'Phone', 'qty' => 0],
['name' => 'Notebook', 'qty' => 1],
['name' => 'SDD Drive', 'qty' => 0],
['name' => 'HDD Drive', 'qty' => 3]
];
$inStock = array_filter($stock, fn($item) => $item['qty'] > 0);
You probably note that the order of arguments in array_filter
function is different than e.g. in array_map
function. About the PHP inconsistency may write a book but it goes beyond the boundaries of this article.
In this example, array_filter
also takes a callable
. This function is a predicate. They must return a boolean value which defines whether element must be included in the resulting array or not.
If you don’t supply the predicate function array_filter
returns all items except false. Maybe is this the reason why the order of arguments is different than in other array functions?
Reduce array to single value
Maybe you remember a few situation when you need to iterate over an array to e.g. sum any value. Take a look at below example. The goal is to get total points for the team.
$team = [
['name' => 'Marry', 'points' => 11],
['name' => 'Jacob', 'points' => 7],
['name' => 'Joanna', 'points' => 6],
['name' => 'Tony', 'points' => 8]
];
$total = 0;
foreach ($team as $person) {
$total += $person['points'];
}
unset($person);
We’ll switch immediately to the array_reduce
function:
For PHP < 7.4:
$team = [
['name' => 'Marry', 'points' => 11],
['name' => 'Jacob', 'points' => 7],
['name' => 'Joanna', 'points' => 6],
['name' => 'Tony', 'points' => 8]
];
$total = array_reduce($team, function ($carry, $person) {
return $carry + $person['points'];
}, 0);
Since PHP 7.4:
$team = [
['name' => 'Marry', 'points' => 11],
['name' => 'Jacob', 'points' => 7],
['name' => 'Joanna', 'points' => 6],
['name' => 'Tony', 'points' => 8]
];
$total = array_reduce($team, fn($carry, $person) => $carry + $person['points'], 0);
The first impression – there is a more code than in the previous snippet!
You’re right. But the number of lines/words/characters shouldn’t be the most important criteria. Especially if you want to write clean, understandable and side-effects free code.
Sometimes is necessary to write more words to get a more understandable and side-effect free code.
I hope you have seen a difference in the callback
function. There are two arguments. The former, accumulator (I call it $carry
) contains the reduced value and the latter keeps the current element. The callback function handles returning the new value of $carry
.
Value of $carry
is also passed to the next callback execution. Finally returned from the array_reduce
function after all. The third argument of this function is the initial value of $carry
.
Mixed operations during iteration
Let’s consider case when you want to do something with elements which are satisfying defined criteria, e.g.
$players = [
['name' => 'Mike', 'level' => 5],
['name' => 'Ray', 'level' => 6],
['name' => 'Ana', 'level' => 5],
['name' => 'Joe', 'level' => 2]
];
$advancedPlayersNames = [];
foreach ($players as $player) {
if ($player['level'] > 4) {
$advancedPlayersNames[] = "Adv." . $player['name'];
}
}
unset($player);
Inside this loop, you could find both filter and mapping. We want to get names of players whose level is more than 4. To accomplish it using the array methods you should split this problem into small parts.
For PHP < 7.4:
$players = [
['name' => 'Mike', 'level' => 5],
['name' => 'Ray', 'level' => 6],
['name' => 'Ana', 'level' => 5],
['name' => 'Joe', 'level' => 2]
];
$advancedPlayers = array_filter($players, function ($player) {
return $player['level'] > 4;
});
$advancedPlayerNames = array_map(function ($player) {
return "Adv" . $player['name'];
}, $advancedPlayers);
Since PHP 7.4:
$players = [
['name' => 'Mike', 'level' => 5],
['name' => 'Ray', 'level' => 6],
['name' => 'Ana', 'level' => 5],
['name' => 'Joe', 'level' => 2]
];
$advancedPlayers = array_filter($players, fn($player) => $player['level'] > 4);
$advancedPlayerNames = array_map(fn($player) => "Adv" . $player['name'], $advancedPlayers);
So as you see there is no big deal. Instead of defining an algorithm to extract final data, think more about the process. It contains steps like mapping, filtering and reducing. Is this code excessive? Maybe. Is it easier to read and understand? I think yes.
Summary
If you don’t use the array functions yet, just try. I highly encourage you to write your loops in this way. You’ll be surprised how easy to understand will be your code particularly for your coworkers or even you after a long long time. This approach brings us closer to the next interesting things – Collections. Personally, I prefer array functions over basic loops. In my opinion, they’re more meaningful and expressive, because the code is more declarative – it means that I define what I want to reach instead of describing the way how to do it.
Of course, if you work on the critical system you should consider this advice. Array functions are a little bit slower than basic loops but I think that there is no significant impact on performance.
Resources
- http://php.net/manual/en/function.array-map.php
- http://php.net/manual/en/function.array-filter.php
- http://php.net/manual/en/function.array-reduce.php
Featured photo by Photo Design Lab on Unsplash.