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
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:
RREEphp-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 と同じになります。
而且需要注意的是,如果两个值的类型不同,那么这样的比较是毫无意义的,也可能会产生意想不到的结果。
最后,我们在欲排序的值最后添加了一个字符 "s",使它们不再是”纯数字“形式的字符串:
<?php $arr = [ "nonce_str" => "441469s", "timestamp" => "1464334314s" ]; asort($arr); var_dump($arr);?>
最后排序的结果变成了:
array(2) { ["timestamp"]=> string(11) "1464334314s" ["nonce_str"]=> string(7) "441469s"}
这才是我们想要的结果。