php / php/php-src

array_unique() with SORT_REGULAR returns duplicate values

オープン
#20,262 コメント 43 件 リアクション 2 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Bug Extension: standard Status: Verified
主要言語
C
スター
40.4k
フォーク
8.2k
平均マージ
2日 13時間
マージ済み PR(30日)
96

説明

Description

The following code:

<?php
$units = ['5', '10', '5', '3A', '5', '5'];
$unique = array_unique($units, SORT_REGULAR);
print_r($unique);

Resulted in this output:

Array
(
    [0] => 5
    [1] => 10
    [3] => 3A
    [4] => 5
)

But I expected this output instead:

Array
(
    [0] => 5
    [1] => 10
    [3] => 3A
)

Demonstrations:


Root Cause

The algorithm:

  1. Sort array using comparison function from php_get_data_compare_func_unstable()
  2. Walk through sorted array comparing only adjacent elements
  3. Delete duplicates when adjacent elements compare equal

The bug:

SORT_REGULAR uses zend_compare() which calls zendi_smart_strcmp() for string comparisons. This function has non-transitive behavior when mixing numeric and non-numeric strings:

  • "5" < "10" → true (numeric comparison: 5 < 10)
  • "10" < "3A" → true (lexicographic: "1" < "3")
  • "3A" < "5" → true (lexicographic: "3" < "5") Creates a cycle!

Because the comparison is non-transitive, sorting algorithms (which require transitive comparisons) produce inconsistent results depending on input order.

The deduplication walks through comparing adjacent elements:

lastkept = position_0;  // "5"
position_1 "10" != "5" → keep, lastkept = position_1
position_2 "10" == "10" → delete
position_3 "3A" != "10" → keep, lastkept = position_3
position_4 "5" != "3A" → keep  ← Bug! Never compared to position_0
position_5 "5" == "5" → delete

The root issue: Non-transitive comparisons break the sorting algorithm's guarantee that equal values will be grouped together. The adjacent-only comparison is correct - but it requires the array to be properly sorted first, which requires transitive comparisons.


Comparison with SORT_STRING

<?php
$units = ['5', '10', '5', '3A', '5', '5'];
echo count(array_unique($units, SORT_REGULAR)) . "\n"; // 4 ✗ Wrong
echo count(array_unique($units, SORT_STRING)) . "\n";  // 3 ✓ Correct

SORT_STRING uses lexical comparison without numeric extraction, so duplicates stay grouped.


Workaround

For simple arrays of scalar values, you can use array_unique with default SORT_STRING flag.

<?php
$unique = array_unique($array, SORT_STRING);

For arrays or objects.

$uniqueAddr = [];
foreach ($addresses as $addr) {
    if (! in_array($addr, $uniqueAddr)) {
        $uniqueAddr[] = $addr;
    }
}
PHP Version
PHP 8.4.13 (cli) (built: Sep 26 2025 00:45:36) (NTS clang 15.0.0)
Copyright (c) The PHP Group
Built by Laravel Herd
Zend Engine v4.4.13, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.13, Copyright (c), by Zend Technologies

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、提示された array_unique() の例を再現し、次に issue で説明されているとおりに php_get_data_compare_func_unstable()、zend_compare()、zendi_smart_strcmp() を追跡します。リンクされたデモと、SORT_REGULAR および SORT_STRING の既存の動作を確認します。報告された重複ケースが回帰テストでカバーされ、選択した比較動作が他のモードを壊さず一貫していれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c, php
領域
backend
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
38/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。