-1

I'm having trouble sorting a string in a priority queue method I'm using. Here's an example of the string keys that I'm sorting:

[ '.0', '.1', '.2', '.4', '.2.1', '.3', '.4.1', '.5', '.5.1.5' ]

The values are representation of hierarchy that need to be ordered from smallest to largest. The result I'm expecting is:

[ '.0', '.1', '.2.1', '.2, '.3', '.4', '.4.1', '.5', '.5.1.5' ]

The method I'm using to push into my queue runs at O(log n) and looks like so:

    queue.add = function(myval) {
        // console.log(myval);
        var i = this.size;
        this.array[this.size++] = myval;
        while ( i > 0) {
            var p = (i - 1) >> 1;
            var ap = this.array[p];
            if(!this.compare(myval, ap )) break;
            this.array[i] = ap;
            i = p;
        }
    };

And my compare function is simply:

(a, b) => {
    return a[0] < b[0];
}

I was considering using localeCompare but since I'm in Node it doesn't seem to be dependably for some reason. When I use:

(a, b) => {
    return a[0].localeCompare(b[0]) > 0;
}

I get a compareLocal not defined error which might be related to something else.

My question is hence, how can I efficiently determine the string ordering in the manner I've outlined?

ddibiase
  • 1,412
  • 1
  • 20
  • 44
  • 2
    How is `'.2.1'` smaller than `'.2'`? – Michał Perłakowski Jan 04 '17 at 17:01
  • How are you calling the `add()` method? – Michał Perłakowski Jan 04 '17 at 17:09
  • Do you want to sort an array, or maintain a correct order when adding new items? – Assan Jan 04 '17 at 17:09
  • @Gothdo: that ordering is a result of how another library spits out it's nodes. .2 is the root node and .2.1 is the first child, .2.1.1 is the first child of the first child etc. Calling `add([ '.2.1.1, null, null])` nulls representing placeholders for other data. @Assan maintain the order when adding new items, sorting the whole array on every add() would increase cost. – ddibiase Jan 04 '17 at 17:22
  • 1
    Gothdo means in your expected result '.2.1' appears before '.2', but intuitively it should appear after. – Assan Jan 04 '17 at 17:27
  • 2
    To follow on @Gothdo's comment, why is `'.4'` smaller than `'.4.1'` if `'.2.1'` is smaller than `'.2'`. – Heretic Monkey Jan 04 '17 at 17:37
  • Also, not really sure you should really care about efficiency here. How many elements do you expect to have and how often new elements are added? Sometimes code simplicity is more important. Calling `sort` after `push` would achieve the desired result (assuming '.2' comes before '.2.1'), and it's complexity is just `O(n log n)`. – Assan Jan 04 '17 at 17:38
  • 1
    Possible duplicate of [How to compare software version number using js? (only number)](http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number) – Mr. Polywhirl Jan 04 '17 at 17:41
  • what is `p = (i - 1) >> 1` doing? – Nina Scholz Jan 04 '17 at 18:19
  • @NinaScholz that's division by two. I remember, in C or assembly, this would be slightly faster than straight up division. Not sure if this is true in JavaScript. – Assan Jan 04 '17 at 18:38
  • @Assan, i know, what it does, but what does it in the context of sorting? – Nina Scholz Jan 04 '17 at 21:47

3 Answers3

0

Although my result looks like this:

[".0", ".1", ".2", ".2.1", ".3", ".4", ".4.1", ".5", ".5.1.5"]

I hope this helps, I used insertion-sort to sort these values:

var arr = [ '.0', '.1', '.2', '.4', '.2.1', '.3', '.4.1', '.5', '.5.1.5' ];

// transform these values into '.21' or '.515' so that they can be sorted better
var transormedArray = arr.map(function(ele){
  return '.'+ele.replace(/[.]/g,'');
});

// insertion sort max-iterations n^2, min-iterations n(if already ordered)
for(var i=1; i< transormedArray.length; i++){
  for(var r=i;  r>=0 && transormedArray[r-1]>transormedArray[r]; r--){
      var tmp = transormedArray[r];
      transormedArray[r] = transormedArray[r-1];
      transormedArray[r-1] = tmp;
  }
}

// retransform them back into '.2.1' or '.5.1.5'
var result = transormedArray.map(function(ele){
  var str = ele.toString()[0];
  for(var i=1; i< ele.toString().length; i++){
    str += ele.toString()[i]+'.';
  }
  return str.slice(0,str.length-1);
});

console.log(result); // [".0", ".1", ".2", ".2.1", ".3", ".4", ".4.1", ".5", ".5.1.5"]
Blauharley
  • 4,186
  • 6
  • 28
  • 47
0

You could split the strings and compare the parts.

function customSort(data, order) {

    function isNumber(v) {
        return (+v).toString() === v;
    }

    var sort = {
            asc: function (a, b) {
                var i = 0,
                    l = Math.min(a.value.length, b.value.length);

                while (i < l && a.value[i] === b.value[i]) {
                    i++;
                }
                if (i === l) {
                    return a.value.length - b.value.length;
                }
                if (isNumber(a.value[i]) && isNumber(b.value[i])) {
                    return a.value[i] - b.value[i];
                }
                return a.value[i].localeCompare(b.value[i]);
            },
            desc: function (a, b) {
                return sort.asc(b, a);
            }
        },
        mapped = data.map(function (el, i) {
            return { index: i, value: el.split('.') };
        });

    mapped.sort(sort[order] || sort.asc);
    return mapped.map(function (el) {
        return data[el.index];
    });
}

var array = ['.0', '.1', '.2', '.4', '.2.1', '.3', '.4.1', '.5', '.5.1.5'];


console.log('sorted array asc', customSort(array));
console.log('sorted array desc ', customSort(array, 'desc'));
console.log('original array ', array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • But then `.2.1` should be smaller than `.2` while `.4` is bigger than `.4.1`. It's a mysterious question... – Redu Jan 04 '17 at 17:42
0

You can create a custom sort comparator method which splits the values by a dot and compares their length; along with their integer values.

This is the easiest and most straight-forward method.

var compareVersions = (a, b) => {
    if (a === b) return 0;
    var aArr = a.split("."), bArr = b.split(".");
    for (var i = 0; i < Math.min(aArr.length, bArr.length); i++) {
        if (parseInt(aArr[i]) < parseInt(bArr[i])) return -1;
        if (parseInt(aArr[i]) > parseInt(bArr[i])) return 1;
    }
    if (aArr.length < bArr.length) return -1;
    if (aArr.length > bArr.length) return 1;
    return 0;
};
var compareVersionsDesc = (a, b) => compareVersions(b, a);

// Example
let versionArr = ['.0', '.1', '.2', '.4', '.2.1', '.3', '.4.1', '.5', '.5.1.5'];

console.log('Original Array: ', JSON.stringify(versionArr));
console.log('Sort Ascending:', JSON.stringify(versionArr.sort(compareVersions)));
console.log('Sort Descending:', JSON.stringify(versionArr.sort(compareVersionsDesc)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132