PHP按strnatcmp和另一个特定关键字排序

  • I need to sort an multi dimensional array which looks like this down below (usort already applied).
  • I need to move '10000_M3' to the first key if an identifier '_M' was found (as natural sorting), but the order from 'first' sort should not be touched.
  • Extra: (Optional) If i have a description like '10000_0', it should be still in index 0
  • If the usort can be done in 1 step would be great
  • I take any solution (usort, foreach, ..)!!!

    usort($anArray, function ($a, $b) {
        return strnatcmp($a['description'], $b['description']);
    });
    
    $anArray = [
        // ? => [
        //     'description' => '10000_0'
        // ]
        0 => [
            'description' => '10000_D2'
        ],
        1 => [
            'description' => '10000_D3'
        ],
        2 => [
            'description' => '10000_M3'
        ],
        3 => [
            'description' => '10000_M4'
        ]
    ]
    

Result (Natural -> at first position = '_0' -> $ident = '_M' after '_0' if its exists):

    $result = [
        0 => [
            'description' => '10000_0'
        ]
        1 => [
            'description' => '10000_M3'
        ],
        2 => [
            'description' => '10000_M4'
        ]
        3 => [
            'description' => '10000_D2'
        ],
        4 => [
            'description' => '10000_D3'
        ],
    ]

You can modify your sort function to check for the special cases individually then fall back on a regular sorting method.

<?php
$inputArray = [
    0 => [
        'description' => '10000_D2'
    ],
    1 => [
        'description' => '10000_D3'
    ],
    2 => [
        'description' => '10000_M3'
    ],
    3 => [
        'description' => '10000_M4'
    ],
    4 => [
         'description' => '10000_0'
    ]
];

usort($inputArray, function ($a, $b) {
    // _0 first then _M* then alphabetic

    //assume only 1 value will be _0?
    if (preg_match('/_0$/', $a['description']) === 1){
        //"a" ends in _0
        return -1;
    }
    if (preg_match('/_0$/', $b['description']) === 1){
        //"b" ends in _0
        return 1;
    }

    if (
        preg_match('/_M\d*$/', $a['description']) === 1
        && preg_match('/_M\d*$/', $b['description']) === 1
    ){
        //both have "M" so sort them normally
        return strnatcmp($a['description'], $b['description']);
    }

    if (preg_match('/_M\d*$/', $a['description']) === 1){
        //only "a" has _M
        return -1;
    }

    if (preg_match('/_M\d*$/', $b['description']) === 1){
        //only "b" has _M
        return 1;
    }

    //neither side has _M or _0 so normal sorting
    return strnatcmp($a['description'], $b['description']);
});

echo print_r($inputArray, true);
?>

Output:

Array
(
    [0] => Array
        (
            [description] => 10000_0
        )

    [1] => Array
        (
            [description] => 10000_M3
        )

    [2] => Array
        (
            [description] => 10000_M4
        )

    [3] => Array
        (
            [description] => 10000_D2
        )

    [4] => Array
        (
            [description] => 10000_D3
        )

)

I'm assuming you will only have a single _0 value. If you could have multiple _0 values then you need to modify the above code to behave like the 3 _M "if" statements.

You can try this :

$anArray = [

    0 => [
        'description' => '10000_D2'
    ],
    1 => [
        'description' => '10000_D3'
    ],
    2 => [
        'description' => '10000_M3'
    ],
    3 => [
        'description' => '10000_M4'
    ]
,   4 => [
        'description' => '10000_0'
    ]
    ,   5 => [
        'description' => '10000_15'
    ]
    ,   6 => [
        'description' => '10000_789'
    ]
];
usort($anArray, function ($a, $b) {

    $tmpa=explode('_',$a['description']);
    $tmpb=explode('_',$b['description']);
        if(ctype_digit($tmpa[1])&&!ctype_digit($tmpb[1]))
            return -1;
        if(!ctype_digit($tmpa[1])&&ctype_digit($tmpb[1]))
            return 1;
        if($tmpa[1][0]==='M'&&$tmpb[1][0]!=='M')
            return -1;
        if($tmpa[1][0]!=='M'&&$tmpb[1][0]==='M')
            return 1;

        return strnatcmp($a['description'], $b['description']);
});

print_r($anArray);

and the output is:

Array
(
    [0] => Array
        (
            [description] => 10000_0
        )

    [1] => Array
        (
            [description] => 10000_15
        )

    [2] => Array
        (
            [description] => 10000_789
        )

    [3] => Array
        (
            [description] => 10000_M3
        )

    [4] => Array
        (
            [description] => 10000_M4
        )

    [5] => Array
        (
            [description] => 10000_D2
        )

    [6] => Array
        (
            [description] => 10000_D3
        )

)