As noted in other notes, natsort() does _not_ always return the expected sort order. It seems especially buggy when decimals or 0 padding is used. I've filed this bug report on the issue:
https://bugs.php.net/bug.php?id=74672
(PHP 4, PHP 5, PHP 7)
natsort — 用"自然排序"算法对数组排序
&$array
)本函数实现了一个和人们通常对字母数字字符串进行排序的方法一样的排序算法并保持原有键/值的关联,这被称为"自然排序"。本算法和通常的计算机字符串排序算法(用于 sort())的区别见下面示例。
Note:
If two members compare as equal, their relative order in the sorted array is undefined.
array
输入的 array。
成功时返回 TRUE
, 或者在失败时返回 FALSE
。
版本 | 说明 |
---|---|
5.2.10 | 用零填充的数字字符串 (例如 '00005')现在本质上会忽略掉填充的前道零。 |
Example #1 natsort() 基本用法的操作示例
<?php
$array1 = $array2 = array("img12.png", "img10.png", "img2.png", "img1.png");
asort($array1);
echo "Standard sorting\n";
print_r($array1);
natsort($array2);
echo "\nNatural order sorting\n";
print_r($array2);
?>
以上例程会输出:
Standard sorting Array ( [3] => img1.png [1] => img10.png [0] => img12.png [2] => img2.png ) Natural order sorting Array ( [3] => img1.png [2] => img2.png [1] => img10.png [0] => img12.png )
For more information see: Martin Pool's » Natural Order String Comparison page.
Example #2 natsort() examples demonstrating potential gotchas
<?php
echo "Negative numbers\n";
$negative = array('-5','3','-2','0','-1000','9','1');
print_r($negative);
natsort($negative);
print_r($negative);
echo "Zero padding\n";
$zeros = array('09', '8', '10', '009', '011', '0');
print_r($zeros);
natsort($zeros);
print_r($zeros);
?>
以上例程会输出:
Negative numbers Array ( [0] => -5 [1] => 3 [2] => -2 [3] => 0 [4] => -1000 [5] => 9 [6] => 1 ) Array ( [2] => -2 [0] => -5 [4] => -1000 [3] => 0 [6] => 1 [1] => 3 [5] => 9 ) Zero padding Array ( [0] => 09 [1] => 8 [2] => 10 [3] => 009 [4] => 011 [5] => 0 ) Array ( [5] => 0 [1] => 8 [3] => 009 [0] => 09 [2] => 10 [4] => 011 )
As noted in other notes, natsort() does _not_ always return the expected sort order. It seems especially buggy when decimals or 0 padding is used. I've filed this bug report on the issue:
https://bugs.php.net/bug.php?id=74672
This made me waste a lot of my precious youth ... natsort() is buggy if all numbers don't have the same number of decimal places.
(php 5.6.4-4ubuntu6.2)
<?php
$different_decimal_places_in_values = array('D'=>'13.59', '14.6' => '14.6', 'C-' => '14.19');
natsort($a);
var_dump($a);
/*echoes
array(3) {
'D' =>
string(5) "13.59"
'14.6' =>
string(4) "14.6" <----------- badly ordered
'C-' =>
string(5) "14.19"
}*/
?>
While this
<?php
$same_num_decimal_places_in_values = array('D'=>'13.59', '14.6' => '14.60', 'C-' => '14.19'); natsort($a); var_dump($a);
/*echoes
array(3) {
'D' =>
string(5) "13.59"
'C-' =>
string(5) "14.19"
'14.6' =>
string(5) "14.60" <--------- that is the correct position
}
*/
?>
To naturally sort by array key, the uksort function can be used.
<?php
echo "Sort by keys\n";
$smoothie = array('orange' => 1, 'apple' => 1, 'yogurt' => 4, 'banana' => 4);
print_r($smoothie);
uksort( $smoothie, 'strnatcmp');
print_r($smoothie)
?>
Output:
Sort by keys
Array
(
[orange] => 1
[apple] => 1
[yogurt] => 4
[banana] => 4
)
Array
(
[apple] => 1
[banana] => 4
[orange] => 1
[yogurt] => 4
)
See http://php.net/manual/en/function.uksort.php for more information about uksort and http://php.net/strnatcmp for usage of strnatcmp.
Be careful of the new behaviour in 5.2.10 version.
See the following sample:
<?php
$array = array('1 bis', '10 ter', '0 PHP', '0', '01', '01 Ver', '0 ', '1 ', '1');
natsort($array);
echo '<pre>';
print_r($array);
echo '</pre>';
?>
5.2.6-1 will output:
Array
(
[3] => 0
[6] => 0
[2] => 0 OP
[4] => 01
[5] => 01 Ver
[8] => 1
[7] => 1
[0] => 1 bis
[1] => 10 ter
)
5.2.10 will output:
Array
(
[6] => 0
[3] => 0
[8] => 1
[4] => 01
[7] => 1
[5] => 01 Ver
[0] => 1 bis
[1] => 10 ter
[2] => 0 OP
)
Greetings
Note:
The natsort function will sort depending on the operating system, but not depending on either Linux or Windows-based systems
There's a difference when sorting an array which is generated from the filesystem:
Array(
[0] => ./system/kernel/js/01_ui.core.js
[1] => ./system/kernel/js/00_jquery-1.3.2.js
[2] => ./system/kernel/js/02_my.desktop.js
)
natsort($array) will result in two different ways:
Case 1: (Debian)
Array(
[1] => ./system/kernel/js/00_jquery-1.3.2.js
[0] => ./system/kernel/js/01_ui.core.js
[2] => ./system/kernel/js/02_my.desktop.js
)
Case 2: (Debian Kernel, but Ubuntu shadowed php-Version)
Array(
[0] => ./system/kernel/js/01_ui.core.js
[2] => ./system/kernel/js/02_my.desktop.js
[1] => ./system/kernel/js/00_jquery-1.3.2.js
)
...so make sure you've named the files beginning with 01, then it works fine.
Note: negatives number.
<?php
$a = array(-5,-2,3,9);
natsort($a);
print_r($a);
?>
Will output:
Array ( [1] => -2 [0] => -5 [2] => 3 [3] => 9 )
natsort might not act like you would expect with zero padding, heres a quick sample.
<?php
$array = array('09', '8', '10', '009', '011');
natsort($array);
?>
/*
Array
(
[3] => 009
[4] => 011
[0] => 09
[1] => 8
[2] => 10
)
*/
There's no need to include your own API code to natsort an associative array by key. PHP's in-built functions (other than natsort) can do the job just fine:
<?php
uksort($myArray, "strnatcmp");
?>
there is another rnatsort function lower on the page, but it didn't work in the context i needed it in.
reasoning for this:
sorting naturally via the keys of an array, but needing to reverse the order.
function rnatsort ( &$array = array() )
{
$keys = array_keys($array);
natsort($keys);
$total = count($keys) - 1;
$temp1 = array();
$temp2 = array();
// assigning original keys to an array with a backwards set of keys, to use in krsort();
foreach ( $keys as $key )
{
$temp1[$total] = $key;
--$total;
}
ksort($temp1);
// setting the new array, with the order from the krsort() and the values of original array.
foreach ( $temp1 as $key )
{
$temp2[$key] = $array[$key];
}
$array = $temp2;
}
Here's a handy function to sort an array on 1 or more columns using natural sort:
<?php
// Example: $records = columnSort($records, array('name', 'asc', 'addres', 'desc', 'city', 'asc'));
$globalMultisortVar = array();
function columnSort($recs, $cols) {
global $globalMultisortVar;
$globalMultisortVar = $cols;
usort($recs, 'multiStrnatcmp');
return($recs);
}
function multiStrnatcmp($a, $b) {
global $globalMultisortVar;
$cols = $globalMultisortVar;
$i = 0;
$result = 0;
while ($result == 0 && $i < count($cols)) {
$result = ($cols[$i + 1] == 'desc' ? strnatcmp($b[$cols[$i]], $a[$cols[$i]]) : $result = strnatcmp($a[$cols[$i]], $b[$cols[$i]]));
$i+=2;
}
return $result;
}
?>
Greetings,
- John
The last comment should have been posted in doc about (r)sort( ). Indeed, and unfortunately, ORDER BY *does not* perform natural ordering. So, sometimes we *must* do a SQL request followed by natsort( ).
This function can be very usefull, but in some cases, like if you want to sort a MySQL query result, it's important to keep in mind that MySQL as built'in sorting functions which are way faster than resorting the result using a complex php algorythm, especially with large arrays.
ex; 'SELECT * FROM `table` ORDER BY columnName ASC, columnName2 DESC'
There's one little thing missing in this useful bit of code posted by mbirth at webwriters dot de:
<?php
function natsort2d(&$aryInput) {
$aryTemp = $aryOut = array();
foreach ($aryInput as $key=>$value) {
reset($value);
$aryTemp[$key]=current($value);
}
natsort($aryTemp);
foreach ($aryTemp as $key=>$value) {
$aryOut[$key] = $aryInput[$key];
// --------^^^^ add this if you want your keys preserved!
}
$aryInput = $aryOut;
}
?>
I got caught out through naive use of this feature - attempting to sort a list of image filenames from a digital camera, where the filenames are leading zero padded (e.g. DSCF0120.jpg) , will not sort correctly.
Maybe the example could be modified to exhibit this behaviour
(e.g. set array to -img0120.jpg','IMG0.png', 'img0012.png', 'img10.png', 'img2.png', 'img1.png', 'IMG3.png)
If the example hadn't used images I would have coded it correctly first time around!
additional to the code posted by justin at redwiredesign dot com (which I found very usefull) here is a function that sorts complex arrays like this:
<?
$array['test0'] = array('main' => 'a', 'sub' => 'a');
$array['test2'] = array('main' => 'a', 'sub' => 'b');
$array['test3'] = array('main' => 'b', 'sub' => 'c');
$array['test1'] = array('main' => 'a', 'sub' => 'c');
$array['test4'] = array('main' => 'b', 'sub' => 'a');
$array['test5'] = array('main' => 'b', 'sub' => 'b');
?>
or
<?
$array[0] = array('main' => 1, 'sub' => 1);
$array[2] = array('main' => 1, 'sub' => 2);
$array[3] = array('main' => 2, 'sub' => 3);
$array[1] = array('main' => 1, 'sub' => 3);
$array[4] = array('main' => 2, 'sub' => 1);
$array[5] = array('main' => 2, 'sub' => 2);
?>
on one or more columns.
the code
<? $array = array_natsort_list($array,'main','sub'); ?>
will result in $array being sortet like this:
test0,test2,test1,test4,test5,test3
or
0,2,1,4,5,3.
you may even submit more values to the function as it uses a variable parameter list. the function starts sorting on the last and the goes on until the first sorting column is reached.
to me it was very usefull for sorting a menu having submenus and even sub-submenus.
i hope it might help you too.
here is the function:
<?
function array_natsort_list($array) {
// for all arguments without the first starting at end of list
for ($i=func_num_args();$i>1;$i--) {
// get column to sort by
$sort_by = func_get_arg($i-1);
// clear arrays
$new_array = array();
$temporary_array = array();
// walk through original array
foreach($array as $original_key => $original_value) {
// and save only values
$temporary_array[] = $original_value[$sort_by];
}
// sort array on values
natsort($temporary_array);
// delete double values
$temporary_array = array_unique($temporary_array);
// walk through temporary array
foreach($temporary_array as $temporary_value) {
// walk through original array
foreach($array as $original_key => $original_value) {
// and search for entries having the right value
if($temporary_value == $original_value[$sort_by]) {
// save in new array
$new_array[$original_key] = $original_value;
}
}
}
// update original array
$array = $new_array;
}
return $array;
}
?>
Under limited testing, natsort() appears to work well for IP addresses. For my needs, it is far less code than the ip2long()/long2ip() conversion I was using before.
For those who want to natsort a 2d-array on the first element of each sub-array, the following few lines should do the job.
<?php
function natsort2d(&$aryInput) {
$aryTemp = $aryOut = array();
foreach ($aryInput as $key=>$value) {
reset($value);
$aryTemp[$key]=current($value);
}
natsort($aryTemp);
foreach ($aryTemp as $key=>$value) {
$aryOut[] = $aryInput[$key];
}
$aryInput = $aryOut;
}
?>
To make a reverse function, you can simply:
function rnatsort(&$a){
natsort($a);
$a = array_reverse($a, true);
}
One of the things I've needed to do lately is apply natural sorting to a complex array, e.g.:
Array
(
[0] => Array
(
[ID] = 4
[name] = Fred
)
[1] => Array
(
[ID] = 6
[name] = Bob
)
)
where I want to sort the parent array by the child's name. I couldn't see a way of doing this using array_walk, so I've written a simple function to do it. Hopefully someone will find this useful:
/**
* @return Returns the array sorted as required
* @param $aryData Array containing data to sort
* @param $strIndex Name of column to use as an index
* @param $strSortBy Column to sort the array by
* @param $strSortType String containing either asc or desc [default to asc]
* @desc Naturally sorts an array using by the column $strSortBy
*/
function array_natsort($aryData, $strIndex, $strSortBy, $strSortType=false)
{
// if the parameters are invalid
if (!is_array($aryData) || !$strIndex || !$strSortBy)
// return the array
return $aryData;
// create our temporary arrays
$arySort = $aryResult = array();
// loop through the array
foreach ($aryData as $aryRow)
// set up the value in the array
$arySort[$aryRow[$strIndex]] = $aryRow[$strSortBy];
// apply the natural sort
natsort($arySort);
// if the sort type is descending
if ($strSortType=="desc")
// reverse the array
arsort($arySort);
// loop through the sorted and original data
foreach ($arySort as $arySortKey => $arySorted)
foreach ($aryData as $aryOriginal)
// if the key matches
if ($aryOriginal[$strIndex]==$arySortKey)
// add it to the output array
array_push($aryResult, $aryOriginal);
// return the return
return $aryResult;
}
About the reverse natsort.. Maybe simpler to do :
function strrnatcmp ($a, $b) {
return strnatcmp ($b, $a);
}
Reverse Natsort:
function rnatsort($a, $b) {
return -1 * strnatcmp($a, $b);
}
usort($arr, "rnatsort");