I have an array like this:
$array = [
['id' => 1, 'content' => 'value 1'],
['id' => 2, 'content' => 'value 2'],
['id' => 3, 'content' => 'value 3'],
];
I have another array containing the actual order of id
s in which $array
should be sorted :
$order = [3, 1, 2];
Which means, the result should be this:
$sorted = [
0 => ['id' => 3, 'content' => 'value 3'],
1 => ['id' => 1, 'content' => 'value 1'],
2 => ['id' => 2, 'content' => 'value 2'],
];
The final requirement is, that the array should be printed using foreach
:
foreach($sorted as $value) {
print $value['content'];
}
I came up with this solution:
function orderArray(array $array, array $order) {
$sorted = [];
foreach($array as $value) {
$id = $value['id'];
$index = array_search($id, $order);
$sorted[$index] = $value;
}
ksort($sorted);
return $sorted;
}
This works fine. However, I wonder whether this is efficent, especially as I have to call ksort
to make the array be printed correctly by foreach
.
Can this be optimized? Maybe there's also a more elegant solution to the problem.
It's a given, that all id
s and only those are in $order
- so no need to test for that.
5 Answers 5
Since your ordering array contains all of the ids in your data array, you can prepare the two arrays to have relatable keys then just call array_replace()
. This means a fully "functional style" instead of classic foreach loops (if you like them). The foreach() loops often outperform function calls due to having less overhead, but with my snippet you won't need to declare the output variable before return
ing it.
array_flip()
provides relatable keys for$order
.array_column()
provides relatable keys for$array
without mutating the values.array_replace()
simply overwrites the$order
values with the$array
data (all sorted now).array_values()
re-indexes the array (removes the temporary, relatable keys).
Code:
function orderArray(array $array, array $order): array {
return array_values(
array_replace(
array_flip($order),
array_column($array, null, 'id')
)
);
}
I find this declarative snippet to be more elegant and easier to read since it doesn't have nested square braces to try to decipher nor any newly declared variables. Again, performance is not the benefit here, but with your sample data being so small, it is unlikely that a user will notice and difference between any of the snippets on this page.
I think this would be the cleanest solution. Similar to @YourCommonSense answer, but slightly inverted as ordered index is already there - only values missing.
$sorted = array_flip($order);
foreach ($array as $value) {
$id = $value['id'];
$sorted[$id] = $value;
}
I would say that array_search() is also a concern.
So I would create an index array to make a correspondence between the id and the position and then just create a new array, like this
$sorted = [];
$index = array_flip(array_column($array, 'id'));
foreach ($order as $id) {
$sorted[] = $array[$index[$id]];
}
If (and only if) the $array that you start with is always ordered from 1 to n (as you said in the comments), then you don't have to do any sorting on your part. It's simply a matter of accessing your array. I came up with the following solution:
$array = [
['id' => 1, 'content' => 'value 1'],
['id' => 2, 'content' => 'value 2'],
['id' => 3, 'content' => 'value 3'],
['id' => 4, 'content' => 'value 4'],
];
$order = [3, 1, 2, 4];
$sorted = [];
$i = 0;
foreach ($array as $value) {
$sorted[$i] = $array[$order[$i]-1];
$i++;
}
However, I'm afraid that I've misunderstood your question if this answers it. The code produces identical output compared to the function in the question, provided that the $array
is sorted. Otherwise it breaks horribly.
If this doesn't fit your use case, write a comment and I'll try to see what's missing.
-
1\$\begingroup\$ you can already have $i from foreach \$\endgroup\$Your Common Sense– Your Common Sense2018年08月31日 15:14:37 +00:00Commented Aug 31, 2018 at 15:14
Assuming you aren't going to use the sorted array you are creating for something else just use the $order
array to echo the values directly.
$array = [
['id' => 1, 'content' => 'value 1'],
['id' => 2, 'content' => 'value 2'],
['id' => 3, 'content' => 'value 3'],
];
$order = [3, 1, 2];
foreach ($order as $point) {
echo $array[$point - 1]['content'] . PHP_EOL;
}
-
\$\begingroup\$ This is not a code an experienced programmer would consider acceptable. Ids are not guaranteed to be in order. Such an assumption would lead to innumerable problems. \$\endgroup\$Your Common Sense– Your Common Sense2018年08月31日 11:24:08 +00:00Commented Aug 31, 2018 at 11:24
-
\$\begingroup\$ Won't argue with you at all but based on his parameters and his comments it will work as he desires. Fragile as can be and not the best solution at all. \$\endgroup\$Dave– Dave2018年08月31日 11:39:12 +00:00Commented Aug 31, 2018 at 11:39
$array
always correspond to the numbers in the$order
array? \$\endgroup\$$order
contains allid
s from$array
and no other elements. I tried to say that in the footnote - hopefully it's clearer now. \$\endgroup\$$array
, theid
s are in order. Is this always the case? Or is the input array not ordered? \$\endgroup\$1
ton
in$array
. \$\endgroup\$