In $data
I have the following values:
Result
[{"id":2,"author":"example@mail.com","id_orders":502}, {"id":2,"author":"example@mail.com","id_orders":503}, {"id":2,"author":"example@mail.com","id_orders":505}, {"id":3,"author":"second-example@mail.com","id_orders":502}, {"id":3,"author":"second-example@mail.com","id_orders":503}, {"id":3,"author":"second-example@mail.com","id_orders":505}, {"id":4,"author":"third-example@mail.com","id_orders":502}, {"id":4,"author":"third-example@mail.com","id_orders":503}, {"id":4,"author":"third-example@mail.com","id_orders":505}]
I want unique results for id
and id_orders
. I want 3 out of these 9 results. I have tried this, but it helps on one id_orders
condition.
PHP code
$result = json_decode($data, true); $unique_array = []; foreach($result as $element) { $hash = $element['id_orders']; $unique_array[$hash] = $element; } $data = array_values($unique_array);
Do you know how it can be different to make it work for two?
Advertisement
Answer
You can do it by keeping track of the values that were already used. Disclaimer: this solution will only produce a clear result for cases where the number of unique values for both criteria is the same.
$uniqueArray = []; $usedValues = [ 'id' => [], 'id_orders' => [], ]; foreach ($result as $element) { if (!in_array($element['id'], $usedValues['id']) && !in_array($element['id_orders'], $usedValues['id_orders'])) { $uniqueArray[] = $element; $usedValues['id'][] = $element['id']; $usedValues['id_orders'][] = $element['id_orders']; } }
Basically, what’s happening here is that we’re using $usedValues
to store all the unique values we’ve already used and comparing against it using in_array
. When we iterate through the objects, any object with an id
or id_orders
that has already been used will be skipped. The pairings will be done in order of appearance in the array.
I’ve gone an extra mile to try and make this code a bit more generic:
* Finds elements with a unique combination of values under given keys. * * Assumes all elements in the array are arrays themselves and that the * subarrays have the same structure. Assumes subarray elements are not * objects (uses strict comparison). */ function uniqueCombination(array $arrayToFilter, array $keysToFilterOn): array { if (empty($arrayToFilter) || empty($keysToFilterOn)) { throw new InvalidArgumentException( 'Parameters of uniqueCombination must not be empty arrays' ); } // get the keys from the first element; others are assumed to be the same $keysOfElements = array_keys(reset($arrayToFilter)); $keysPresentInBoth = array_intersect($keysToFilterOn, $keysOfElements); // no point in running the algorithm if none of the keys are // actually found in our array elements if (empty($keysPresentInBoth)) { return []; } $result = []; $usedValues = array_combine( $keysPresentInBoth, array_fill(0, count($keysPresentInBoth), []) ); foreach ($arrayToFilter as $element) { if (!isAlreadyUsed($usedValues, $element)) { $result[] = $element; foreach ($keysPresentInBoth as $keyToUse) { $usedValues[$keyToUse][] = $element[$keyToUse]; } } } return $result; } function isAlreadyUsed(array $usedValues, array $element): bool { foreach ($usedValues as $usedKey => $usedValue) { if (in_array($element[$usedKey], $usedValue)) { return true; } } return false; }
In its core, this is the same algorithm, but made dynamic. It allows a variable number of keys to filter on (that’s why they’re passed separately as an argument), so the $usedValues
array is created dynamically (using the first element’s keys as its own keys, filled with empty arrays) and all the keys must be compared in loops (hence the separate function to check if an element’s value had already been used).
It could probably be tweaked here or there as I haven’t tested it thoroughly, but should provide satisfactory results for most structures.