# Sorting and slicing array, based on another “weight” array?

Posted on

Problem

I have the following input array:

``````\$arr = array(
'water' => array(0, 1, 2, 3, 4, 5),
'apple' => array(1, 5, 3, 4, 0, 0),
'beer' => array(0, 0, 0, 0, 0, 0)
);
``````

Now I want to know which element has largest sum, so I have the following code:

``````\$arrWeight = array();

foreach (\$arr as \$key => \$value)
{
\$arrWeight[\$key] = is_array(\$value) ? array_sum(\$value) : 0;
}
``````

Then, I want to sort array based on weight. That’s easy.

``````arsort(\$arrWeight);
``````

Now, I want to slice the original array (`\$arr`) based on generated weights and limit.

``````\$arrAllowedKeys = array_slice(\$arrWeight, 0, self::METRICS_BREAKDOWN_LIMIT, true);

foreach (\$arr as \$key => \$value)
{
if (!array_key_exists(\$key, \$arrAllowedKeys))
{
unset(\$arr[\$key]);
}
}
``````

For example, if I have `self::METRICS_BREAKDOWN_LIMIT` equals to 2, I’d expect the following output:

``````\$arr = array(
'water' => array(0, 1, 2, 3, 4, 5),
'apple' => array(1, 5, 3, 4, 0, 0),
);
``````

So, basically, the above solution is working, but I’m looking for better approach.

Solution

The only improvement I see is to not preserve the ordering when you slice.

``````\$arrAllowedKeys = array_slice(\$arrWeight, 0, self::METRICS_BREAKDOWN_LIMIT, true);
``````

This is all you need:

``````\$arrAllowedKeys = array_slice(\$arrWeight, 0, self::METRICS_BREAKDOWN_LIMIT);
``````

The title of your question is “sorting and slicing array”,
but the input array is not sorted.
During the process, only the calculated array of weights is sorted,
the original is not.

If the input is this:

``````\$arr = array(
'apple' => array(1, 5, 3, 4, 0, 0),
'water' => array(0, 1, 2, 3, 4, 5),
'beer' => array(0, 0, 0, 0, 0, 0)
);
``````

Then the output will be:

``````\$arr = array(
'apple' => array(1, 5, 3, 4, 0, 0),
'water' => array(0, 1, 2, 3, 4, 5)
);
``````

In other words, the input array is sliced, but not sorted.

What happens to elements with the same weight, around the `self::METRICS_BREAKDOWN_LIMIT` index? For example, if `self::METRICS_BREAKDOWN_LIMIT = 1`, and the initial array is this:

``````\$arr = array(
'water' => array(1, 2, 3),
'apple' => array(1, 2, 3),
'beer' => array(1, 2, 3)
);
``````

Which record will be in the result? It depends on whether `arsort` is using a stable sorting algorithm (not so easy to find in the docs of `arsort`). If `arsort` is stable, insertion order will be preserved, and the answer will be `water`. Otherwise it can be any of these. It would be a good idea to add a comment in the code about this.

One you have the `\$arrAllowedKeys`, you can simply use `array_intersect_key` to get the final array, instead of looping through `\$arr`.

``````array_intersect_key(\$arr, array_fill_keys(\$arrAllowedKeys, null));
``````

You might use `array_map` rather than the initial `foreach` loop.

``````\$arrWeight = array_map(\$arr, function(\$value) {
return is_array(\$value) ? array_sum(\$value) : 0;
});
``````

``````\$arr = [
'water' => [0, 1, 2, 3, 4, 5],
'apple' => [1, 5, 3, 4, 0, 0],
'beer' => [0, 0, 0, 0, 0, 0]
];

\$arrWeight = array_map(\$arr, function(\$value) {
return is_array(\$value) ? array_sum(\$value) : 0;
});

arsort(\$arrWeight);

\$arrAllowedKeys = array_slice(\$arrWeight, 0, self::METRICS_BREAKDOWN_LIMIT, true);

\$arr = array_intersect_key(\$arr, array_fill_keys(\$arrAllowedKeys, null));
``````