1

I want to sort an array with numbers in natural order, so numbers with a bigger value come after smaller ones like this:

php > $numbers = array('10 Apple', '2 Grapes', '3 Apples', '3.2 Apples', '3.1 Apples', '3.3 Apples', '3.10 Apples', '3.11 Apples', 'Lots of Apples');
php > natsort($numbers);

This is what I get as result, which is not the result I need, decimals are not threated correctly.

php > print_r($numbers);
Array
(
    [1] => 2 Grapes
    [2] => 3 Apples
    [4] => 3.1 Apples
    [3] => 3.2 Apples
    [5] => 3.3 Apples
    [6] => 3.10 Apples
    [7] => 3.11 Apples
    [0] => 10 Apple
    [8] => Lots of Apples
)

There is already a similar question Array Sorting in php for decimal values but no fitting answer was found there.

The expected output for a property sorting would be

Array
(
    [1] => 2 Grapes
    [2] => 3 Apples
    [4] => 3.1 Apples
    [6] => 3.10 Apples
    [7] => 3.11 Apples
    [3] => 3.2 Apples
    [5] => 3.3 Apples
    [0] => 10 Apple
    [8] => Lots of Apples
)

So I would kind of expect natsort() do to exactly that, but it looks like it is buggy and I have to implement a similar logic by my self? Is that correct?

One solution I am thinking of is to reformat the numbers somehow to fixed precision and hope that natsort() works then, but I am wondering if there are easier solutions or PHP-builtin ones.

I tried https://github.com/awssat/numbered-string-order which is very interesting but also does not support decimals.

ggorlen
  • 33,459
  • 6
  • 59
  • 67
Alex
  • 29,618
  • 13
  • 100
  • 157
  • 1
    Use `usort()` with a comparison function that extracts the numbers from the strings and compares them. – Barmar Jun 23 '21 at 14:56
  • If all the strings end with `Apples`, why don't you just remove that and sort it numerically? – Barmar Jun 23 '21 at 14:56
  • 3
    StackOverflow is not a free coding service. You're expected to [try to solve the problem first](https://meta.stackoverflow.com/questions/261592/how-much-research-effort-is-expected-of-stack-overflow-users). Please update your question to show what you have already tried in a [mcve]. For further information, please see [ask], and take the [tour] :) – Barmar Jun 23 '21 at 14:57
  • Thanks all, I improve the question a bit – Alex Jun 23 '21 at 15:00

1 Answers1

1

I'm not 100% sure of your specification so please test this, but strnatcmp seems like it can be used to run a natsort variant in usort. If both strings passed to the comparator start with float numbers, then cast them to floats and use the spaceship, otherwise, default to strnatcmp.

<?php

$numbers = ['10 Apple', '2 Grapes', '3 Apples', '3.2 Apples', '3.1 Apples', '3.3 Apples', '3.10 Apples', '3.11 Apples', 'Lots of Apples'];

usort($numbers, function ($a, $b) {
    if (preg_match("~^\d*\.\d+\b~", $a, $m)) {
        $aa = (float)$m[0];

        if (preg_match("~^\d*\.\d+\b~", $b, $m)) {
            $bb = (float)$m[0];
            return $aa <=> $bb;
        }
    }

    return strnatcmp($a, $b);
});
print_r($numbers);
ggorlen
  • 33,459
  • 6
  • 59
  • 67