143

Can anyone verify this for me? JavaScript does not have a version of strcmp(), so you have to write out something like:

 ( str1 < str2 ) ? 
            -1 : 
             ( str1 > str2 ? 1 : 0 );
Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
  • 1
    You're not alone - [other people have done this before](http://phpjs.org/functions/strcmp:533). The [PHP.JS project](http://phpjs.org/) actually has done this for many other common functions, as well. It's a handy resource. – Reed Copsey Jul 24 '09 at 18:35

8 Answers8

155

What about

str1.localeCompare(str2)
newacct
  • 119,665
  • 29
  • 163
  • 224
  • localeCompare() looked good, but it looked like it was MS-only, or not in the standard at best. –  Jul 24 '09 at 18:49
  • 12
    what standard are you looking at? it seems to be in ECMA-262 standard section 15.5.4.9, as well as in the mozilla Javascript reference (https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods_unrelated_to_HTML) – newacct Jul 24 '09 at 19:37
  • newacct is absolutely correct. This seems to be ECMAScript standard. Probably the best solution in this case. – coderjoe Jul 24 '09 at 21:26
  • 3
    `localeCompare()` sometimes behaves differently on each browser. – Varda Elentári Mar 23 '16 at 16:21
  • 2
    @VardaElentári: Only for characters that have no lexical ordering in the given locale. For characters that *do* and browsers that don't restrict what parts of Unicode they use, results are consistent and [defined by ECMA-402 and Unicode](http://ecma-international.org/ecma-402/3.0/index.html#sec-implementation-dependencies). – T.J. Crowder Apr 14 '17 at 09:51
  • Update 9 years later- seems this has partial support in IE6+ (covering the single-arg version), full support in IE11, and all other decent browsers of course support it: https://caniuse.com/#search=localecompare – Kip Sep 13 '18 at 13:57
45

Javascript doesn't have it, as you point out.

A quick search came up with:

function strcmp ( str1, str2 ) {
    // http://kevin.vanzonneveld.net
    // +   original by: Waldo Malqui Silva
    // +      input by: Steve Hilder
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +    revised by: gorthaur
    // *     example 1: strcmp( 'waldo', 'owald' );
    // *     returns 1: 1
    // *     example 2: strcmp( 'owald', 'waldo' );
    // *     returns 2: -1

    return ( ( str1 == str2 ) ? 0 : ( ( str1 > str2 ) ? 1 : -1 ) );
}

from http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_strcmp/

Of course, you could just add localeCompare if needed:

if (typeof(String.prototype.localeCompare) === 'undefined') {
    String.prototype.localeCompare = function(str, locale, options) {
        return ((this == str) ? 0 : ((this > str) ? 1 : -1));
    };
}

And use str1.localeCompare(str2) everywhere, without having to worry wether the local browser has shipped with it. The only problem is that you would have to add support for locales and options if you care about that.

Esteban Küber
  • 36,388
  • 15
  • 79
  • 97
  • 1
    I think this is a nice way of handling it (feature detection and polyfill FTW), but if micro speed performance is so important, as for the need of this method, then I am a little puzzled that `==` is used and not `===` since the latter avoid type conversion and hence is that micro second faster. – Tokimon Jun 30 '14 at 08:45
  • a note on the polyfill-- localeCompare is not case-sensitive, so to make the polyfill also not case-sensitive you might do something like- `var a = this.toUpperCase(); var b = str.toUpperCase(); return ((a == b) ? 0 : ((a > b) ? 1 : -1));` – Kip Sep 13 '18 at 14:01
28

localeCompare() is slow, so if you don't care about the "correct" ordering of non-English-character strings, try your original method or the cleaner-looking:

str1 < str2 ? -1 : +(str1 > str2)

This is an order of magnitude faster than localeCompare() on my machine.

The + ensures that the answer is always numeric rather than boolean.

Community
  • 1
  • 1
1''
  • 26,823
  • 32
  • 143
  • 200
  • Two bugs: does not return 0 for str1 == str2, does not return 1 for str1 > str2 – stackunderflow Sep 03 '13 at 18:14
  • @stackunderflow What are you suggesting that it returns in those cases? – 1'' Sep 03 '13 at 18:16
  • @1" I suggest it should be useful as a comparefun in [Array.prototype.sort](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11), but actually the values need not be 1 and -1, just negative or positive as I just reread the standard. – stackunderflow Sep 03 '13 at 18:22
  • 2
    @stackunderflow I'm using it successfully in a sorting function. What is the bug that you are experiencing? – 1'' Sep 03 '13 at 18:23
  • @stackunderflow that line is correct, it returns -1 if str1str2 which from boolean to int is 0 or 1. – Felype Jul 13 '15 at 12:23
  • 1
    This will return -1, false, or true instead of -1, 0, or 1. To get it to return numbers always, tweak it like this: str1 < str2 ? -1 : +(str1 > str2) – jfren484 Jul 21 '16 at 17:56
  • 2
    One more thing (I'm using this in code I'm writing right now, so I've been perfecting it): just be aware that this is a case-sensitive comparison ('Foo' will come before 'bar' but 'Bar' will come after 'foo'). That corresponds to OP's question about strcmp, but many people may come here looking for a case-agnostic comparison. – jfren484 Jul 21 '16 at 20:30
  • 10
    Here's an even cleaner-looking expression: `(str1 > str2) - (str1 < str2)` – Jonathan Gilbert Nov 15 '16 at 16:37
9
var strcmp = new Intl.Collator(undefined, {numeric:true, sensitivity:'base'}).compare;

Usage: strcmp(string1, string2)

Result: 1 means string1 is bigger, 0 means equal, -1 means string2 is bigger.

This has higher performance than String.prototype.localeCompare

Also, numeric:true makes it do logical number comparison

Anonymous
  • 91
  • 1
  • 1
1

from this How to Check if Two Strings are Equal in JavaScript article:

  1. Generally, if your strings contain only ASCII characters, you use the === operator to check if they are equal.
  2. But when your strings contain characters that include combining characters(eg. e + ◌́ = é), you normalize them first before comparing for equality as follows- s1.normalize() === s2.normalize()
0

So I fell into this rabbit hole and wrote some tests to build an intuition, the result's are weird. tldr it looks like localeCompare Performs a to lowercase that the equality operators do not. This causes "ff" to be >= "ZZ" but locale Compare returns -1 becuse "ff" <= 'zz'

For best results view logs of code in browser console ctrl + shift + i

second snip it hides hand tests so you see some random ones.

Home this helps someone

function stringBench(n, bench, min = 10, len = 10, logDif = false) {
  function makeid(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
      result += characters.charAt(Math.floor(Math.random() *
        charactersLength));
    }
    return result;

  }

  var a = [];
  var b = [];
  var pool = {};
  let rle = [];
  let rlc = [];

  let now;
  for (let i = 0; i < n; i++) {
    pool[i] = (makeid(min + Math.floor(Math.random() *
      len))); //10-20ish

  }
  for (let i = 0; i < bench; i++) {
    a[i] = (Math.floor(Math.random() * n));
    b[i] = (Math.floor(Math.random() * n));
  }

  console.log("now testin le vs lc on a pool of", n, " with this many samples ", bench);
  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  let lcDelta = Date.now() - now;
  console.log("Performed ", bench, "lc localeCompare in ", lcDelta);


  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rle[i] = pool[a[i]] <= pool[b[i]];
  }
  let leDelta = Date.now() - now;
  console.log("Performed ", bench, "le (<=) compares in ", leDelta)

  for (let i = 0; i < n; i++) {
    pool[i] = (makeid(min + Math.floor(Math.random() *
      len))); //10-20ish

  }
  for (let i = 0; i < bench; i++) {
    a[i] = (Math.floor(Math.random() * n));
    b[i] = (Math.floor(Math.random() * n));
  }


  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rle[i] = pool[a[i]] <= pool[b[i]];
  }
  let leDelta2 = Date.now() - now;
  console.log("Performed ", bench, "le (<=) compares in ", leDelta2)

  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  let lcDelta2 = Date.now() - now;
  console.log("Performed ", bench, "lc localeCompare in ", lcDelta2);

  function testCmp(a, b, log = true) {
    let le = a <= b;
    let ge = a >= b;
    let lc = a.localeCompare(b);
    let l = a < b;
    let g = a > b;
    if (le && ge) console.assert(lc == 0, 'le && ge -> == -> lc == 0,')
    if (le) console.assert(lc <= 0, 'le-> lc <= 0')
    if (ge) console.assert(lc >= 0, 'ge-> lc >= 0')
    if (l) console.assert(lc < 0, 'l=>lc < 0')
    if (g) console.assert(lc > 0, 'g-> lc > 0')
    if (!log) return;
    console.log(`Compare:  ${a} le ${b} `, a <= b);
    console.log(`Compare:  ${a} ge ${b}`, a >= b);
    console.log(`Compare: ${a} lc ${b}`, a.localeCompare(b));
  }

  let c = 0
  for (let i = 0; i < bench; i++) {
    if (rle[i] != rlc[i] <= 0) {
      c++;
      testCmp(pool[a[i]], pool[b[i]], true);
      console.warn(pool[a[i]], ' le != lc <= 0 ', pool[b[i]]);

    }


    // rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  console.warn(' le != lc  out of bench, num diffs: ', c);


  testCmp('ff', 'ff')
  testCmp('ff', 'fa')
  testCmp('ff', 'fz')
  testCmp('ff', 'fff')
  testCmp('ff', 'ffa')
  testCmp('ff', 'ffz')
  testCmp('ff', 'a')
  testCmp('ff', 'z')
  testCmp('ff', 'f')
  testCmp('ff', 'zff')
  testCmp('ff', 'aff')
  testCmp('ff', 'ZZ')
  testCmp('ff', 'AA')
  testCmp('FF', 'ZZ')
  testCmp('FF', 'ff')
  testCmp('FF', 'AA')
  testCmp('ff', 'ZZZ')

  console.log("Dif le - lc = ", leDelta2 - lcDelta2);

  console.log("avg le ms/Mops = ", (leDelta + leDelta2) / (bench / 1000000));
  console.log("avg lc ms/Mops = ", (lcDelta + lcDelta2) / (bench / 1000000));


  console.log("Dif  - lc = ", leDelta2 - lcDelta2);

};
stringBench(1000, 5000, 1, 3, true);
// stringBench(1000000, 1000000);//nothing is equire
// stringBench(1000, 100000000);
// stringBench(1000000, 100000000, 3, 5);
// stringBench(1000000, 100000000, 15, 20);

function stringBench(n, bench, min = 10, len = 10, logDif = false) {
  function makeid(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
      result += characters.charAt(Math.floor(Math.random() *
        charactersLength));
    }
    return result;


  }

  var a = [];
  var b = [];
  var pool = {};
  let rle = [];
  let rlc = [];

  let now;
  for (let i = 0; i < n; i++) {
    pool[i] = (makeid(min + Math.floor(Math.random() *
      len))); //10-20ish

  }
  for (let i = 0; i < bench; i++) {
    a[i] = (Math.floor(Math.random() * n));
    b[i] = (Math.floor(Math.random() * n));
  }

  console.log("now testin le vs lc on a pool of", n, " with this many samples ", bench);
  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  let lcDelta = Date.now() - now;
  console.log("Performed ", bench, "lc localeCompare in ", lcDelta);


  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rle[i] = pool[a[i]] <= pool[b[i]];
  }
  let leDelta = Date.now() - now;
  console.log("Performed ", bench, "le (<=) compares in ", leDelta)

  for (let i = 0; i < n; i++) {
    pool[i] = (makeid(min + Math.floor(Math.random() *
      len))); //10-20ish

  }
  for (let i = 0; i < bench; i++) {
    a[i] = (Math.floor(Math.random() * n));
    b[i] = (Math.floor(Math.random() * n));
  }


  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rle[i] = pool[a[i]] <= pool[b[i]];
  }
  let leDelta2 = Date.now() - now;
  console.log("Performed ", bench, "le (<=) compares in ", leDelta2)

  now = Date.now();
  for (let i = 0; i < bench; i++) {
    rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  let lcDelta2 = Date.now() - now;
  console.log("Performed ", bench, "lc localeCompare in ", lcDelta2);

  function testCmp(a, b, log = true) {
    let le = a <= b;
    let ge = a >= b;
    let lc = a.localeCompare(b);
    let l = a < b;
    let g = a > b;
    if (le && ge) console.assert(lc == 0, 'le && ge -> == -> lc == 0,')
    if (le) console.assert(lc <= 0, 'le-> lc <= 0')
    if (ge) console.assert(lc >= 0, 'ge-> lc >= 0')
    if (l) console.assert(lc < 0, 'l=>lc < 0')
    if (g) console.assert(lc > 0, 'g-> lc > 0')
    if (!log) return;
    console.log(`Compare:  ${a} le ${b} `, a <= b);
    console.log(`Compare:  ${a} ge ${b}`, a >= b);
    console.log(`Compare: ${a} lc ${b}`, a.localeCompare(b));
  }

  let c = 0
  for (let i = 0; i < bench; i++) {
    if (rle[i] != rlc[i] <= 0) {
      c++;
      testCmp(pool[a[i]], pool[b[i]], true);
      console.warn(pool[a[i]], ' le != lc <= 0 ', pool[b[i]]);

    }


    // rlc[i] = pool[a[i]].localeCompare(pool[b[i]]);
  }
  console.warn(' le != lc  out of bench, num diffs: ', c);



  testCmp('ff', 'fa')
  testCmp('ff', 'fz')
  testCmp('ff', 'ZZ')



  console.log("Dif le - lc = ", leDelta2 - lcDelta2);

  console.log("avg le ms/Mops = ", (leDelta + leDelta2) / (bench / 1000000));
  console.log("avg lc ms/Mops = ", (lcDelta + lcDelta2) / (bench / 1000000));







  console.log("Dif  - lc = ", leDelta2 - lcDelta2);


  // for (let i = 0; i < bench; i++) {
  //     rlc[i] != rle[i]
  //     pool[a[i]].localeCompare(pool[b[i]]);
  // }
  //
  // console.log(makeid(5));
};
stringBench(1000, 5000, 1, 3, true);
// stringBench(1000000, 1000000);//nothing is equire
// stringBench(1000, 100000000);
// stringBench(1000000, 100000000, 3, 5);
// stringBench(1000000, 100000000, 15, 20);
Exo Flame
  • 97
  • 4
0

In my tests, this is about 10% faster than using a pair of ternary statements on the same set of randomly selected words.

function strcmp( a, b ) {
    for( let i=0 ; i<Math.min( a.length, b.length ) ; i++ ) {
        const n = a.charCodeAt(i) - b.charCodeAt(i);
        if( n ) return  n && ( ( n>>31 ) || 1 );
    }
    const n = a.length - b.length;
    return  n && ( ( n>>31 ) || 1 );
}
NastyCarl
  • 91
  • 4
-1

How about:

String.prototype.strcmp = function(s) {
    if (this < s) return -1;
    if (this > s) return 1;
    return 0;
}

Then, to compare s1 with 2:

s1.strcmp(s2)
  • 1
    It would help if you said why they shouldn't do what they did. I could understand if they were altering how an existing function method worked, but in this case they are adding a new one. – Relaxing In Cyprus Jun 23 '14 at 12:14
  • 3
    Extending prototypes unconditionally like this is generally a big no-no. – Christopher Feb 04 '19 at 18:33