多字段排序 -- PHP版

上期單機(jī)redis平穩(wěn)重啟的我的解決辦法:
用不同的端口來搭建一個(gè)cluster,這樣就可以不中斷服務(wù)重啟了,而且,管理多塊小內(nèi)存可能比管理一大塊內(nèi)存要好。


閑來有空,說一下自己寫的一個(gè)多字段排序算法。
之前有一個(gè)需求是每日更新游戲排行榜,需要把近7天新增的游戲評(píng)價(jià)集合,計(jì)算平均分,按平均分倒序排列,平均分相等的按照游戲發(fā)行時(shí)間倒序來排。

數(shù)據(jù)庫(kù)設(shè)計(jì)的時(shí)候是按照游戲id進(jìn)行hash,對(duì)10取模獲取游戲評(píng)價(jià)表名的。然而把數(shù)據(jù)集合起來之后,需要對(duì)數(shù)據(jù)進(jìn)行重新排序,這里就不能用mysql的order by了。(其實(shí)也可以的,新建一個(gè)臨時(shí)表來查詢,但是估計(jì)沒人想用這么傻的方法)

既然這樣,就需要自己手寫一個(gè)多字段排序的算法來解決這個(gè)問題了。(先說一下結(jié)果,我寫完之后,發(fā)現(xiàn)php有現(xiàn)成的function可以用,array_multisort,看官們可以去查一下手冊(cè)看用法,但是我自己寫的這個(gè)還有另外一個(gè)用處,就是對(duì)不進(jìn)行排序的字段可以一并保留輸出。但從性能來說,肯定是php原生的function會(huì)高得多,所以要看情況來使用)

首先來明確一下思路,對(duì)一個(gè) list 進(jìn)行排序(這里我用 list,因?yàn)?set 沒有重復(fù)值,list更符合大多數(shù)情況),排序有順序和倒序。list 中 n 個(gè)元素前面 j 個(gè)值都相同,則比較下一級(jí)字段,如果所有字段的值都相等,則這 n 個(gè)元素可以視為相同,則只要它們的排序是相連的即可。另外,如果某兩個(gè)元素全部字段都相等,它們可以互換位置,也可以不換位置。如果換位置,這個(gè)排序就稱為不穩(wěn)定的,反之就是穩(wěn)定的。
ps:我寫的這個(gè)算法是不穩(wěn)定的。。。o(╥﹏╥)o

做法:建立一個(gè)新的空 list,從輸入的 list 取出一個(gè)(頭尾都可)這里稱為tmp,按順序去跟新的 list 中的元素比較(順序搜索法),如果搜索到一個(gè)元素是,按照當(dāng)前排序規(guī)則是可以排在當(dāng)前元素的前面的,或者兩元素相等,則讓tmp占據(jù)新 list 的當(dāng)前位置,當(dāng)前元素及其后面的元素依次往后移動(dòng)(插入排序)。到最后如果搜索不到符合條件的元素,則讓tmp直接進(jìn)入隊(duì)尾。說的有點(diǎn)多,來看代碼。talk is cheap,show me the code。

/**
 * [multiSortArray 數(shù)組多字段排序(算法為順序搜索和插入排序,是不穩(wěn)定的排序,但可以維持其他不排序字段不變,時(shí)間復(fù)雜度大概是O(n2),空間復(fù)雜度不會(huì)算),不關(guān)心非排序字段可用PHP原生函數(shù):array_multisort ()]
 * @author [KAEL] <[<email address>]>
 * @param  [Array]  $arr    [原數(shù)組]
 * @param  [Array]  $fields [排序字段]
 * @param  [Array]  $sorts   [排序規(guī)則,正序傳SORT_ASC,倒序傳SORT_DESC]
 * @return [type]         [description]
 */
public static function multiSortArray(Array $arr, Array $fields, Array $sorts) {
// 這里的$sorts的長(zhǎng)度可以$fields少,不足的按照最后一個(gè)補(bǔ)齊,讓調(diào)用者可以稍微偷點(diǎn)懶

// 以下代碼都是進(jìn)行參數(shù)驗(yàn)證的代碼,可以忽略不看,是為了程序的魯棒性-----------------------

    if (empty($arr) || empty($fields) || empty($sorts)) {
        return false;
    }
    // 二維數(shù)組
    $arr = array_values($arr);
    if (!is_array($arr[0])) {
        return false;
    }
    $fields = array_values($fields);
    $sorts = array_values($sorts);

    $tmp = array_unique($sorts);
    $sortType = array(SORT_ASC, SORT_DESC);
    if ((!in_array($tmp[0], $sortType)) || (isset($tmp[1]) && !in_array($tmp[1], $sortType))) {
        return false;
    }

    $tmp = array_slice($sorts, -1, 1);
    $tmp = $tmp[0];// 最后一個(gè)排序
    $sorts = array_pad($sorts, count($fields), $tmp);// 填充排序數(shù)組
    foreach ($fields as $key => $value) {
        if (!is_string($value) || !isset($arr[0][$value])) {
            return false;
        }
    }

    $fcount = count($fields);
    $list = array();
    $list[] = array_shift($arr);

// 以上代碼都是進(jìn)行參數(shù)驗(yàn)證的代碼,可以忽略不看,是為了程序的魯棒性-----------------------

    while ($tmp = array_shift($arr)) {
        $count = count($list);
        $flag = false;// 一個(gè)標(biāo)記是否有進(jìn)行插入排序的flag
        foreach ($list as $key => $value) {
            if (self::recurseCompare($tmp, $value, $fields, $sorts, 0)) {
                        // 匹配成功就進(jìn)行插入排序
                for ($i = $count; $i > $key; --$i) {
                    $list[$i] = $list[$i-1];
                }
                $list[$key] = $tmp;
                $flag = true;
                break;
            }
        }
        if (!$flag) {
                // 插入隊(duì)尾
            array_push($list, $tmp);
        }
    }
    return $list;
}

以下是字段比較的遞歸算法:

        /**
         * [recurseCompare 多字段排序遞歸比較]
         * @param  [Array]   $var1   [變量1]
         * @param  [Array]   $var2   [變量2]
         * @param  [Array]   $fields [字段數(shù)組]
         * @param  [Array]   $sorts  [排序數(shù)組]
         * @param  [integer] $index  [比較字段索引]
         * @return [type]          [description]
         */
        private static function recurseCompare(Array $var1, Array $var2, Array $fields, Array $sorts, $index = 0) {
            $index = intval($index);
            $key = $fields[$index];
            $count = count($fields);
            if (!is_numeric($var1[$key]) && is_string($var1[$key])) {
                // 字符串排序,英文的,中文別想了,大兄dei
                $res = StrUtil::dictCompare($var1[$key], $var2[$key]);
                // -1表示 val1 比 val2 小,0表示相等,1表示 val1 的比 val2 要大
                if ($res === 0 && $key == $count - 1) {
                // 當(dāng)前字段相等且已經(jīng)沒有下一字段可供比較,返回true
                // 這里可以返回其他值,供調(diào)用方判斷,減少一位元素的移動(dòng),以提高性能,變成穩(wěn)定的排序
                    return true;
                }elseif (($sorts[$index] == SORT_ASC && $res == 1) || ($sorts[$index] == SORT_DESC && $res == -1)) {
                    return false;
                }elseif (($sorts[$index] == SORT_ASC && $res == -1) || ($sorts[$index] == SORT_DESC && $res == 1)) {
                    return true;
                }elseif ($res === 0 && $key < $count - 1) {
                    // 當(dāng)前字段相等則比較下一字段
                    return self::recurseCompare($var1, $var2, $fields, $sorts, ++$index);
                }
            }
            // 數(shù)字排序
            if ($key == $count - 1 && $var1[$key] == $var2[$key]) {
                // 與上面同理
                return true;
            }elseif ($sorts[$index] == SORT_ASC && $var1[$key] > $var2[$key]) {
                return false;
            }elseif ($sorts[$index] == SORT_DESC && $var1[$key] < $var2[$key]) {
                return false;
            }elseif ($sorts[$index] == SORT_ASC && $var1[$key] < $var2[$key]) {
                return true;
            }elseif ($sorts[$index] == SORT_DESC && $var1[$key] > $var2[$key]) {
                return true;
            }elseif ($count - 1 > $index && $var1[$key] == $var2[$key]) {
                // 與上面同理
                return self::recurseCompare($var1, $var2, $fields, $sorts, ++$index);
            }
        }

下面送上字典排序方法(實(shí)際上就是比較ASCII碼,想到更好方法的小伙伴可以在下面留言):

        /**
         * [dictCompare 比較兩個(gè)字符串的字典順序]
         * @param  [string] $str1 [字符串1]
         * @param  [string] $str2 [字符串2]
         * @return [type]       [description]
         */
        public static function dictCompare($str1, $str2) {
            if (!is_string($str1) || !is_string($str2)) {
                return false;
            }
            if ($str1 == $str2) {
                return 0;
            }
            $len1 = strlen($str1);
            $len2 = strlen($str2);
            $maxlen = $len1>$len2?$len1:$len2;
            for ($i = 0; $i < $maxlen; ++$i) {
                if (!isset($str1[$i])) {
                    return -1;
                }elseif (!isset($str2[$i])) {
                    return 1;
                }
                $asc1 = ord($str1[$i]);
                $asc2 = ord($str2[$i]);
                if ($asc1 > $asc2) {
                    return 1;
                }elseif ($asc1 < $asc2) {
                    return -1;
                }
            }
        }

這期的代碼和文字有點(diǎn)多,但是算法的時(shí)間復(fù)雜度大O是n2,空間復(fù)雜度估計(jì)不是很高吧,適合數(shù)據(jù)量不大的情況,把順序搜索法換成二分法搜索估計(jì)時(shí)間復(fù)雜度會(huì)降低不少,也能處理多一點(diǎn)的數(shù)據(jù)。另外如果字符串比較的方法能夠改進(jìn)一下的話,那這個(gè)算法就比較通用了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述:排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,818評(píng)論 0 15
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,297評(píng)論 0 52
  • 楔子 渭河岸邊矗立著一座這樣的城池,由肥沃卻飽經(jīng)風(fēng)霜蒼涼的黃土筑起的高高城墻上千瘡百孔,城樓上殘破的旌旗在迎風(fēng)飄揚(yáng)...
    絳冬閱讀 638評(píng)論 6 18
  • 1. JavaScript 定義了幾種數(shù)據(jù)類型? 哪些是原始類型?哪些是復(fù)雜類型?原始類型和復(fù)雜類型的區(qū)別是什么?...
    謹(jǐn)言_慎行閱讀 198評(píng)論 0 0
  • 路遙先生曾這樣說過:“只有永不遏制的奮斗,才能使青春之花即便是凋謝,也是壯麗的凋謝?!笔前?,青春是人生之王,一旦逝...
    木籽離閱讀 1,044評(píng)論 0 2

友情鏈接更多精彩內(nèi)容