php 自定义排序平面数组由负数,大写字母,符号,小写字母,然后正数

xwbd5t1u  于 2023-04-28  发布在  PHP
关注(0)|答案(2)|浏览(146)

我绞尽脑汁想解决这个难题。
PHP默认的sort函数不能提供解决方案,但使用usort也不容易。
这就是我想解决的问题。我按照这个顺序创建了一个数组:

$data = array( '_', '@', ...range(-10, 10), ...range('A', 'Z'), ...range('a', 'z') )

现在我想使用usort对这个数组进行排序,这样:

  • negative数字先出现,
  • uppercase字母
  • _@字符如下
  • lowercase字母如下
  • 最后positive数字结束订单

有点像:

/*
array(

  "-10",
  "-9",...

  "A",
  "B",...

  "_",
  "@", // @ may come first

  "a",
  "b",...

  "1",
  "2"...

) */

有什么方法可以解决这个问题吗?

我尝试了什么?

usort($data, function($a,$b) {
    if( is_numeric($a) && (int)$a < 0 ) return -1; // take negative number to start
    else {
        if( !is_numeric($a) ) {
            if( is_numeric($b) && (int)$b > 0 ) return -1;
            else return $b < $a ? 1 : 0;
        } else return 1; // take positive number to end
    }
});
cczfrluj

cczfrluj1#

把它看作是一个等级制度。你有5个不重叠的“类”要排序:负数、大写、符号、小写、正数。因此,首先确定类排序,如果两个项的类相同,则比较它们的值。

class MySorter {
    const CLASS_NUM_NEG = 0;
    const CLASS_STR_UC  = 1;
    const CLASS_STR_OT  = 2;
    const CLASS_STR_LC  = 3;
    const CLASS_NUM_POS = 4;
    
    static function get_class($item) {
        switch(gettype($item)) {
            case 'integer':
            case 'float':
                return ($item < 0) ? self::CLASS_NUM_NEG : self::CLASS_NUM_POS;
            case 'string':
                $ord = ord($item[0]);
                // note: below ord() calls are illustrative, and 
                // should be replaced with non-computed values to
                // avoid repetitive work.
                if( $ord >= ord('A') && $ord <= ord('Z')) {
                    return self::CLASS_STR_UC;
                } else if( $ord >= ord('a') && $ord <= ord('z')) {
                    return self::CLASS_STR_LC;
                } else {
                    return self::CLASS_STR_OT;
                }
            default:
                throw new \Exception("Unhandled type: " . gettype($item));
        }
    }
    
    static function compare($a, $b) {
        $res = self::get_class($a) <=> self::get_class($b);
        if( $res !== 0 ) { return $res; }
        return $a <=> $b;
    }
}

$data = [ '_', '@', ...range(-10, 10), ...range('A', 'Z'), ...range('a', 'z') ];

usort($data, ['MySorter', 'compare']);

echo json_encode($data);

旁白:类可以作为替代名称空间来包含相关的函数和变量,这样你就可以比完全内联或在本地/全局名称空间中转储东西更好地框出逻辑。
输出:

[-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","@","_","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",0,1,2,3,4,5,6,7,8,9,10]
yks3o0rb

yks3o0rb2#

也许使用正则表达式对于一些开发人员来说会更难阅读/维护,我没有费心比较性能,但它确实提供了一些很好的代码简洁性。在正则表达式模式中对一系列可选的捕获组进行排序。正则表达式引擎将尝试满足最早出现的子模式,而剩余的捕获组甚至不会在matches数组($m[])中表示。然后,因为PHP在比较实际数据之前会先按数组的计数排序,所以$m中元素最少的条目将首先按array_multisort()排序。当然,这可以通过使用u模式修饰符来增强以尊重多字节字符串。
代码:(Demo

$m = [];
foreach ($data as $v) {
    preg_match('/(-\d+)?([A-Z]+)?([^A-Za-z0-9]+)?([a-z]+)?(\d+)?/', $v, $m[]);
    //                                                     ^^^- positive integers
    //                                            ^^^^^^- lowercase letters
    //                            ^^^^^^^^^^^^^- non-letters, non-numbers
    //                   ^^^^^^- uppercase letters
    //            ^^^^- negative integers
}
array_multisort($m, $data);
var_export($data);

更直观和易于扩展/维护的是使用具有速记三进制和宇宙飞船操作符比较的回退比较,直到适合常规排序。
代码:(Demo)(或Demo

usort(
    $data,
    fn($a, $b) => ($b < 0 <=> $a < 0)                                           // prioritize negatives
                  ?: (ctype_upper((string) $b) <=> ctype_upper((string) $a))    // prioritize uppercase letters
                  ?: (is_int($a) <=> is_int($b))                                // deprioritize integers
                  ?: ($a <=> $b)                                                // sort normally
);
var_export($data);

或者,如果性能是一个问题,通过准备计算数组来减少所需函数调用的总数,然后调用array_multisort()。(Demo

$negatives = [];
$uppers = [];
$integers = [];
foreach ($data as $v) {
    $negatives[] = $v < 0;
    $uppers[] = ctype_upper((string) $v);
    $integers[] = is_int($v);
}

array_multisort(
    $negatives,
    SORT_DESC,
    $uppers,
    SORT_DESC,
    $integers,
    $data
);
var_export($data);

相关:

相关问题