Last active
November 12, 2020 21:31
-
-
Save y2k-shubham/52f6f8e0e4ae566607ee6aec658a441c to your computer and use it in GitHub Desktop.
PHP utility functions for array manipulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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