Mapping, Filtering and Reducing in PHP
Over the past couple of years I have transitioned from boilerplate-heavy, imperative code, to begin using the more intuitive map , filter and reduce higher-order functions. In this article I hope to highlight the transformation that occurs, along with simplification, when moving away from the imperative and onto the declarative. We shall be performing a simple process which transforms a collection of user entries into a filtered collection of their names. Although trivial in nature, it is a great place to highlight the power of the paradigm shift.
Below you will find the initial collection of user entires we wish to process. The objective is to create a function which returns only the users names, excluding however, the one associated to a supplied id.
$users = [ [ 'id' => 1, 'name' => 'Joe' ], [ 'id' => 2, 'name' => 'Bob' ], [ 'id' => 3, 'name' => 'Sally' ], ];
Imperative Approach
Looking at this problem with an imperative mindset leads us to iterating through the collection, adding names to a temporary collection which match the desired predicate. This boilerplate code is sprinkled throughout many code-bases, and although correct, I feel can be improved upon in its intent and expression.
function getNames(array $users, $excludeId) $names = []; foreach ($users as $u) if ($u['id'] != $excludeId) $names[] = $u['name']; > > return $names; >
Mapping and Filtering
The first higher-order functions we shall be using to tackle this problem are the map and filter . As their names suggests, mapping over a collection applies the given function to each of its’ elements, were as filtering returns the matching elements based on a supplied predicate function. Using these two functions we are able to break apart the problem very clearly into its two individual pieces. Unfortunately however, within PHP we do have to provide quiet verbose declarations, but the intent I feel is still more clearly highlighted.
function getNames(array $users, $excludeId) $filtered = array_filter($users, function ($u) use ($excludeId) return $u['id'] != $excludeId; >); return array_map(function ($u) return $u['name']; >, $filtered); >
Reducing
The solution above is a significant improvement in my opinion, but can be made even better with the introduction of reduce . Simply put reduce can be considered the backbone of both map and filter , as both functions can be created from it. In this instance the supplied function is applied to each of the elements in the collection. This may sound similar to a map , however the addition of an accumulator value (initial value supplied) which is supplied and returned upon each element application, allows you to craft the result you desire. Using this higher-order function provides you with far more flexibility in the resulting transformation. Were as mapping focuses on transforming individual elements, and filtering removes elements from the collection, reducing can do both and so much more.
function getNames(array $users, $excludeId) return array_reduce($users, function ($acc, $u) use ($excludeId) if ($u['id'] == $excludeId) return $acc; > return array_merge($acc, [ $u['name'] ]); >, []); >
Again sadly PHP’s syntax leaves a lot to be desired in regard to declaration of lambda functions and immutable arrays. However, again the intent of the code to me has been significantly improved from the original imperative solution.
The Map class
A Map is a sequential collection of key-value pairs, almost identical to an array used in a similar context. Keys can be any type, but must be unique. Values are replaced if added to the map using the same key.
Strengths
- Keys and values can be any type, including objects.
- Supports array syntax (square brackets).
- Insertion order is preserved.
- Performance and memory efficiency is very similar to an array .
- Automatically frees allocated memory when its size drops low enough.
Weaknesses
Class synopsis
Predefined Constants
Changelog
Version | Description |
---|---|
PECL ds 1.3.0 | The class now implements ArrayAccess . |
Table of Contents
- Ds\Map::allocate — Allocates enough memory for a required capacity
- Ds\Map::apply — Updates all values by applying a callback function to each value
- Ds\Map::capacity — Returns the current capacity
- Ds\Map::clear — Removes all values
- Ds\Map::__construct — Creates a new instance
- Ds\Map::copy — Returns a shallow copy of the map
- Ds\Map::count — Returns the number of values in the map
- Ds\Map::diff — Creates a new map using keys that aren’t in another map
- Ds\Map::filter — Creates a new map using a callable to determine which pairs to include
- Ds\Map::first — Returns the first pair in the map
- Ds\Map::get — Returns the value for a given key
- Ds\Map::hasKey — Determines whether the map contains a given key
- Ds\Map::hasValue — Determines whether the map contains a given value
- Ds\Map::intersect — Creates a new map by intersecting keys with another map
- Ds\Map::isEmpty — Returns whether the map is empty
- Ds\Map::jsonSerialize — Returns a representation that can be converted to JSON
- Ds\Map::keys — Returns a set of the map’s keys
- Ds\Map::ksort — Sorts the map in-place by key
- Ds\Map::ksorted — Returns a copy, sorted by key
- Ds\Map::last — Returns the last pair of the map
- Ds\Map::map — Returns the result of applying a callback to each value
- Ds\Map::merge — Returns the result of adding all given associations
- Ds\Map::pairs — Returns a sequence containing all the pairs of the map
- Ds\Map::put — Associates a key with a value
- Ds\Map::putAll — Associates all key-value pairs of a traversable object or array
- Ds\Map::reduce — Reduces the map to a single value using a callback function
- Ds\Map::remove — Removes and returns a value by key
- Ds\Map::reverse — Reverses the map in-place
- Ds\Map::reversed — Returns a reversed copy
- Ds\Map::skip — Returns the pair at a given positional index
- Ds\Map::slice — Returns a subset of the map defined by a starting index and length
- Ds\Map::sort — Sorts the map in-place by value
- Ds\Map::sorted — Returns a copy, sorted by value
- Ds\Map::sum — Returns the sum of all values in the map
- Ds\Map::toArray — Converts the map to an array
- Ds\Map::union — Creates a new map using values from the current instance and another map
- Ds\Map::values — Returns a sequence of the map’s values
- Ds\Map::xor — Creates a new map using keys of either the current instance or of another map, but not of both