[php-src] Issue #20262: array_unique() with SORT_REGULAR returns duplicate values
| From: | jmarble | Date: | Wed, 22 Oct 2025 21:20:31 +0000 |
| Subject: | [php-src] Issue #20262: array_unique() with SORT_REGULAR returns duplicate values | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-251101@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/20262
Author: jmarble
### Description
The following code:
```php
<?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
)
```
**The value
"5" appears at both indices [0] and [4] in the result.**
This array represents apartment/unit numbers (e.g., Unit 5, Unit 10, Unit 3A), which is a standard
format in building management systems where letter suffixes denote sub-units or variations.
Demonstration: https://3v4l.org/M5lcP
---
## Root Cause
From analyzing PHP source code (ext/standard/array.c,
Zend/zend_operators.c):
**The algorithm:**
1. Sort array using zendi_smart_strcmp() which calls
is_numeric_string_ex()
2. Walk through sorted array comparing **only adjacent elements**
3. Delete duplicates from original array
**The bug:**
is_numeric_string_ex() extracts leading numeric portions:
- "3A" → extracts 3
- "5" → extracts 5
- "10" → extracts 10
- Compares numerically: 3 < 5 < 10
However, unstable sort produces:
```
Sorted: ["5", "10", "10", "3A", "5",
"5"]
```
The "3A" (numeric value 3) ends up AFTER "10" instead
of before "5", separating the duplicate "5" values.
The deduplication walks through comparing adjacent elements:
```c
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 flaw:** The algorithm only compares with lastkept (last unique value), not with
all previous values. Position 4's "5" is never compared back to position
0's "5".
**Source files:**
- ext/standard/array.c - PHP_FUNCTION(array_unique)
- Zend/zend_operators.c - zendi_smart_strcmp(),
is_numeric_string_ex()
---
## Comparison with SORT_STRING
```php
<?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
```php
<?php
$unique = array_unique($array, SORT_STRING);
```
### PHP Version
```plain
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
```
### Operating System
_No response_