PHPのSORT_REGULARとSORT_STRINGの違い

WBOY
リリース: 2016-06-23 13:04:27
オリジナル
2685 人が閲覧しました

asortの問題

PHPの基本関数asortを使用しているときに次の問題に遭遇しました:

<?php    $arr = [        "nonce_str" => "441469",        "timestamp" => "1464334314"    ];    asort($arr);    var_dump($arr);?>
ログイン後にコピー

ソートの結果は次のとおりです:

array(2) {    ["nonce_str"]=>    string(6) "441469"    ["timestamp"]=>    string(10) "1464334314"}
ログイン後にコピー

WTF?

そうじゃないのに、なぜソートされるのですか? 4 前1 の場合、文字列は文字列比較によってソートされるべきではないでしょうか?

php.net リファレンス ドキュメントを確認し、sort クラス関数の 2 番目のパラメーターが SORT_FLAG であることを確認してください:

SORT_REGULAR - 通常の比較単位 (型の変更なし)

SORT_NUMERIC - 単位は数値として比較されます

SORT_STRING - セルは文字列として比較されます

SORT_LOCALE_STRING - セルは現在のロケール設定に従って文字列として比較されます。これは setlocale() で変更できます。

SORT_NATURAL - natsort() と同様に、各セルの文字列を「自然な順序」で並べ替えます。 PHP 5.4.0 の新機能。

SORT_FLAG_CASE - SORT_STRING または SORT_NATURAL と (ビットごとの OR) を組み合わせて、大文字と小文字を区別せずに文字列を並べ替える機能。

デフォルトのソート方法はいわゆるSORT_REGULARのようです。しかし、「通常の比較単位」とは何を意味するのでしょうか? 「型を変更しない」とはどういう意味ですか? 型は変更されないので、2 つの文字列は文字列比較 strcmp の順序で配置される必要があります。つまり、文字列「1464334314」が「441469」の前にある必要があります。右!これらの疑問を持って、phpのソースコードを見つけました。

ソースコード分析

http://git.php.net/?p=php-src.git;a=blob_plain;f=ext/standard/array.c;hb=42be298b3020337653cfcbdd87698b90006b2197

php-src.git / ext/standard/array.c, 887:

/* {{{ proto bool asort(array &array_arg [, int sort_flags])   Sort an array and maintain index association */PHP_FUNCTION(asort){    zval *array;    zend_long sort_type = PHP_SORT_REGULAR;    compare_func_t cmp;    if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {        RETURN_FALSE;    }    cmp = php_get_data_compare_func(sort_type, 0);    if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {        RETURN_FALSE;    }    RETURN_TRUE;}/* }}} */
ログイン後にコピー

php_get_data_compare_funcにsort_typeを渡すことで得られる、特定の比較シーケンス制御用の関数ポインタがcmpであることがわかります。sort_typeはSORT_REGULAR / SORT_STRINGなどのマークです。

php-src.git/ext/standard/array.c, 632:

static compare_func_t php_get_data_compare_func(zend_long sort_type, int reverse) /* {{{ */{    switch (sort_type & ~PHP_SORT_FLAG_CASE) {        // ...        case PHP_SORT_STRING:            if (sort_type & PHP_SORT_FLAG_CASE) {                if (reverse) {                    return php_array_reverse_data_compare_string_case;                } else {                    return php_array_data_compare_string_case;                }            } else {                if (reverse) {                    return php_array_reverse_data_compare_string;                } else {                    return php_array_data_compare_string;                }            }            break;        // ...        case PHP_SORT_REGULAR:        default:            if (reverse) {                return php_array_reverse_data_compare;            } else {                return php_array_data_compare;            }            break;    }    return NULL;}/* }}} */
ログイン後にコピー

この関数では、SORT_REGULAR が比較に php_array_data_compare を使用し、SORT_STRING が比較に php_array_data_compare_string を使用していることがわかります。

php-src.git/ext/standard/array.c, 370:

/* Numbers are always smaller than strings int this function as it * anyway doesn't make much sense to compare two different data types. * This keeps it consistent and simple. * * This is not correct any more, depends on what compare_func is set to. */static int php_array_data_compare(const void *a, const void *b) /* {{{ */{    // ...    if (compare_function(&result, first, second) == FAILURE) {        return 0;    }    ZEND_ASSERT(Z_TYPE(result) == IS_LONG);    return Z_LVAL(result);}/* }}} */
ログイン後にコピー

php-src.git/ext/standard/array.c, 465:

static int php_array_data_compare_string(const void *a, const void *b) /* {{{ */{    // ...    return string_compare_function(first, second);}/* }}} */
ログイン後にコピー

これで、2 つの呼び出しチェーンが得られます:

SORT_REGULAR -> php_get_data_compare_func -> php_array_data_compare_function;

SORT_STRING -> php_array_data_compare_function;

SORT_REGULAR

最初の項目のcompare_functionから始めます:

http :// git.php.net/?p=php-src.git;a=blob_plain;f=Zend/zend_operators.c;hb=42be298b3020337653cfcbdd87698b90006b2197

php-src.git/Zend/zend_operators.c, 1818:

ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */{    // ...    while (1) {        switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {            // ...            case TYPE_PAIR(IS_STRING, IS_STRING):                if (Z_STR_P(op1) == Z_STR_P(op2)) {                    ZVAL_LONG(result, 0);                    return SUCCESS;                }                ZVAL_LONG(result, zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2)));                return SUCCESS;            // ...        }    }}/* }}} */
ログイン後にコピー

SORT_REGULAR -> php_get_data_compare_func -> php_array_data_compare -> zendi_smart_strcmp;

php-src.git/zend_operators.c、2681:

ここに zendi_smart_strcm があることがわかります。 p まず、この 2 つが一致するかどうかを判断します。比較される文字列は数値型です。そうである場合、「数値」のような文字列は整数または浮動小数点数として比較され、そうでない場合、文字列は zend_binary_strcmp と比較されます。

php-src.git/Zend/zend_operators.c, 2791:

ZEND_API zend_long ZEND_FASTCALL zendi_smart_strcmp(zend_string *s1, zend_string *s2) /* {{{ */{    int ret1, ret2;    int oflow1, oflow2;    zend_long lval1 = 0, lval2 = 0;    double dval1 = 0.0, dval2 = 0.0;    if ((ret1 = is_numeric_string_ex(s1->val, s1->len, &lval1, &dval1, 0, &oflow1)) &&        (ret2 = is_numeric_string_ex(s2->val, s2->len, &lval2, &dval2, 0, &oflow2))) {#if ZEND_ULONG_MAX == 0xFFFFFFFF        if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0. &&            ((oflow1 == 1 && dval1 > 9007199254740991. /*0x1FFFFFFFFFFFFF*/)            || (oflow1 == -1 && dval1 < -9007199254740991.))) {#else        if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0.) {#endif            /* both values are integers overflown to the same side, and the             * double comparison may have resulted in crucial accuracy lost */            goto string_cmp;        }        if ((ret1 == IS_DOUBLE) || (ret2 == IS_DOUBLE)) {            if (ret1 != IS_DOUBLE) {                if (oflow2) {                    /* 2nd operand is integer > LONG_MAX (oflow2==1) or < LONG_MIN (-1) */                    return -1 * oflow2;                }                dval1 = (double) lval1;            } else if (ret2 != IS_DOUBLE) {                if (oflow1) {                    return oflow1;                }                dval2 = (double) lval2;            } else if (dval1 == dval2 && !zend_finite(dval1)) {                /* Both values overflowed and have the same sign,                 * so a numeric comparison would be inaccurate */                goto string_cmp;            }            dval1 = dval1 - dval2;            return ZEND_NORMALIZE_BOOL(dval1);        } else { /* they both have to be long's */            return lval1 > lval2 ? 1 : (lval1 < lval2 ? -1 : 0);        }    } else {        int strcmp_ret;string_cmp:        strcmp_ret = zend_binary_strcmp(s1->val, s1->len, s2->val, s2->len);        return ZEND_NORMALIZE_BOOL(strcmp_ret);    }}/* }}} */
ログイン後にコピー

上記のコードは、文字列が「数値」の形式であるかどうかを判断する比較関数です。

SORT_STRING

次に、2 番目の呼び出しチェーンを見てみましょう: SORT_STRING ->get_data_compare_func -> php_array_data_compare_string -> 9:

RREE

見ることができます最後に、SORT_STRING は文字列比較に zend_binary_strcmp 関数も使用します。次のコードは zend_binary_strcmp の実装です:

php-src.git/Zend/zend_operators.c, 2539:

ZEND_API zend_uchar ZEND_FASTCALL _is_numeric_string_ex(const char *str, size_t length, zend_long *lval, double *dval, int allow_errors, int *oflow_info) /* {{{ */{    const char *ptr;    int digits = 0, dp_or_e = 0;    double local_dval = 0.0;    zend_uchar type;    zend_long tmp_lval = 0;    int neg = 0;    if (!length) {        return 0;    }    if (oflow_info != NULL) {        *oflow_info = 0;    }    /* Skip any whitespace     * This is much faster than the isspace() function */    while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {        str++;        length--;    }    ptr = str;    if (*ptr == '-') {        neg = 1;        ptr++;    } else if (*ptr == '+') {        ptr++;    }    if (ZEND_IS_DIGIT(*ptr)) {        /* Skip any leading 0s */        while (*ptr == '0') {            ptr++;        }        /* Count the number of digits. If a decimal point/exponent is found,         * it's a double. Otherwise, if there's a dval or no need to check for         * a full match, stop when there are too many digits for a long */        for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {check_digits:            if (ZEND_IS_DIGIT(*ptr)) {                tmp_lval = tmp_lval * 10 + (*ptr) - '0';                continue;            } else if (*ptr == '.' && dp_or_e < 1) {                goto process_double;            } else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2) {                const char *e = ptr + 1;                if (*e == '-' || *e == '+') {                    ptr = e++;                }                if (ZEND_IS_DIGIT(*e)) {                    goto process_double;                }            }            break;        }        if (digits >= MAX_LENGTH_OF_LONG) {            if (oflow_info != NULL) {                *oflow_info = *str == '-' ? -1 : 1;            }            dp_or_e = -1;            goto process_double;        }    } else if (*ptr == '.' && ZEND_IS_DIGIT(ptr[1])) {process_double:        type = IS_DOUBLE;        /* If there's a dval, do the conversion; else continue checking         * the digits if we need to check for a full match */        if (dval) {            local_dval = zend_strtod(str, &ptr);        } else if (allow_errors != 1 && dp_or_e != -1) {            dp_or_e = (*ptr++ == '.') ? 1 : 2;            goto check_digits;        }    } else {        return 0;    }    if (ptr != str + length) {        if (!allow_errors) {            return 0;        }        if (allow_errors == -1) {            zend_error(E_NOTICE, "A non well formed numeric value encountered");        }    }    if (type == IS_LONG) {        if (digits == MAX_LENGTH_OF_LONG - 1) {            int cmp = strcmp(&ptr[-digits], long_min_digits);            if (!(cmp < 0 || (cmp == 0 && *str == '-'))) {                if (dval) {                    *dval = zend_strtod(str, NULL);                }                if (oflow_info != NULL) {                    *oflow_info = *str == '-' ? -1 : 1;                }                return IS_DOUBLE;            }        }        if (lval) {            if (neg) {                tmp_lval = -tmp_lval;            }            *lval = tmp_lval;        }        return IS_LONG;    } else {        if (dval) {            *dval = local_dval;        }        return IS_DOUBLE;    }}/* }}} */
ログイン後にコピー

summary

上記の分析の後、SORT_STRING ソート メソッドの基礎となる実装が次のとおりであることがわかります。 C 言語の Memcmp は、2 つの文字列を前から後ろにバイトごとに比較し、バイトに違いがあれば終了してサイズを比較します。

SORT_REGULAR は、並べ替えるオブジェクトのタイプをインテリジェントに決定します。両方の文字列が「純粋な数値」形式の文字列である場合、文字列全体で表される 10 進整数と浮動小数点数のサイズを比較することによって並べ替えられます。 2 つの文字列が「純粋な数値」の形式ではない場合、SORT_STRING と同じになります。

したがって、文字列 strcmp メソッドを使用して前から後ろまでバイトごとに比較して並べ替える必要がある場合は、PHP の並べ替え関数を呼び出すときに必ず SORT_STRING フラグを使用してください。そうしないと、両方の文字列が「純粋な数値」形式の場合、次のようになります。それらが表す数値の大きさに従って並べ替えられます。

而且需要注意的是,如果两个值的类型不同,那么这样的比较是毫无意义的,也可能会产生意想不到的结果。

最后的测试

最后,我们在欲排序的值最后添加了一个字符 "s",使它们不再是”纯数字“形式的字符串:

<?php    $arr = [        "nonce_str" => "441469s",        "timestamp" => "1464334314s"    ];    asort($arr);    var_dump($arr);?>
ログイン後にコピー

最后排序的结果变成了:

array(2) {    ["timestamp"]=>    string(11) "1464334314s"    ["nonce_str"]=>    string(7) "441469s"}
ログイン後にコピー

这才是我们想要的结果。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート