Javascript - sort Array by reference

≯℡__Kan透↙ 提交于 2019-12-13 08:09:53

问题


I'm trying to sort a JavaScript Array by the reference to another object.

I have An array of Mesh. Each mesh contains an attribute named "texture", which contains the reference to a WebGLTexture object. WebGLTexture objects doesn't contains any readable attribute, I can only compare it by reference (==). toString method isn't defined.

Here is an example of the initial situation :

var texture1 = gl.createTexture(/* blah */); // Returns a WebGLTexture object
var texture2 = gl.createTexture(/* blah */); // Returns a WebGLTexture object
var texture3 = gl.createTexture(/* blah */); // Returns a WebGLTexture object
var meshes = [
    {name: "Mesh 0", texture: texture1},
    {name: "Mesh 1", texture: texture2},
    {name: "Mesh 2", texture: texture3},
    {name: "Mesh 3", texture: texture3},
    {name: "Mesh 4", texture: texture2},
    {name: "Mesh 5", texture: texture1},
    {name: "Mesh 6", texture: texture1},
    {name: "Mesh 7", texture: texture2},
    {name: "Mesh 8", texture: texture3},
    {name: "Mesh 9", texture: texture1}
];

What I want to do is to sort the array by texture reference, to have something like this (the order is not really important, I just want the object which have the same texture to be consecutive) :

var meshes = [
    {name: "Mesh 0", texture: texture1},
    {name: "Mesh 5", texture: texture1},
    {name: "Mesh 6", texture: texture1},
    {name: "Mesh 9", texture: texture1},
    {name: "Mesh 1", texture: texture2},
    {name: "Mesh 4", texture: texture2},
    {name: "Mesh 7", texture: texture2},
    {name: "Mesh 2", texture: texture3},
    {name: "Mesh 3", texture: texture3},
    {name: "Mesh 8", texture: texture3}
];

I know it could be possible to achieve it with loops, but It will require to create objects and arrays, and to do many nested loops. Performance is really important here.

The best solution I can found is to manually add a unique "id" attribute to each texture, and use Array.sort on it. But I'm not really happy with this solution it implies to modify native objects.

Do you know any native and fast method ?

EDIT : Based on wared answer, here is the solution :

var tempSortTextures = [];
meshes.sort(function(a, b) {
    var iA = null;
    var iB = null;
    for(var i = 0 ; i <= tempSortTextures.length ; i++) {
        if(i == tempSortTextures.length) {
            if(iA == null) {
                tempSortTextures.push(a.texture);
            } else /*if(iB == null)*/ {
                tempSortTextures.push(b.texture);
            }
        }
        var currentTexture = tempSortTextures[i];
        if(iA == null && a.texture == currentTexture) iA = i;
        if(iB == null && b.texture == currentTexture) iB = i;
        if(iA != null && iB != null) return iA - iB;
    }
});

回答1:


Probably not the best solution but it works (with pure javascript) :)

var map = [texture1, texture2, texture3];

meshes.sort(function (a, b) {
    var i = 0, item;
    while (item = map[i]) {
        if (a.texture && item === a.texture) { a = i; }
        if (b.texture && item === b.texture) { b = i; }
        if (!a.texture && !b.texture) { return a - b; }
        i++;
    }
});



回答2:


I doubt that there is a built-in solution for that.

You cannot use Array.sort() unless you can define an ordering relation on your objects, e.g. implement an operation <=. JavaScript does not expose meta information such as memory addresses, so there is no non-obtrusive solution.

As always, "fast" is a relative term. There is no "absolute fast", only "fast enough", so a naive O(n^2) implementation might work for you.




回答3:


Here's one way of doing the sorting using the new experimental WeakMap (only supported in Firefox as of this writing). By associating each texture with an index with the help of a WeakMap we can use the index in the sort function:

var texture1 = {name:"tex1"};
var texture2 = {name:"tex2"};
var texture3 = {name:"tex3"};

var meshes = [
    {name: "Mesh 0", texture: texture1},
    {name: "Mesh 1", texture: texture2},
    {name: "Mesh 2", texture: texture3},
    {name: "Mesh 3", texture: texture3},
    {name: "Mesh 4", texture: texture2},
    {name: "Mesh 5", texture: texture1},
    {name: "Mesh 6", texture: texture1},
    {name: "Mesh 7", texture: texture2},
    {name: "Mesh 8", texture: texture3},
    {name: "Mesh 9", texture: texture1}
];

var textureSortOrder = [texture1, texture2, texture3];
var sortMap = new WeakMap();

for (var i=0;i<textureSortOrder.length;i++) {
    sortMap.set(textureSortOrder[i], i);
}

meshes = meshes.sort(function(a, b){
   var indexA = sortMap.get(a.texture);
   var indexB = sortMap.get(b.texture);

    if (indexA < indexB) {
        return -1;
    } else if (indexA > indexB) {
        return 1;
    } else {
        return 0;
    }
});

console.log("sorted:");
for (var i=0;i<meshes.length;i++) {
    console.log(i, meshes[i].texture.name);
}

http://jsfiddle.net/ch5QJ/

I have no idea how fast this is, and as I said only Firefox supports WeakMap. But it might be worth testing it and use it for browsers which do support it if it does turn out to be fast.




回答4:


You can use underscore for this. Which anyway i suggest you to include if you need to manipulate objects, make your life easier:

here there is the fiddle: http://jsfiddle.net/pmcalabrese/2KSSn/

and here the code.

sorted = _(meshes).sortBy(function(meshes) {
    return meshes.texture;
});

I suggest you to give a look at the documentation of sortBy.




回答5:


Perhaps something like this? I haven't done any performance testing.

Javascript

function sortByTextureOrderOrGroupThem (theArray, theOrder) {
    var temp = [],
        thisOrder = theOrder || [],
        thisOrderLength = thisOrder.length,
        thisOrderIndex,
        order,
        theArrayLength,
        theArrayIndex,
        element;

    // if we were given an order then build the temp array from that and remove the element from theArray
    for (thisOrderIndex = 0; thisOrderIndex < thisOrderLength; thisOrderIndex += 1) {
        order = thisOrder[thisOrderIndex];
        for (theArrayIndex = theArray.length - 1; theArrayIndex >= 0; theArrayIndex -= 1) {
            element = theArray[theArrayIndex];
            if (element.texture === order) {
                temp.push(theArray.splice(theArrayIndex, 1)[0]);
            }
        }
    }

    // anything remaining in the array, group them together
    theArray.sort(function (a, b) {
        if (a.texture === b.texture) {
            return 0;
        }

        if (a.texture < b.texture) {
            return -1;
        }

        return 1;
    });

    // join any remaining grouped items to the temp
    temp = temp.concat(theArray);
    // empty theArray
    theArray.length = 0;
    // add the length and the starting index for use with apply
    temp.unshift(temp.length);
    temp.unshift(0);
    // splice temp back into theArray
    [].splice.apply(theArray, temp);

    // return theArray in case it is needed this way
    return theArray;
}

var texture1 = {"name": "texture1"},
    texture2 = {"name": "texture2"},
    texture3 = {"name": "texture3"},
    meshes = [
        {name: "Mesh 0", texture: texture1},
        {name: "Mesh 1", texture: texture2},
        {name: "Mesh 2", texture: texture3},
        {name: "Mesh 3", texture: texture3},
        {name: "Mesh 4", texture: texture2},
        {name: "Mesh 5", texture: texture1},
        {name: "Mesh 6", texture: texture1},
        {name: "Mesh 7", texture: texture2},
        {name: "Mesh 8", texture: texture3},
        {name: "Mesh 9", texture: texture1}
    ],
    order = [texture1, texture2, texture3];

sortByTextureOrderOrGroupThem(meshes, order);
console.log(JSON.stringify(meshes));

jsFiddle



来源:https://stackoverflow.com/questions/19135485/javascript-sort-array-by-reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!