4

Given the following array of strings, I have been attempting to natural sort a-z strings first, followed by numerical strings and finally special characters.

    nextSearchTerms = ["T","D","I","C","Y","O","4","K","N","800","S","1","V","(","10","'","`","B","M","[","-"," ","J","U","H","G","R","E","P"];
    console.log(nextSearchTerms);

    Array.prototype.naturalSort = function(){
        var a, b, a1, b1, rx=/(\d+)|(\D+)/g, rd=/\d+/;
        return this.sort(function(as, bs){
            a= String(as).toLowerCase().match(rx);
            b= String(bs).toLowerCase().match(rx);
            while(a.length && b.length){
                a1= a.shift();
                b1= b.shift();
                if (rd.test(a1) || rd.test(b1)){
                    if(!rd.test(a1)) return -1;
                    if(!rd.test(b1)) return 1;
                    if(a1 != b1) return a1-b1;
                }
                else if (a1 != b1) {
                    var aIsLetter = a1[0].charAt(0).match(/[a-z]/i),
                        bIsLetter = b1[0].charAt(0).match(/[a-z]/i);
                    if (aIsLetter && !bIsLetter) return -1;
                    if (!aIsLetter && bIsLetter) return 1;
                    return (a1[0] == b1[0] ? 0 : (a1[0] < b1[0] ? -1 : 1));
                }
            }
            return a.length - b.length;
        });
    }
    console.log(nextSearchTerms.naturalSort());

The function I have been attempting to modify currently returns.

["B", "C", "D", "E", "G", "H", "I", "J", "K", "M", "N", "O", "P", "R", "S", "T", "U", "V", "Y", " ", "'", "(", "-", "[", "`", "1", "4", "10", "800"]

I would like the final array output to be.

["B", "C", "D", "E", "G", "H", "I", "J", "K", "M", "N", "O", "P", "R", "S", "T", "U", "V", "Y", "1", "4", "10", "800", "'", "(", "-", "[", "`"," "]

Any suggestions on what I am missing?

Seigs
  • 43
  • 4
  • will the array ever change? between characters of same type (symbols for example) what order do you want them in? – tudor.gergely Apr 17 '16 at 13:22
  • Have a look at [natural sort](https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort) – Bergi Apr 17 '16 at 13:36
  • @tudor.gergely yes for sure, they will change all the time. Regarding order of special characters I have preference only that I they are ordered last (after a-z & 0-9). – Seigs Apr 17 '16 at 20:17
  • @Seigs I see. Well I modified my solution to also work for numbers. Have a look. – tudor.gergely Apr 18 '16 at 05:49

4 Answers4

2

Here's my stab at what you're looking for. I think it's a bit cleaner than what you have:

Array.prototype.naturalSort = function() {
  var stringRE = /^[A-Za-z]+$/
  var numberRE = /^[\d]+$/
  return this.sort(function(a, b) {
    var aIsString = stringRE.test(a);
    var bIsString = stringRE.test(b)
    var aIsNumeric = numberRE.test(a);
    var bIsNumeric = numberRE.test(b);
    if (aIsString && bIsString) {
      return a.localeCompare(b);
    } else if (aIsNumeric && bIsNumeric) {
      return parseInt(a, 10) - parseInt(b, 10);
    } else if (aIsString && bIsNumeric) {
      return -1;
    } else if (aIsNumeric && bIsString) {
      return 1;
    } else if (aIsString || aIsNumeric) {
      return -1;
    } else if (bIsString || bIsNumeric) {
      return 1;
    } else {
      return a.localeCompare(b);
    }
  })
};

var chars = ["T","D","I","C","Y","O","4","K","N","800","S","1","V","(","10","'","`","B","M","[","-"," ","J","U","H","G","R","E","P"];

console.log(chars.naturalSort());
// ["B", "C", "D", "E", "G", "H", "I", "J", "K", "M", "N", "O", "P", "R", "S", "T", "U", "V", "Y", "4", "1", "10", "800", " ", "-", "'", "(", "[", "`"]
Alex R
  • 624
  • 3
  • 10
  • Much cleaner, I had to change the return on `aIsNumeric && bIsNumeric` test too `return parseInt(a, 10)-parseInt(b, 10)` to get the numerical strings to natural sort. Now returns, `["B", "C", "D", "E", "G", "H", "I", "J", "K", "M", "N", "O", "P", "R", "S", "T", "U", "V", "Y", "1", "4", "10", "800", " ", "-", "'", "(", "[", "\`"]` – Seigs Apr 17 '16 at 20:52
0

How about this? When something is compared to numeric...

            if (rd.test(a1) || rd.test(b1)){
                var aIsLetter = a1[0].charAt(0).match(/[a-z]/i),
                    bIsLetter = b1[0].charAt(0).match(/[a-z]/i);
                if(!rd.test(a1) && aIsLetter) return -1;
                if(!rd.test(a1) && !aIsLetter) return 1;
                if(!rd.test(b1) && bIsLetter) return 1;
                if(!rd.test(b1) && !bIsLetter) return -1;
                if(a1 != b1) return a1-b1;
            }
J.Cheong
  • 227
  • 1
  • 7
0

Here is a shorter solution:

function naturalSort(a, b) {
  var aPriority = /[a-z]/i.test(a) * 3 + /\d+/i.test(a) * 2; 
  var bPriority = /[a-z]/i.test(b) * 3 + /\d+/i.test(b) * 2; 

  if (aPriority === bPriority) return a.localeCompare(b, 'en', {numeric: true});
  return aPriority < bPriority ? 1: -1;
}

nextSearchTerms.sort(naturalSort)
// ["B", "C", "D", "E", "G", "H", "I", "J", "K", "M", "N", "O", "P", "R", "S", "T", "U", "V", "Y", "1", "4", "10", "800", " ", "-", "'", "(", "[", "`"]

If I understood correctly this should answer your request. It is rather short and easy to understand. Letters have bigger priority (3), numbers have 2 and the rest have 1. And the array is sorted in order of priority.

LE: Fixed sorting for numbers;

tudor.gergely
  • 4,800
  • 1
  • 16
  • 22
-1

I guess the following approach shall accomplish the requested task. Actually the function is just a single liner which takes the advantage of chainable array functions. For better understanding i spread it over three lines. Works in the following steps;

  1. reduce the array into an array of 3 arrays of separate types. While doing this convert decimals to numbers as it will be needed in the next step.
  2. reduce the array by sorting the types arrays mapping them back into string if they are number type and concatting with the previous one.

I believe it can be simplified further but so far that's what i could have come up with. Please advise...

var testar = ["T","D","I","C","Y","O","4","K","N","800","S","1","V","(","10","'","`","B","M","[","-"," ","J","U","H","G","R","E","P", "0", "-17"];
function naturalSort(a){
  return a.reduce((p,c) => {
                             /[A-Z]/i.test(c) ? p[0].push(c)   :
                             /\d+/.test(c)    ? p[1].push(c*1) : p[2].push(c);
                             return p
                           }, [[],[],[]])
          .reduce((p,c) => p.concat(c.sort((p,c) => p < c ? -1:1)
          .map(e => typeof e == "number" ? e.toString() : e)),[]);
}
document.write('<pre>' + JSON.stringify(naturalSort(testar), 0, 2) + '</pre>');
Redu
  • 25,060
  • 6
  • 56
  • 76
  • Downvoter please clarify what exactly you were thinking right at the moment you clicked the down arrow. This is actually "the one and only answer" which truly answers the question. Even the accepted one fails when you insert a negative number like "-17". If you had any ideas on how to make this code more efficient please come forward and share with me. If there is anything you just don't want to see in this snippet please let me know. What is the reason for the downvote i am really curious. How did i make you unhappy..? – Redu Apr 18 '16 at 05:14