你所瞭解的array_diff_uassoc 真的是你瞭解的那樣嗎?

如果讓你用一句話描述PHP函數array_diff_uassoc,也許你開口就來了,就是同時比較兩個或多個函數,並返回在第一個函數出現且沒有在其他函數出現的鍵值同時相同的數據。

最近看到一個很有意思的問題,問的是關於array_diff_uassoc執行閱讀這個問題才明白對這個函數的誤解有多深。

下面是問題的簡化版本:


function comparekey($a,$b){
    return 0;
}

$array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4];
$array2 = ['a'=>2,'d'=>4,'e'=>6];
$res = array_diff_uassoc($array1,$array2,'comparekey');
var_dump($res);

爲什麼結果是

['a'=>1,'c'=>3,'d'=>4];

按正常邏輯,array_diff_uassoc 返回key不一樣,且值不一樣的數組數據。自定義比較函數返回0則認爲key值一樣。所以正常邏輯應該返回的是

['a'=>1,'b'=>2,'c'=>3]

你瞭解的真的對嗎?

1.自定義函數比較的是兩個數組的鍵嗎?

其實,說實話,一開始我也是這麼認爲的。直到我在自定義函數中分別輸出a,b,看到那奇葩的輸出內容才覺得,那個比較函數沒那麼簡單。

爲了方便看出內容,使用下面的數組替代問題中的數組內容

function comparekey($a,$b){
    echo $a.'-'.$b;
    return 0;
}

$array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4];
$array2 = ['e'=>'2','f'=>5,'g'=>6];
$res = array_diff_uassoc($array1,$array2,'comparekey');

函數輸出內容爲

a-b b-c c-d e-f f-g a-e b-e c-e d-e

所以可以看出來,傳入自定義函數進行比較的不一定是來自不同數組的鍵。還有可能是相同數組的鍵。

2.自定義函數只是比較鍵值是否相等嗎?

當然不是了,這個比較函數本身是比較大小的。但是卻不是我們理解的比較鍵值是否相等的。根據自定的返回結果,php內部會對內部的指針位置進行調整,所以我們看到後面的比較是a-e b-e c-e d-e

3.比較鍵值的時候,真的是相同健名的數組元素鍵值相比較嗎?

這個也不是的。實際上就是因爲比較函數的數組結果回影響到php內部數組指針位置的變更。變更方式不同會導致最終相互比價的不是我們認爲的相同鍵名的值相互比較。

看一下php源碼,array_diff_uassoc最終都是通過php_array_diff函數實現的。

static void php_array_diff(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp)
{
    ...

if (hash->nNumOfElements > 1) {
    if (behavior == DIFF_NORMAL) {
        zend_sort((void *) lists[i], hash->nNumOfElements,
                sizeof(Bucket), diff_data_compare_func, (swap_func_t)zend_hash_bucket_swap);
    } else if (behavior & DIFF_ASSOC) { /* triggered also when DIFF_KEY */
        zend_sort((void *) lists[i], hash->nNumOfElements,
                sizeof(Bucket), diff_key_compare_func, (swap_func_t)zend_hash_bucket_swap);
    }
}
...
}

可以看到diff_key_compare_func傳給了排序函數。所以,自定義函數的返回結果會影響到臨時變量lists的輸出。

php內部首先對所有的輸入數組進行進行排序。所以在自定義函數中可以看出前面的輸出內容都是先把數組的鍵名依次進行比較。

真實面目

當輸入的數組的都按鍵名拍好序之後,就要對第一個數組分別於其他數組的鍵名進行比較。

1) 比較第一個數組當前元素的鍵名與要比較數組的各個元素健名是否一樣,知道遇到第一個一樣或者比較結束爲止。

RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0])));
while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) {
  for (i = 1; i < arr_argc; i++) {
    Bucket *ptr = ptrs[i];
    if (behavior == DIFF_NORMAL) {
      while (Z_TYPE(ptrs[i]->val) != IS_UNDEF && (0 < (c = diff_data_compare_func(ptrs[0], ptrs[i])))) {
        ptrs[i]++;
      }
    } else if (behavior & DIFF_ASSOC) { /* triggered also when DIFF_KEY */
      while (Z_TYPE(ptr->val) != IS_UNDEF && (0 != (c = diff_key_compare_func(ptrs[0], ptr)))) {
        ptr++;
      }
    }
    ...
  }
  ...
}

2) 如果鍵名一樣(健名比較函數返回0),則比較鍵值是否相等。如果不相等,則c設置爲-1,繼續比較下一個數組的元素。

RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0])));
while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) {
    ...
    for (i = 1; i < arr_argc; i++) {
        ...
        if (!c) {
            ...
            if (diff_data_compare_func(ptrs[0], ptr) != 0) {
                c = -1;
                if (key_compare_type == DIFF_COMP_KEY_USER) {
                    BG(user_compare_fci) = *fci_key;
                    BG(user_compare_fci_cache) = *fci_key_cache;
                }
            }
            ...
        }
        ...
    }
    ...
}

3) 根據比較結果,如果比較結果不相等,則用第一個數組的下一個元素比較其他數組的所有元素。

如果比較結果相等(c=0),則刪除返回數組(第一個數組複製得到的)對應的鍵名。

RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0])));
while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) {
    ...
    if (!c) {
        for (;;) {
            p = ptrs[0];
            p = ptrs[0];
            if (p->key == NULL) {
                zend_hash_index_del(Z_ARRVAL_P(return_value), p->h);
            } else {
                zend_hash_del(Z_ARRVAL_P(return_value), p->key);
            }
            if (Z_TYPE((++ptrs[0])->val) == IS_UNDEF) {
                goto out;
            }
            ...
        }
    }
    else {
        for (;;) {
            if (Z_TYPE((++ptrs[0])->val) == IS_UNDEF) {
                goto out;
            }
            ...
        }
        ...
    }
...
}

以下列數組以及自定義函數爲例說明比較過程。

function comparekey($a,$b){
    return 0;
}

$array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4];
$array2 = ['a'=>2,'d'=>4,'e'=>6];

設置返回數組未array1

比較健名"a","a"相等,則比較array1['a']!=$array2['a']。

比較健名"b","a",相等,則比較array1['b']==$array2['a'],刪除返回數組的鍵值'b'

比較健名"c","a",相等,則比較array1['c']!=$array2['a']。

比較健名"d","a",相等,則比較array1['c']!=$array2['a']。

所以最終返回數組爲

$res = ['a'=>1,'c'=>3,'d'=>4]

總結

所以,自定義函數並不是讓我們完全的自定義。自定義的函數返回結果回導致不一樣的輸出結果。php數組有很多提供自定義的函數方法。但是,如果你的自定義函數返回值是“有悖常理的”,比如這個問題中的函數,永遠都是相等的,但是php同一個數組的鍵值不可能相同,所以這個自定義函數的比較結果其實是"有問題的"。在這個前提下,那麼php返回的結果也有可能會有意外的輸出。

當你下次使用array_diff_uassoc函數的時候,應該瞭解到,這個自定義函數並不僅僅是比較兩個數組的健名是否一樣,還會影響到比較之前php對輸入數組的內部排序;自定義函數的返回結果會直接影響到php數組指針的變更順序,導致比較結果的不一樣;

文章首發於公衆【寫PHP的老王】
PS:分享不易,如果覺得有用,記得分享喲
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章