Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Take an array of items and "unsort" them so that the items sharing similar properties are separated as much as possible. This code is not highly optimised and is not recommended for large data sets.
#!/usr/bin/php
<?php
// This is the input array.
$values = array(
array(
'title' => 'First item (1,1)',
'categoryID' => 1,
'storeID' => 1
),
array(
'title' => 'Second item (10,3)',
'categoryID' => 10,
'storeID' => 3
),
array(
'title' => 'Third item (50,12)',
'categoryID' => 50,
'storeID' => 12
),
array(
'title' => 'Fourth item (15,3)',
'categoryID' => 15,
'storeID' => 3
),
array(
'title' => 'Fifth item (49,1)',
'categoryID' => 49,
'storeID' => 1
),
array(
'title' => 'Sixth item (33,3)',
'categoryID' => 33,
'storeID' => 3
),
array(
'title' => 'Seventh item (12,3)',
'categoryID' => 12,
'storeID' => 3
),
array(
'title' => 'Eighth item (30,6)',
'categoryID' => 30,
'storeID' => 6
),
array(
'title' => 'Ninth item (29,6)',
'categoryID' => 29,
'storeID' => 6
),
array(
'title' => 'Tenth item (1,2)',
'categoryID' => 1,
'storeID' => 2
),
);
// These are the fields we care about, in the order in which we care about them (first is most important), mapped to
// empty arrays.
$counts = array(
'categoryID' => array(),
'storeID' => array()
);
// Get the number of items with each value of each field. This produces an array like:
// [
// categoryID => [ {categoryID value} => {count}, ... ],
// storeID => [ {storeID value} => {count} ]
// ]
foreach ($values as $item) {
foreach (array_keys($counts) as $field) {
$value = $item[$field];
if (!isset($counts[$field][$value])) {
$counts[$field][$value] = 0;
}
$counts[$field][$value]++;
}
}
// Initialise result and util variables.
$result = array();
$previousField = null;
$previousFieldValue = null;
// Process until there is nothing left to do.
while (!empty($counts)) {
$nextField = null;
$nextFieldValue = null;
$nextFieldCount = 0;
// Find the next field and value to include in the result; this is the field/value combination with the most hits.
foreach ($counts as $field => $fieldCounts) {
foreach ($fieldCounts as $value => $count) {
if (($field !== $previousField || $value !== $previousFieldValue) && $count > $nextFieldCount) {
$nextField = $field;
$nextFieldValue = $value;
$nextFieldCount = $count;
}
}
}
// Find the first item which matches the given field and value.
foreach ($values as $index => $item) {
if ($item[$nextField] === $nextFieldValue) {
// We've found a match, so add the item to the result, and remove it from the input.
$result[] = $item;
unset($values[$index]);
break; // that's right, break from a foreach()... so we don't process more than one matching item.
}
}
// Update the counts, and remove them when they reach zero.
$counts[$nextField][$nextFieldValue]--;
if ($counts[$nextField][$nextFieldValue] === 0) {
unset($counts[$nextField][$nextFieldValue]);
if (empty($counts[$nextField])) {
unset($counts[$nextField]);
}
}
// Remember the field and value we used, so we don't produce a duplicate next time.
// If we want to look at more than one previous item, this could be an array which we keep at a maximum length
// using array_pop() and array_shift().
$previousField = $nextField;
$previousFieldValue = $nextFieldValue;
}
// Show me the money...
print_r($result);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment