array_unique() with SORT_REGULAR returns duplicate values
まだ誰も着手していません。
- 主要言語
- 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:
- simple array of strings: https://3v4l.org/M5lcP
- array of objects: https://3v4l.org/5kr1t
- array of arrays: https://3v4l.org/0HVoV
Root Cause
The algorithm:
- Sort array using comparison function from
php_get_data_compare_func_unstable() - Walk through sorted array comparing only adjacent elements
- 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
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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