Jump to content

Reverse Natural Sort issue


Go to solution Solved by requinix,

Recommended Posts

I am trying to read data from an XML file that I do not control, which contains FileID's. I need to present a dropdown box which sorts these as below:


What I have				What I get with natsort($filelist_array);		What I want (loaded into a dropdown box)
<FILEID>A2011-1</FILEID>		A2011-1							A2011-16
<FILEID>A2011-10</FILEID>		A2011-2							A2011-15
<FILEID>A2011-11</FILEID>		A2011-3							A2011-14
<FILEID>A2011-12</FILEID>		A2011-4							A2011-13
<FILEID>A2011-13</FILEID>		A2011-5							A2011-12
<FILEID>A2011-14</FILEID>		A2011-6							A2011-11
<FILEID>A2011-15</FILEID>		A2011-7							A2011-10
<FILEID>A2011-16</FILEID>		A2011-8							A2011-9
<FILEID>A2011-2</FILEID>		A2011-9							A2011-8
<FILEID>A2011-3</FILEID>		A2011-10						A2011-7
<FILEID>A2011-4</FILEID>		A2011-11						A2011-6
<FILEID>A2011-5</FILEID>		A2011-12						A2011-5
<FILEID>A2011-6</FILEID>		A2011-13						A2011-4
<FILEID>A2011-7</FILEID>		A2011-14						A2011-3
<FILEID>A2011-8</FILEID>		A2011-15						A2011-2
<FILEID>A2011-9</FILEID>		A2011-16						A2011-1
<FILEID>CP2015-102 </FILEID>		CP2015-75						CP2015-114 
<FILEID>CP2015-106 </FILEID>		CP2015-76						CP2015-106 
<FILEID>CP2015-114 </FILEID>		CP2015-86						CP2015-102 
<FILEID>CP2015-75 </FILEID>		CP2015-93						CP2015-93 
<FILEID>CP2015-76 </FILEID>		CP2015-102						CP2015-86 
<FILEID>CP2015-86 </FILEID>		CP2015-106						CP2015-76 
<FILEID>CP2015-93 </FILEID>		CP2015-114						CP2015-75 
<FILEID>CP2016-237 </FILEID>		CP2016-3						CP2016-246 
<FILEID>CP2016-238 </FILEID>		CP2016-237						CP2016-242 
<FILEID>CP2016-241 </FILEID>		CP2016-238						CP2016-241 
<FILEID>CP2016-242 </FILEID>		CP2016-241						CP2016-238 
<FILEID>CP2016-246 </FILEID>		CP2016-242						CP2016-237 
<FILEID>CP2016-3 </FILEID>		CP2016-246						CP2016-3 
<FILEID>MC2016-164 </FILEID>		MC2016-164						MC2016-167 
<FILEID>MC2016-167 </FILEID>		MC2016-167						MC2016-164 

I load the XML file into an array as such:
$FILEID = $xml->xpath('FileNOEventsDataSet/FileNOEventsData/FILEID');

$FIRST_NAT_SORT = array();
foreach ($FILEID as $node){
	$FIRST_NAT_SORT[]=(string)$node[0];
}

natsort($FIRST_NAT_SORT);

foreach ($FIRST_NAT_SORT as $NEWFILEID){
	$html .="<option value'".$NEWFILEID."' >".$NEWFILEID."</option>";
}

This results in normal natsort result in the select box.

What I want is column 3 above, which sorts by everything to the left of the 'dash' alphabetically, and everything to the right of the dash descending.

I think I can do an explode on the $node[0], such as:
$SECOND_NAT_SORT = array();
$second_nat_sort = explode("-",$node[0]);
which results in $second_nat_sort[0] = 'A2001' and $second_nat_sort[1]='16'

I then try to do a multiarraysort, but I am lost at this point. Above my skill level.

I am extremely novice, so bear with me.

 

 

Link to comment
https://forums.phpfreaks.com/topic/301524-reverse-natural-sort-issue/
Share on other sites

  • Solution

Use usort() with strnatcmp() to implement your own sorting algorithm.

usort($FIRST_NAT_SORT, function($a, $b) {
	$a1 = strtok($a, "-");
	$a2 = strtok("");
	$b1 = strtok($b, "-");
	$b2 = strtok("");

	return strnatcmp($a1, $b1) ?: -strnatcmp($a2, $b2);
});
[edit] It's been asked so I'll explain:

 

The function to usort() has to return 0 depending on how the two arguments ($a and $b) compare to each other - negative if $a $b. Fortunately that's exactly how strnatcmp() behaves too.

return strnatcmp($a1, $b1) will handle sorting for just the parts before the hyphens, but if they're equal then you need to compare the parts after the hyphens. $x ?: $y is shorthand for $x ? $x : $y, so

return strnatcmp($a1, $b1) ? strnatcmp($a1, $b1) : -strnatcmp($a2, $b2);
(except the shorthand will only evaluate the first strnatcmp() once)

 

Since strnatcmp() returns 0 if the two are equal and nonzero if not, the function will return that number if the two are not equal because it won't try to do the second comparison.

 

If they are equal then the second strnatcmp() does get evaluated. It works the exact same way as the first one, except it has a minus sign to negate the return value. Thus the result is backwards: negative if $a2 > $b2 and positive if $a2 $b2. The effect is a descending sort on the values after the hyphens.

 

To be absolutely explicit about everything you could write that line as

$compare1 = strnatcmp($a1, $b1);
if ($compare1 < 0) { // $a1 < $b1
	return -1;
} else if ($compare1 > 0) { // $a1 > $b1
	return 1;
}

// $compare1 == 0 and $a1 == $b1

$compare2 = strnatcmp($a2, $b2);
if ($compare2 < 0) { // $a2 < $b2
	return 1; // opposite result
} else if ($compare2 > 0) { // $a2 > $b2
	return -1; // opposite result
} else {
	return 0;
}
Edited by requinix
  • Like 1
$TABLE Should contain a list of XML elements like below. I did a print_r($TABLE) to see this result:

SimpleXMLElement Object ( [DOCID] => 96629 [DATERECD] => 2016-07-14T00:00:00-04:00 [DOCTITLE] => This is title of file [FILEID] => CP2015-106 )

The $TABLE should have various rows, and then we do following:

<?php
foreach($TABLE as $CASE)
$file_id = $CASE->FILEID;
{
?>
<tr>
<td><a href="/file/showfile/<?php echo $file_id; ?></a></td>
</tr>
<?php
}
?>

Which produces table like below:
CP2015-106
CP2015-83
CP2015-93

What I need is same sort you provided before:
CP2015-106
CP2015-93
CP2015-83

Can this be done just like example you provided? I need to get the FILEID out of the SimpleXML rows and do the sort.
Thoughts on this?

I have one other issue in another area.

Your Result	Needed Result
A98-1	A2010-10
A99-1	A2010-9
A2000-1	A2010-8
A2003-1	A2010-7
A2006-1	A2010-6
A2007-1	A2010-5
A2009-1	A2010-4
A2010-10	A2010-3
A2010-9	A2010-2
A2010-8	A2010-1 
A2010-7	A2009-1
A2010-6	A2007-1
A2010-5	A2006-1
A2010-4	A2003-1
A2010-3	A2000-1
A2010-2	A99-1
A2010-1	A98-1

Thanks again, my second question was answered. Now back to the first question on natsort.  I thought the solution you provided fixed my issue, but after deeper look this is situation:

Left column is what the solution you provided creates, Right column is what I am trying to do. Is it possible to sort the left side of the dash A to Z then numbers high to low natsort, and then right of dash natsort descending (10-1). 

try

$data = [   'A98-1',
            'A99-1',
            'A2000-1',
            'A2003-1',
            'A2006-1',
            'A2007-1',
            'A2009-1',
            'A2010-10',
            'A2010-9',
            'A2010-8',
            'A2010-7',
            'A2010-6',
            'A2010-5',
            'A2010-4',
            'A2010-3',
            'A2010-2',
            'A2010-1'   ];
usort ($data, function($a, $b) {
    list($a1,$a2) = explode('-', $a);
    list($b1,$b2) = explode('-', $b);
    $x = strnatcmp($b1, $a1);
    if ($x==0) {
        return strnatcmp($b2,$a2);
    }
    return $x;
});


Results:

Array
(
    [0] => A2010-10
    [1] => A2010-9
    [2] => A2010-8
    [3] => A2010-7
    [4] => A2010-6
    [5] => A2010-5
    [6] => A2010-4
    [7] => A2010-3
    [8] => A2010-2
    [9] => A2010-1
    [10] => A2009-1
    [11] => A2007-1
    [12] => A2006-1
    [13] => A2003-1
    [14] => A2000-1
    [15] => A99-1
    [16] => A98-1
)
Edited by Barand
  • Like 1
Array (
[0] => R2016-6
[1] => R2016-5
[2] => R2016-4
[3] => R2016-3
[4] => R2016-2
[5] => R2016-1
[6] => R2015-6
[7] => R2015-5
[8] => R2015-4
[9] => R2015-3
[10] => R2015-2
[11] => R2015-1
[12] => R2014-7
[13] => R2014-6
[14] => R2014-5
[15] => R2014-4
[16] => R2014-3
[17] => R2014-2
[18] => R2014-1
[19] => R2013-11
[20] => R2013-10
[21] => R2013-9
[22] => R2013-8
[23] => R2013-7
[24] => R2013-6
[25] => R2013-5
[26] => R2013-4
[27] => R2013-3
[28] => R2013-2
[29] => R2013-1
[30] => R2012-9
[31] => R2012-8
[32] => R2012-7
[33] => R2012-6
[34] => R2012-5
[35] => R2012-4
[36] => R2012-3
[37] => R2012-2
[38] => R2012-1
[39] => R2011-7
[40] => R2011-6
[41] => R2011-5
[42] => R2011-4
[43] => R2011-3
[44] => R2011-2
[45] => R2011-1
[46] => R2010-6
[47] => R2010-5
[48] => R2010-4
[49] => R2010-3
[50] => R2010-2
[51] => R2010-1
[52] => R2009-5
[53] => R2009-4
[54] => R2009-3
[55] => R2009-2
[56] => R2009-1
[57] => R2008-1
[58] => R2006-1

.....

[1055] => A2010-3
[1056] => A2010-2
[1057] => A2010-1
[1058] => A2009-1
[1059] => A2007-1
[1060] => A2006-1
[1061] => A2003-1
[1062] => A2000-1
[1063] => A99-1
[1064] => A98-1
[1065] => N2014-1
[1066] => N2012-2
[1067] => N2012-1
[1068] => N2011-1
[1069] => N2010-1
[1070] => N2009-1
[1071] => N2006-1
[1072] => SS2010-1
[1073] => IM2006-1
[1074] => IM2005-1
[1075] => IM2004-1
[1076] => IM2003-1
[1077] => *2003
[1078] => IM2002-1
[1079] => IM2001-1
[1080] => IM2000-1
[1081] => IM99-1
[1082] => PL105/277
[1083] => PI2016-3
[1084] => PI2016-2
[1085] => PI2016-1
[1086] => PI2015-1
[1087] => PI2014-1
[1088] => PI2013-1
[1089] => PI2012-1
[1090] => PI2010-3
[1091] => PI2010-2
[1092] => PI2010-1
[1093] => PI2009-1
[1094] => PI2008-4
[1095] => PI2008-3
[1096] => PI2008-2
[1097] => PI2008-1
[1098] => PI2007-1
[1099] => ACR2015
[1100] => ACR2014
[1101] => ACR2013
[1102] => ACR2012
[1103] => ACR2011
[1104] => ACR2010
[1105] => ACR2009
[1106] => ACR2008
[1107] => ACR2007
[1108] => CP2016-249
[1109] => CP2016-248
[1110] => CP2016-247
[1111] => CP2016-246
[1112] => CP2016-245
[1113] => CP2016-244
[1114] => CP2016-243
[1115] => CP2016-242
[1116] => CP2016-241
[1117] => CP2016-240

Ok, I tried what you have, and I get a different result. Let me see if I can explain a little better.

The Array will have a pattern as such:

 

AAA = 1 or more Letters

XXXX = 1 or more numbers

- = Possible Dash

YYYY = 1 or more numbers and/or letters.

 

I am trying to sort as such:

 AAA (1 or more letters) from A to Z.

XXXX (1 or more numbers) from highest to lowest, natsort.

- (May or may not be there)

YYYY (Remainder of the text) would is mostly numbers.

 

Here is a better Array which is what I have before I sort using your suggestion:

how's this?

usort($data, function($a,$b) {
    list($a1,$a2,$a3) = splitParts($a);
    list($b1,$b2,$b3) = splitParts($b);
    $x = strcmp($a1,$b1);
    if ($x == 0) {
        $y = $b2 - $a2;
        if ($y == 0) {
            return $b3 - $a3;
        }
        return $y;
    }
    return $x;
});


      
function splitParts($s) {
    $k = strlen($s);
    $a = ['','',''];
    $i = 0;
    $p = 0;
    while ($i < $k) {
        $c = $s[$i++];
        if (ctype_digit($c) && $p==0) $p = 1;
        if ($c=='-') {
            $c = $s[$i++];
            ++$p;
        }
        $a[$p] .= $c;
    }
    return $a;
}

Ok, this all worked in the area I needed. Thank you endlessly for that.  Now I have another section of code, that does not seem to work. It is the same principle but the code runs elsewhere. When I place your function in the code, it fails. This is must be inline php issue or something. Attached is the var_dump and var_export of the data. It must be something different, as I can not seem to get the code to work on this data set.

 

Array-Sample2-varExport.txt

Array-Sample2-varDump.txt

Given that the structure of the new array is nothing like the structure of the original, it is hardly surprising that it doesn't work. The array also contains references to undocumented SimpleXMLElement::__set_state() method.

 

Fatal error: Call to undefined method SimpleXMLElement::__set_state()

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.