Skip to content

Instantly share code, notes, and snippets.

@y2k-shubham
Last active November 12, 2020 21:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save y2k-shubham/52f6f8e0e4ae566607ee6aec658a441c to your computer and use it in GitHub Desktop.
Save y2k-shubham/52f6f8e0e4ae566607ee6aec658a441c to your computer and use it in GitHub Desktop.
PHP utility functions for array manipulation
class ArrayUtils {
/**
* - Groups a (non-associative) array of items into associative array of chunks (of items), where key of chunk
* is determined by $key_retriever callable
* OR
* - Groups elements of an array into subarrays, thereby converting Array into Array[group => Array]
*
* - Check unit-tests to understand further
* @param array $data
* @param callable $key_retriever function applied to item to determine which bucket an item goes into
* @param callable|null $item_transformer optional function applied to item to transform it in output map
* @param bool $sort whether or not to sort output map by keys of buckets
* @return array Array[group_key => Array[group items]]
*/
public static function groupByFn(
array $data,
callable $key_retriever,
$item_transformer = null,
bool $sort = true
): array {
$grouped_data = array();
if (empty($item_transformer)) {
// if items are NOT to be transformed, we put them in groups as-it-is
foreach ($data as $datum) {
$grouped_data[$key_retriever($datum)][] = $datum;
}
} else {
foreach ($data as $datum) {
// if items are to be transformed, we put transform them before putting into respective groups
$grouped_data[$key_retriever($datum)][] = $item_transformer($datum);
}
}
if ($sort) {
// sort the grouped data (done to improve predictability, ease unit-tests)
ksort($grouped_data);
}
return $grouped_data;
}
/**
* - Groups a (non-associative) array items recursively, essentially converting it into a nested
* tree or JSON like structure. Inspiration taken from: https://stackoverflow.com/a/8587437/3679900
* OR
* - Converts an (non-associative) array of items into a multi-dimensional array by using series
* of callables $key_retrievers and recursion
*
* - This function is an extension to above 'groupByFn', which also groups array but only till 1 (depth) level
* (whereas this one does it till any number of depth levels by using recursion)
* - Check unit-tests to understand further
* @param array $data Array[mixed] (non-associative) array of items that has to be grouped / converted to
* multi-dimensional array
* @param array $key_retrievers Array[Callable[[mixed], int|string]]
* - A list of functions applied to item one-by-one, to determine which
* (key) bucket an item goes into at different levels
* OR
* - A list of callables each of which takes an item or input array as input and returns an int
* or string which is to be used as a (grouping) key for generating multi-dimensional array.
* @param callable|null $leaf_item_value_transformer optional function applied to 'leaf' items (after all grouping
* has been done) to transform their values in output
* @param callable|null $leaf_item_key_transformer optional function applied to 'leaf' items (after all grouping
* has been done) to transform their keys in output. after retrieving new keys, leaf items
* are also sorted by their key (within their respective bucket)
* @param callable|null $leaf_items_sorter optional comparator function to sort lead items by their value.
* - comparator takes two items and returns integer as discussed here:
* https://www.php.net/manual/en/function.uasort.php#refsect1-function.uasort-examples
* - if supplied, it overrides sorting by key (which is default behaviour)
* @return array A nested assoc-array / multi-dimensional array generated by 'grouping' items of
* input $data array at different levels by application of $key_retrievers on them (one-by-one)
*/
public static function groupByFnRecursive(
array $data,
array $key_retrievers,
$leaf_item_value_transformer = null,
$leaf_item_key_transformer = null,
$leaf_items_sorter = null
): array {
// in following expression we are checking for array-length = 0 (and not nullability)
// why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
if (empty($data)) {
// edge-case: if the input $data array is empty, return it unmodified (no need to check for other args)
return $data;
// in following expression we are checking for array-length = 0 (and not nullability)
// why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
} elseif (empty($key_retrievers)) {
// base-case of recursion: when all 'grouping' / 'nesting' into multi-dimensional array has been done,
// then if required, transform the values of 'leaf' items of that array
$data_values = $data;
if (!empty($leaf_item_value_transformer)) {
$data_values = array_map(static function ($item) use ($leaf_item_value_transformer) {
return $leaf_item_value_transformer($item);
}, $data);
}
// if required, transform keys of 'leaf' items
$data_keys = array_keys($data);
if (!empty($leaf_item_key_transformer)) {
$data_keys = array_map(static function ($item) use ($leaf_item_key_transformer) {
return $leaf_item_key_transformer($item);
}, $data);
}
// rebuild the array, sort it by key or value and return
$data_arr = array_combine($data_keys, $data_values);
if (empty($leaf_items_sorter)) {
ksort($data_arr);
} else {
uasort($data_arr, $leaf_items_sorter);
}
return $data_arr;
} else {
// group the array by 1st key_retriever
$grouped_data = self::groupByFn($data, $key_retrievers[0]);
// remove 1st key_retriever from list
array_shift($key_retrievers);
// and then recurse into further levels
// note that here we are able to use array_map (and need not use array_walk) because array_map can preserve
// keys as told here:
// https://www.php.net/manual/en/function.array-map.php#refsect1-function.array-map-returnvalues
return array_map(static function (array $item) use (
$key_retrievers,
$leaf_item_value_transformer,
$leaf_item_key_transformer,
$leaf_items_sorter
): array {
return self::groupByFnRecursive(
$item,
$key_retrievers,
$leaf_item_value_transformer,
$leaf_item_key_transformer,
$leaf_items_sorter
);
}, $grouped_data);
}
}
/**
* Filters a multi-dimensional array recursively by applying series of filtering function callables, each at a
* different level. Filtering is done starting from innermost depth and moving outwards.
* It is assumed that structure / depth of array is consistent throughout (each key grows upto same max depth)
*
* Regarding $filter_callables
* - this is a series of filtering functions (callables) applied at each level (1st callable is for first /
* top-most or outer-most level, next callable is for next level at depth 2 and so on)
* - each filter callable function should accept exactly 2 arguments: (1) the value or item and (2) the key of item
* as mandated by 'ARRAY_FILTER_USE_BOTH' flag of PHP's array_filter function
* - to skip applying filtering at a level, we can pass null (instead of callable) for that position
* - no of callables should be less than or equal to depth of array (or else exception will be thrown)
*
* see test-cases to understand further (plus detailed explaination)
* @param array $nested_array Nested array to be filtered resursively
* @param array $filter_callables List of callables to be used as 'filter' functions at each 'depth' level
* @return array Recursively filtered array
*/
public static function filterByFnRecursive(array $nested_array, array $filter_callables): array {
if (empty($nested_array) || empty($filter_callables)) {
// base case: if array is empty (empty array was passed) or no more callables left to be applied, return
return $nested_array;
} else {
// retrieve first callable (meant for this level)
$filterer = array_shift($filter_callables);
if (!empty($filter_callables)) {
// if there are more callables, recursively apply them on items of current array
$modified_nested_array = array_map(static function (array $item) use ($filter_callables): array {
return self::filterByFnRecursive($item, $filter_callables);
}, $nested_array);
} else {
// otherwise keep the current array intact
$modified_nested_array = $nested_array;
}
if (empty($filterer)) {
// if callable is NULL, return array (at current level) unmodified
// this is provided to allow skipping filtering at any level (by passing null callable)
return $modified_nested_array;
} else {
// otherwise filter the items at current level
return array_filter($modified_nested_array, $filterer, ARRAY_FILTER_USE_BOTH);
}
}
}
/**
* Transposes a 2D array (array of associative arrays) so that keys in 2nd level come to 1st level and vice-versa.
* - The function is resilient to non-uniform keys on 2nd level (that is when not all items have same set of keys)
* - We can optionally provide $inner_keys, in which case only those keys from items (2nd level) will be
* transposed (data of rest will be discarded). This also handles non-existent key being supplied in
* $keys_to_transpose
*
* Check unit-tests for more clarity
*
* @param array $data Array of associative arrays to be transposed Array[int|string => Array[int|string => mixed]]
* @param array $inner_keys Array[int|string] Optional list of item keys (2nd level) to be transposed.
* If not supplied, all unique keys across all items are transposed
* @return array Transposed array
*/
public static function transpose2DArray(array $data, array $inner_keys = array()): array {
if (empty($data)) {
return $data;
} else {
if (empty($inner_keys)) {
// retrieve unique 2nd level keys from PHP 2D array: https://stackoverflow.com/a/31540179/3679900
$inner_keys = array_unique(
array_reduce(
array_map('array_keys', $data),
'array_merge',
[]
)
);
}
$transposed_data = array();
foreach ($inner_keys as $inner_key) {
$transposed_item_assoc = array();
foreach ($data as $outer_key => $outer_item_assoc) {
// this check handles non-uniformity b/w data-items (different items having different keys)
if (array_key_exists($inner_key, $outer_item_assoc)) {
$transposed_item_assoc[$outer_key] = $outer_item_assoc[$inner_key];
}
}
// this check will help if non-existent (inner) keys have been passed in $inner_keys
if (!empty($transposed_item_assoc)) {
$transposed_data[$inner_key] = $transposed_item_assoc;
}
}
return $transposed_data;
}
}
}
class ArrayUtilsTest extends \PHPUnit\Framework\TestCase {
public function testGroupByFn() {
$test_case_inputs = [
// normal case
[
'data' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'key_retriever' => function (int $item): int {
return ($item % 2);
},
'item_transformer' => null,
'sort' => true
],
// transformer function
[
'data' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'key_retriever' => function (int $item): int {
return ($item % 2);
},
'item_transformer' => function (int $item): int {
return $item * 2;
},
'sort' => true
],
// disabled sorting
[
'data' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'key_retriever' => function (int $item): int {
return ($item % 2);
},
'item_transformer' => null,
'sort' => false
],
// empty array
[
'data' => [],
'key_retriever' => function (int $item): int {
return ($item % 2);
},
'item_transformer' => null,
'sort' => false
],
];
$test_case_expected_outputs = [
[
0 => [2, 4, 6, 8, 10],
1 => [1, 3, 5, 7, 9]
],
[
0 => [4, 8, 12, 16, 20],
1 => [2, 6, 10, 14, 18]
],
[
1 => [1, 3, 5, 7, 9],
0 => [2, 4, 6, 8, 10]
],
[]
];
$test_case_computed_outputs = array_map(
static function (array $args): array {
return ArrayUtils::groupByFn(
$args['data'],
$args['key_retriever'],
$args['item_transformer'],
$args['sort']
);
},
$test_case_inputs
);
foreach (array_map(null, $test_case_expected_outputs, $test_case_computed_outputs) as $outputs) {
$this->assertEquals($outputs[0], $outputs[1]);
}
}
public function testGroupByFnRecursive() {
$test_cases = [
// empty-1
[
'inputs' => [
'data' => [],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-2
[
'inputs' => [
'data' => [],
'key_retrievers' => [null],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-3
[
'inputs' => [
'data' => [],
'key_retrievers' => [static function (array $item): int {
return $item['k'];
},],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-4
[
'inputs' => [
'data' => [],
'key_retrievers' => [
static function (array $item): int {
return $item['k1'];
},
static function (array $item): int {
return $item['k2'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-5
[
'inputs' => [
'data' => [],
'key_retrievers' => [],
'leaf_item_value_transformer' => static function (array $item): int {
return $item['v'];
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-6
[
'inputs' => [
'data' => [],
'key_retrievers' => [null],
'leaf_item_value_transformer' => static function (array $item): int {
return $item['v'];
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-7
[
'inputs' => [
'data' => [],
'key_retrievers' => [static function (array $item): int {
return $item['k'];
},],
'leaf_item_value_transformer' => static function (array $item): int {
return $item['v'];
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// empty-8
[
'inputs' => [
'data' => [],
'key_retrievers' => [
static function (array $item): int {
return $item['k1'];
},
static function (array $item): int {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): int {
return $item['v'];
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [],
],
// non-empty-0-1
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2']],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2']],
],
// non-empty: 1-level (length = 1)
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => ['0v' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],],
],
// non-empty: 1-level (length > 1)
[
'inputs' =>
[
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'1v' => [['k' => '1v', 'k1' => '1v1', 'k2' => '1v2'],]
],
],
// non-empty: 1-level (length > 1) matching keys
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
],
],
// non-empty: 1-level (length = 1) with leaf_item_value_transformer
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => ['0v' => ['k=0v_k1=0v1_k2=0v2',],],
],
// non-empty: 1-level (length > 1) with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v' => ['k=0v_k1=0v1_k2=0v2'],
'1v' => ['k=1v_k1=1v1_k2=1v2'],
],
],
// non-empty: 1-level (length > 1) matching keys with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [static function (array $item): string {
return $item['k'];
},],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => ['v' => ['k=v_k1=0v1_k2=0v2', 'k=v_k1=1v1_k2=1v2'],],
],
// non-empty: 2-level (length = 1)
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'0v2' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
]
],
],
// non-empty: 2-level (length > 1)
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'0v2' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
],
'1v1' => [
'1v2' => [['k' => '1v', 'k1' => '1v1', 'k2' => '1v2'],],
]
],
],
// non-empty: 2-level (length > 1) match + no-match
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'0v1' => [['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],],
'1v1' => [['k' => 'v', 'k1' => '1v1', 'k2' => '1v2'],],
],
],
],
// non-empty: 2-level (length > 1) no-match + match
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'v' => [['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],],
],
'1v1' => [
'v' => [['k' => 'v', 'k1' => '1v1', 'k2' => '1v2'],],
]
],
],
// non-empty: 2-level (length > 1) match + match
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'v1' => [
['k' => 'v', 'k1' => 'v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => '1v2']
],
],
],
],
// non-empty: 2-level (length = 1) with leaf_item_value_transformer
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'0v2' => ['k=0v_k1=0v1_k2=0v2'],
],
],
],
// non-empty: 2-level (length > 1) with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'0v2' => ['k=0v_k1=0v1_k2=0v2'],
],
'1v1' => [
'1v2' => ['k=1v_k1=1v1_k2=1v2'],
],
],
],
// non-empty: 2-level (length > 1) match + no-match with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'0v1' => ['k=v_k1=0v1_k2=0v2',],
'1v1' => ['k=v_k1=1v1_k2=1v2',],
],
],
],
// non-empty: 2-level (length > 1) no-match + match with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v1' => [
'v' => ['k=v_k1=0v1_k2=0v2'],
],
'1v1' => [
'v' => ['k=v_k1=1v1_k2=1v2'],
],
],
],
// non-empty: 2-level (length > 1) match + match with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'v1' => ['k=v_k1=v1_k2=0v2', 'k=v_k1=v1_k2=1v2'],
],
],
],
// non-empty: 3-level (length > 1) with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'0v' => ['0v1' => ['0v2' => ['k=0v_k1=0v1_k2=0v2'],],],
'1v' => ['1v1' => ['1v2' => ['k=1v_k1=1v1_k2=1v2'],],],
],
],
// non-empty: 3-level (length > 1) match + match + no-match with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'v1' => [
'0v2' => ['k=v_k1=v1_k2=0v2',],
'1v2' => ['k=v_k1=v1_k2=1v2',],
],
],
],
],
// non-empty: 3-level (length > 1) match + match + match with leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'v' => [
'v1' => [
'v2' => ['k=v_k1=v1_k2=v2', 'k=v_k1=v1_k2=v2'],
],
],
],
],
// non-empty: 3-level (length > 1) with leaf_item_key_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k'];
},
],
'expected_outputs' => [
'0v' => ['0v1' => ['0v2' => ['0v' => ['k' => '0v', 'k1' => '0v1', 'k2' => '0v2']],],],
'1v' => ['1v1' => ['1v2' => ['1v' => ['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']],],],
],
],
// non-empty: 3-level (length > 1) match + match + no-match with leaf_item key & value transformers
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => '0v2'],
['k' => 'v', 'k1' => 'v1', 'k2' => '1v2']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k'];
},
],
'expected_outputs' => [
'v' => [
'v1' => [
'0v2' => ['v' => 'k=v_k1=v1_k2=0v2',],
'1v2' => ['v' => 'k=v_k1=v1_k2=1v2',],
],
],
],
],
// non-empty: 3-level (length > 1) match + match + match with leaf_item key & value transformers
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2', 'k3' => '0v3'],
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2', 'k3' => '1v3']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k3'];
},
],
'expected_outputs' => [
'v' => [
'v1' => [
'v2' => [
'0v3' => 'k=v_k1=v1_k2=v2',
'1v3' => 'k=v_k1=v1_k2=v2',
],
],
],
],
],
// this is found to be irrelevant for testing since the assertion passes even without correct ordering
// non-empty: 3-level (length > 1) match + match + match with leaf_item key & value transformers and sorting by key
[
'inputs' => [
'data' => [
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2', 'k3' => 'b'],
['k' => 'v', 'k1' => 'v1', 'k2' => 'v2', 'k3' => 'a']
],
'key_retrievers' => [
static function (array $item): string {
return $item['k'];
},
static function (array $item): string {
return $item['k1'];
},
static function (array $item): string {
return $item['k2'];
},
],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}_k3=${item['k3']}";
},
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k3'];
},
],
'expected_outputs' => [
'v' => [
'v1' => [
'v2' => [
'a' => 'k=v_k1=v1_k2=v2_k3=a',
'b' => 'k=v_k1=v1_k2=v2_k3=b',
],
],
],
],
],
// non-empty: 0-levels (no key retrievers) (length = 1) with only leaf_item_value_transformer
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [],
'leaf_item_value_transformer' => static function (array $item): string {
return $item['k'];
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => ['0v',],
],
// non-empty: 0-levels (no key retrievers) (length > 1) with only leaf_item_value_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => null,
],
'expected_outputs' => ['k=0v_k1=0v1_k2=0v2', 'k=1v_k1=1v1_k2=1v2'],
],
// non-empty: 0-levels (no key retrievers) (length = 1) with only leaf_item_key_transformer
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k'];
},
],
'expected_outputs' => ['0v' => ['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
],
// non-empty: 0-levels (no key retrievers) (length > 1) with only leaf_item_key_transformer
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
],
'expected_outputs' => [
'k=0v_k1=0v1_k2=0v2' => ['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
'k=1v_k1=1v1_k2=1v2' => ['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
],
// non-empty: 0-levels (no key retrievers) (length = 1) with only leaf_item key & value transformers
[
'inputs' => [
'data' => [['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],],
'key_retrievers' => [],
'leaf_item_value_transformer' => static function (array $item): string {
return $item['k'];
},
'leaf_item_key_transformer' => static function (array $item): string {
return $item['k'];
},
],
'expected_outputs' => ['0v' => '0v',],
],
// non-empty: 0-levels (no key retrievers) (length > 1) with only leaf_item key & value transformers
[
'inputs' => [
'data' => [
['k' => '0v', 'k1' => '0v1', 'k2' => '0v2'],
['k' => '1v', 'k1' => '1v1', 'k2' => '1v2']
],
'key_retrievers' => [],
'leaf_item_value_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
'leaf_item_key_transformer' => static function (array $item): string {
return "k=${item['k']}_k1=${item['k1']}_k2=${item['k2']}";
},
],
'expected_outputs' => [
'k=0v_k1=0v1_k2=0v2' => 'k=0v_k1=0v1_k2=0v2',
'k=1v_k1=1v1_k2=1v2' => 'k=1v_k1=1v1_k2=1v2'
],
],
// actual inputs without key-transformer
[
'inputs' => [
'data' => [
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "fpa",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
'key_retrievers' => [
static function (array $creative): int {
return $creative['campaign_id'];
},
static function (array $creative): int {
return $creative['banner_id'];
},
static function (array $creative): string {
return $creative['image_type'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
1 => [
11 => [
'default' => [
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
]
],
],
],
2 => [
21 => [
'default' => [
[
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
22 => [
'default' => [
[
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
],
3 => [
31 => [
'default' => [
[
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
32 => [
'fpa' => [
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "fpa",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
]
],
'mb' => [
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
],
],
],
// actual inputs with key-transformer
[
'inputs' => [
'data' => [
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "fpa",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
'key_retrievers' => [
static function (array $creative): int {
return $creative['campaign_id'];
},
static function (array $creative): int {
return $creative['banner_id'];
},
static function (array $creative): string {
return $creative['image_type'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => static function (array $creative): int {
return $creative['creative_id'];
},
],
'expected_outputs' => [
1 => [
11 => [
'default' => [
111 => [
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
112 => [
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
]
],
],
],
2 => [
21 => [
'default' => [
211 => [
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
22 => [
'default' => [
221 => [
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
],
3 => [
31 => [
'default' => [
311 => [
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
32 => [
'fpa' => [
321 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "fpa",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
]
],
'mb' => [
322 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
323 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
],
],
],
// actual inputs with key-transformer & items-sorter
[
'inputs' => [
'data' => [
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_UNKNOWN,
],
[
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => -2,
],
[
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_UNKNOWN,
],
[
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => 5,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_REJECTED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_APPROVED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 324,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_DELETED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 325,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => 5,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 326,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_REJECTED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 327,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_PENDING,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 328,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_APPROVED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 329,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_DELETED,
],
[
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 3210,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_PENDING,
],
],
'key_retrievers' => [
static function (array $creative): int {
return $creative['campaign_id'];
},
static function (array $creative): int {
return $creative['banner_id'];
},
static function (array $creative): string {
return $creative['image_type'];
},
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => static function (array $creative): int {
return $creative['creative_id'];
},
'leaf_items_sorter' => static function (array $creative_1, array $creative_2): int {
$status_1 = $creative_1['creative_status'] ?? self::STATUS_UNKNOWN;
$status_2 = $creative_2['creative_status'] ?? self::STATUS_UNKNOWN;
$rank_1 = self::STATUS_RANKS[$status_1] ?? self::STATUS_RANKS[self::STATUS_UNKNOWN];
$rank_2 = self::STATUS_RANKS[$status_2] ?? self::STATUS_RANKS[self::STATUS_UNKNOWN];
return $rank_1 - $rank_2;
}
],
'expected_outputs' => [
1 => [
11 => [
'default' => [
111 => [
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 111,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_UNKNOWN,
],
112 => [
"campaign_id" => 1,
"banner_id" => 11,
"creative_id" => 112,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => -2,
]
],
],
],
2 => [
21 => [
'default' => [
211 => [
"campaign_id" => 2,
"banner_id" => 21,
"creative_id" => 211,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_UNKNOWN,
],
],
],
22 => [
'default' => [
221 => [
"campaign_id" => 2,
"banner_id" => 22,
"creative_id" => 221,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
],
],
],
3 => [
31 => [
'default' => [
311 => [
"campaign_id" => 3,
"banner_id" => 31,
"creative_id" => 311,
"image_type" => "default",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => 5,
],
],
],
32 => [
'mb' => [
327 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 327,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_PENDING,
],
3210 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 3210,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_PENDING,
],
323 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 323,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_APPROVED,
],
328 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 328,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_APPROVED,
],
321 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 321,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_REJECTED,
],
326 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 326,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_REJECTED,
],
324 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 324,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_DELETED,
],
329 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 329,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => self::STATUS_DELETED,
],
322 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 322,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
],
325 => [
"campaign_id" => 3,
"banner_id" => 32,
"creative_id" => 325,
"image_type" => "mb",
"path" => "https://b.zmtcdn.com/data/ads_creatives//",
"heading" => "Social Affair",
"description" => "Coffee saves the day - hang in there!",
"creative_status" => 5,
],
],
],
],
],
],
// inputs with items other than arrays
[
'inputs' => [
'data' => [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'key_retrievers' => [
static function (int $num): string {
return ($num >= 0) ? 'positive' : 'negative';
},
static function (int $num): int {
return ($num % 2);
},
static function (int $num): string {
return (abs($num) > 5) ? 'abs_value > 5' : 'abs_value <= 5';
}
],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
],
'expected_outputs' => [
'negative' => [
0 => [
'abs_value > 5' => [-10, -8, -6],
'abs_value <= 5' => [-4, -2],
],
-1 => [
'abs_value > 5' => [-9, -7],
'abs_value <= 5' => [-5, -3, -1],
],
],
'positive' => [
0 => [
'abs_value > 5' => [6, 8, 10,],
'abs_value <= 5' => [0, 2, 4,],
],
1 => [
'abs_value > 5' => [7, 9,],
'abs_value <= 5' => [1, 3, 5,],
],
],
]
],
[
'inputs' => [
'data' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'],
'key_retrievers' => [
// group by 2nd last digit of ASCII value
static function (string $str): int {
return intdiv(ord($str[0]), 10);
}
],
'leaf_item_value_transformer' => null,
// use ASCII value as key
'leaf_item_key_transformer' => static function (string $str): int {
return ord($str[0]);
},
'leaf_items_sorter' => static function (string $str_1, string $str_2): int {
// put all even-ending ASCII values before odd ones
return (ord($str_1[0]) % 2) <=> (ord($str_2[0]) % 2);
}
],
'expected_outputs' => [
9 => [
98 => "b",
97 => "a",
99 => "c"
],
10 => [
100 => "d",
102 => "f",
104 => "h",
106 => "j",
108 => "l",
101 => "e",
103 => "g",
105 => "i",
107 => "k",
109 => "m"
],
11 => [
110 => "n",
112 => "p",
111 => "o"
]
],
],
[
'inputs' => [
'data' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
// use ASCII value as key
'leaf_item_key_transformer' => static function (string $str): int {
return ord($str[0]);
},
'leaf_items_sorter' => static function (string $str_1, string $str_2): int {
// put all even-ending ASCII values before odd ones
return (ord($str_1[0]) % 2) <=> (ord($str_2[0]) % 2);
}
],
'expected_outputs' => [
98 => "b",
100 => "d",
102 => "f",
104 => "h",
106 => "j",
108 => "l",
110 => "n",
112 => "p",
97 => "a",
99 => "c",
101 => "e",
103 => "g",
105 => "i",
107 => "k",
109 => "m",
111 => "o"
],
],
[
'inputs' => [
'data' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'],
'key_retrievers' => [],
'leaf_item_value_transformer' => null,
'leaf_item_key_transformer' => null,
'leaf_items_sorter' => static function (string $str_1, string $str_2): int {
// put all even-ending ASCII values before odd ones
return (ord($str_1[0]) % 2) <=> (ord($str_2[0]) % 2);
}
],
'expected_outputs' => [
1 => "b",
3 => "d",
5 => "f",
7 => "h",
9 => "j",
11 => "l",
13 => "n",
15 => "p",
0 => "a",
2 => "c",
4 => "e",
6 => "g",
8 => "i",
10 => "k",
12 => "m",
14 => "o"
],
],
];
foreach ($test_cases as $test_case) {
$inputs = $test_case['inputs'];
$computed_outputs = ArrayUtils::groupByFnRecursive(
$inputs['data'],
$inputs['key_retrievers'],
$inputs['leaf_item_value_transformer'] ?? null,
$inputs['leaf_item_key_transformer'] ?? null,
$inputs['leaf_items_sorter'] ?? null
);
$expected_outputs = $test_case['expected_outputs'];
$this->assertEquals($expected_outputs, $computed_outputs);
}
}
public function testFilterByFnRecursive() {
/**
* explaination the working (this is the first test-case below)
* suppose we have the $nested_array of res-data belonging to grouped by countries (1, 2, & 3) & cities (11, 12, 21,
* 22, 23, 31)
* [
* 1 => [
* 11 => [
* 111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
* 112 => ['res_id' => 112, 'city_id' => 11, 'country_id' => 1],
* 113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
* ],
* 12 => [
* 121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
* ],
* ],
* 2 => [
* 21 => [
* 212 => ['res_id' => 212, 'city_id' => 21, 'country_id' => 2],
* 214 => ['res_id' => 214, 'city_id' => 21, 'country_id' => 2],
* ],
* 22 => [
* 221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
* 222 => ['res_id' => 222, 'city_id' => 22, 'country_id' => 2],
* 223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
* ],
* ],
* 3 => [
* 31 => [
* 312 => ['res_id' => 312, 'city_id' => 21, 'country_id' => 2],
* 314 => ['res_id' => 314, 'city_id' => 21, 'country_id' => 2],
* ],
* ]
* ]
* and we want to remove all restaurants (plus the parent sub-array structure) having even res_ids (keep odd
* ones) so that resulting output nested array is (note that not only individual 'leaf' items depicting res
* have been filtered, but also higher level city and country items have been filtered if they contained only
* even res_ids (which we intended to remove)
* [
* 1 => [
* 11 => [
* 111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
* 113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
* ],
* 12 => [
* 121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
* ],
* ],
* 2 => [
* 22 => [
* 221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
* 223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
* ],
* ],
* ]
* to achieve this, we'll pass the following list of $filter_callables
* $filter_callables = [
* static function($value, $key): bool {return !empty($value);},
* static function($value, $key): bool {return !empty($value);},
* static function($value, $key): bool {
* return (((int) ($value['res_id'] ?? 0)) % 2) != 0;
* }
* ]
*/
$test_cases = [
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
112 => ['res_id' => 112, 'city_id' => 11, 'country_id' => 1],
113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
],
12 => [
121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
],
],
2 => [
21 => [
212 => ['res_id' => 212, 'city_id' => 21, 'country_id' => 2],
214 => ['res_id' => 214, 'city_id' => 21, 'country_id' => 2],
],
22 => [
221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
222 => ['res_id' => 222, 'city_id' => 22, 'country_id' => 2],
223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
],
],
3 => [
31 => [
312 => ['res_id' => 312, 'city_id' => 21, 'country_id' => 2],
314 => ['res_id' => 314, 'city_id' => 21, 'country_id' => 2],
],
]
],
'filter_callables' => [
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return (((int) ($value['res_id'] ?? 0)) % 2) !== 0;
}
],
],
'expected_output' => [
1 => [
11 => [
111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
],
12 => [
121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
],
],
2 => [
22 => [
221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
],
],
],
],
// same case as previous but demonstrates skipping filtering at some level (here top-most) by passing
// null in place of filter_callable
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
112 => ['res_id' => 112, 'city_id' => 11, 'country_id' => 1],
113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
],
12 => [
121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
],
],
2 => [
21 => [
212 => ['res_id' => 212, 'city_id' => 21, 'country_id' => 2],
214 => ['res_id' => 214, 'city_id' => 21, 'country_id' => 2],
],
22 => [
221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
222 => ['res_id' => 222, 'city_id' => 22, 'country_id' => 2],
223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
],
],
3 => [
31 => [
312 => ['res_id' => 312, 'city_id' => 21, 'country_id' => 2],
314 => ['res_id' => 314, 'city_id' => 21, 'country_id' => 2],
],
]
],
'filter_callables' => [
null,
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return (((int) ($value['res_id'] ?? 0)) % 2) !== 0;
}
],
],
'expected_output' => [
1 => [
11 => [
111 => ['res_id' => 111, 'city_id' => 11, 'country_id' => 1],
113 => ['res_id' => 113, 'city_id' => 11, 'country_id' => 1],
],
12 => [
121 => ['res_id' => 121, 'city_id' => 12, 'country_id' => 1],
],
],
2 => [
22 => [
221 => ['res_id' => 221, 'city_id' => 22, 'country_id' => 2],
223 => ['res_id' => 223, 'city_id' => 22, 'country_id' => 2],
],
],
3 => [],
],
],
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
'default' => [
'11d1' => [
'creative_id' => '11d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'11d2' => [
'creative_id' => '11d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
],
'filter_callables' => [
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
$pending_creatives = array_filter(
$value,
static function (array $creative): bool {
return (((int) $creative['creative_status']) === \Ads\Creative::STATUS_PENDING);
}
);
return !empty($pending_creatives);
}
],
],
'expected_output' => [
1 => [
11 => [
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
],
],
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
'default' => [
'11d1' => [
'creative_id' => '11d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'11d2' => [
'creative_id' => '11d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
12 => [
'default' => [
'12d1' => [
'creative_id' => '12d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'12d2' => [
'creative_id' => '12d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
],
'filter_callables' => [
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
$pending_creatives = array_filter(
$value,
static function (array $creative): bool {
return (((int) $creative['creative_status']) === \Ads\Creative::STATUS_PENDING);
}
);
return !empty($pending_creatives);
}
],
],
'expected_output' => [
1 => [
12 => [
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
],
],
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
'default' => [
'11d1' => [
'creative_id' => '11d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'11d2' => [
'creative_id' => '11d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
12 => [
'default' => [
'12d1' => [
'creative_id' => '12d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'12d2' => [
'creative_id' => '12d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
2 => [
21 => [
'default' => [
'21d1' => [
'creative_id' => '21d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'21d2' => [
'creative_id' => '21d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'21f1' => [
'creative_id' => '21f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'21f2' => [
'creative_id' => '21f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
22 => [
'default' => [
'22d1' => [
'creative_id' => '22d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'22d2' => [
'creative_id' => '22d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'22f1' => [
'creative_id' => '22f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'22f2' => [
'creative_id' => '22f2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
],
],
],
'filter_callables' => [
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
$pending_creatives = array_filter(
$value,
static function (array $creative): bool {
return (((int) $creative['creative_status']) === \Ads\Creative::STATUS_PENDING);
}
);
return !empty($pending_creatives);
}
],
],
'expected_output' => [
1 => [
12 => [
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
],
],
[
'inputs' => [
'nested_array' => [],
'filter_callables' => [],
],
'expected_output' => [],
],
[
'inputs' => [
'nested_array' => [],
'filter_callables' => [
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
return !empty($value);
},
static function ($value, $key): bool {
$pending_creatives = array_filter(
$value,
static function (array $creative): bool {
return (((int) $creative['creative_status']) === \Ads\Creative::STATUS_PENDING);
}
);
return !empty($pending_creatives);
}
],
],
'expected_output' => [],
],
[
'inputs' => [
'nested_array' => [
1 => [
11 => [
'default' => [
'11d1' => [
'creative_id' => '11d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'11d2' => [
'creative_id' => '11d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
12 => [
'default' => [
'12d1' => [
'creative_id' => '12d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'12d2' => [
'creative_id' => '12d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
2 => [
21 => [
'default' => [
'21d1' => [
'creative_id' => '21d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'21d2' => [
'creative_id' => '21d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'21f1' => [
'creative_id' => '21f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'21f2' => [
'creative_id' => '21f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
22 => [
'default' => [
'22d1' => [
'creative_id' => '22d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'22d2' => [
'creative_id' => '22d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'22f1' => [
'creative_id' => '22f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'22f2' => [
'creative_id' => '22f2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
],
],
],
'filter_callables' => [],
],
'expected_output' => [
1 => [
11 => [
'default' => [
'11d1' => [
'creative_id' => '11d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'11d2' => [
'creative_id' => '11d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'11f1' => [
'creative_id' => '11f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'11f2' => [
'creative_id' => '11f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
12 => [
'default' => [
'12d1' => [
'creative_id' => '12d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'12d2' => [
'creative_id' => '12d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'12f1' => [
'creative_id' => '12f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'12f2' => [
'creative_id' => '12f2',
'creative_status' => \Ads\Creative::STATUS_PENDING
],
],
],
],
2 => [
21 => [
'default' => [
'21d1' => [
'creative_id' => '21d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'21d2' => [
'creative_id' => '21d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'21f1' => [
'creative_id' => '21f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'21f2' => [
'creative_id' => '21f2',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
],
],
22 => [
'default' => [
'22d1' => [
'creative_id' => '22d1',
'creative_status' => \Ads\Creative::STATUS_REJECTED
],
'22d2' => [
'creative_id' => '22d2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
'fpa' => [
'22f1' => [
'creative_id' => '22f1',
'creative_status' => \Ads\Creative::STATUS_INACTIVE
],
'22f2' => [
'creative_id' => '22f2',
'creative_status' => \Ads\Creative::STATUS_APPROVED
],
],
],
],
],
],
];
$computed_outputs = array_map(
static function (array $inputs): array {
$arr = ArrayUtils::filterByFnRecursive(
$inputs['nested_array'],
$inputs['filter_callables']
);
return $arr;
},
array_column($test_cases, 'inputs')
);
$expected_outputs = array_column($test_cases, 'expected_output');
foreach (array_map(null, $expected_outputs, $computed_outputs) as $idx => $zipped_arrs) {
list($expected_output, $computed_output) = $zipped_arrs;
$this->assertEquals($expected_output, $computed_output);
}
}
public function testTranspose2DArray() {
$test_cases = [
// test-case 1: data empty
[
'inputs' => [
'data' => [],
'keys_to_transpose' => [],
],
'expected_output' => [],
],
// test-case 2: normal count($data) = 1 (length 1)
[
'inputs' => [
'data' => [
111 => [
'r1' => 1,
'r2' => 2,
'r3' => 3
],
],
'keys_to_transpose' => [],
],
'expected_output' => [
'r1' => [111 => 1],
'r2' => [111 => 2],
'r3' => [111 => 3]
],
],
// test-case 3: normal count($data) = 3 (length 3)
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r1' => 12,
'r2' => 22,
'r3' => 32
],
333 => [
'r1' => 13,
'r2' => 23,
'r3' => 33
],
],
'keys_to_transpose' => [],
],
'expected_output' => [
'r1' => [111 => 11, 222 => 12, 333 => 13],
'r2' => [111 => 21, 222 => 22, 333 => 23],
'r3' => [111 => 31, 222 => 32, 333 => 33],
],
],
// test-case 4: normal 2-keys supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r1' => 12,
'r2' => 22,
'r3' => 32
],
333 => [
'r1' => 13,
'r2' => 23,
'r3' => 33
],
],
'keys_to_transpose' => ['r1', 'r3'],
],
'expected_output' => [
'r1' => [111 => 11, 222 => 12, 333 => 13],
'r3' => [111 => 31, 222 => 32, 333 => 33],
],
],
// test-case 5: normal 1-key1 supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r1' => 12,
'r2' => 22,
'r3' => 32
],
333 => [
'r1' => 13,
'r2' => 23,
'r3' => 33
],
],
'keys_to_transpose' => ['r2'],
],
'expected_output' => [
'r2' => [111 => 21, 222 => 22, 333 => 23],
],
],
// test-case 6: uneven inner keys
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r2' => 22,
'r3' => 32,
'r4' => 42
],
333 => [
'r3' => 33,
'r4' => 43,
'r5' => 53,
'r6' => 63
],
],
'keys_to_transpose' => null,
],
'expected_output' => [
'r1' => [111 => 11],
'r2' => [111 => 21, 222 => 22],
'r3' => [111 => 31, 222 => 32, 333 => 33],
'r4' => [222 => 42, 333 => 43],
'r5' => [333 => 53],
'r6' => [333 => 63],
],
],
// test-case 7: some non-existent keys supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r1' => 12,
'r2' => 22,
'r3' => 32
],
333 => [
'r1' => 13,
'r2' => 23,
'r3' => 33
],
],
'keys_to_transpose' => ['r-1', 'r2', 'r5'],
],
'expected_output' => [
'r2' => [111 => 21, 222 => 22, 333 => 23],
],
],
// test-case 8: only non-existent keys supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r1' => 12,
'r2' => 22,
'r3' => 32
],
333 => [
'r1' => 13,
'r2' => 23,
'r3' => 33
],
],
'keys_to_transpose' => ['r-1', 'r5'],
],
'expected_output' => [],
],
// test-case 9: uneven inner keys and some non-existent keys supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r2' => 22,
'r3' => 32,
'r4' => 42
],
333 => [
'r3' => 33,
'r4' => 43,
'r5' => 53,
'r6' => 63
],
],
'keys_to_transpose' => ['r-1', 'r2', 'r3', 'r3.5', 'r4', 'r7',],
],
'expected_output' => [
'r2' => [111 => 21, 222 => 22,],
'r3' => [111 => 31, 222 => 32, 333 => 33],
'r4' => [222 => 42, 333 => 43,],
],
],
// test-case 10: uneven inner keys and only non-existent keys supplied
[
'inputs' => [
'data' => [
111 => [
'r1' => 11,
'r2' => 21,
'r3' => 31
],
222 => [
'r2' => 22,
'r3' => 32,
'r4' => 42
],
333 => [
'r3' => 33,
'r4' => 43,
'r5' => 53,
'r6' => 63
],
],
'keys_to_transpose' => ['r-1', 'r3.5', 'r7',],
],
'expected_output' => [],
],
// test-case 11: real test-case from Jumbo/EventStream/NonLogsResServiceabilityJumboPusher
[
'inputs' => [
'data' => [
"101" => [
"5900009" => 0,
"18206231" => 0,
"18878036" => 0
],
"103" => [
"5900009" => 0,
"18206231" => 0,
"18878036" => 0
],
"140" => [
"5900009" => 1,
"18206231" => 1,
"18878036" => 1
],
"709" => [
"5900009" => 1,
"18206231" => 1,
"18878036" => 1
],
"710" => [
"5900009" => 1,
"18206231" => 1,
"18878036" => 1
],
"711" => [
"5900009" => 0,
"18206231" => 1,
"18878036" => 1
],
],
'keys_to_transpose' => null,
],
'expected_output' => [
"5900009" => [
"101" => 0,
"103" => 0,
"140" => 1,
"709" => 1,
"710" => 1,
"711" => 0,
],
"18206231" => [
"101" => 0,
"103" => 0,
"140" => 1,
"709" => 1,
"710" => 1,
"711" => 1,
],
"18878036" => [
"101" => 0,
"103" => 0,
"140" => 1,
"709" => 1,
"710" => 1,
"711" => 1,
],
],
],
];
$expected_outputs = array_column($test_cases, 'expected_output');
$computed_outputs = array_map(
static function (array $inputs): array {
$arr = ArrayUtils::transpose2DArray(
$inputs['data'] ?? [],
$inputs['keys_to_transpose'] ?? []
);
return $arr;
},
array_column($test_cases, 'inputs')
);
foreach (array_map(null, $expected_outputs, $computed_outputs) as $idx => $zipped_arrs) {
list($expected_output, $computed_output) = $zipped_arrs;
$this->assertEquals($expected_output, $computed_output);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment