简体   繁体   English

你能在JavaScript中对一组不透明的对象实例进行排序吗? 不是由财产

[英]Can you sort an array of opaque object instances in JavaScript? NOT by a property

THIS IS NOT A QUESTION OF SORTING BY A PROPERTY!! 这不是物业分类的问题!

Assume I have an array of object instances. 假设我有一个对象实例数组。 Many instances are in the array more than once. 许多实例不止一次出现在数组中。

var array = [
  opaqueObjectA,
  opaqueObjectB,
  opaqueObjectA,
  opaqueObjectC,
  opaqueObjectB,
  opaqueObjectC,
  opaqueObjectA,
];

I don't care about the order, I care that the objects that are the same instance end up next to each other. 我不关心顺序,我关心的是同一个实例的对象最终彼此相邻。 In other words after sorting, one possible result would be 换句话说,在排序之后,可能会有一个结果

var array = [
  opaqueObjectB,
  opaqueObjectB,
  opaqueObjectC,
  opaqueObjectC,
  opaqueObjectA,
  opaqueObjectA,
  opaqueObjectA,
];

I don't care if the A's or the B's or the C's come first, I only care that objects of the same instance are next to each other after sorting. 我不在乎A或者B或C是否排在第一位,我只关心同一个实例的对象在排序后是否彼此相邻。

So the questions are 所以问题是

  1. is JavaScript sort guaranteed to handle this case? 在JavaScript的sort保证来处理这种情况?

  2. If not how can I do it? 如果不是我该怎么办? The sort function requires me to return -1 if a < b, 1 of a > b and 0 if a === b but given the objects are opaque, and since I don't have access to pointer values or something else, I have nothing to compare them with to get a less than or greater than result, only an equal result. sort函数要求我返回-1,如果a <b,1是a> b,0如果a === b但是给定对象是不透明的,并且由于我无法访问指针值或其他东西,我没有什么可以比较它们得到小于或大于结果,只有相同的结果。

I can go add some sortId to each opaque object but that seems kind of bad to add properties to objects, I'd have no idea if I'm cobbering a property. 我可以为每个不透明的对象添加一些sortId,但是向对象添加属性似乎有点不好,我不知道我是否在考虑一个属性。 I could make another set of objects, each with an id and a reference to one instance, sort those, then collect their instances into a new array. 我可以创建另一组对象,每个对象都有一个id和一个实例的引用,对它们进行排序,然后将它们的实例收集到一个新数组中。 That also seems rather lame to have to go build an entire array of objects to sort. 对于必须构建整个对象数组进行排序,这似乎相当蹩脚。

Actually I'd also like to be able to sort by multiple instances which is a property but still not comparable. 实际上我也希望能够按多个实例进行排序,这是一个属性,但仍无法比较。 Example: 例:

var array = [
  { thing: opaqueThingA, stuff: opaqueStuffG, },
  { thing: opaqueThingA, stuff: opaqueStuffH, },
  { thing: opaqueThingB, stuff: opaqueStuffG, },
  { thing: opaqueThingC, stuff: opaqueStuffG, },
  { thing: opaqueThingB, stuff: opaqueStuffH, },
  { thing: opaqueThingA, stuff: opaqueStuffG, },
  { thing: opaqueThingA, stuff: opaqueStuffH, },
  { thing: opaqueThingC, stuff: opaqueStuffG, },
];

I'd like to be able to sort them first by thing, then by stuff. 我希望能够先用东西,然后按东西对它们进行排序。 So one possible result would be 所以一个可能的结果是

var array = [
  { thing: opaqueThingB, stuff: opaqueStuffG, },
  { thing: opaqueThingB, stuff: opaqueStuffH, },
  { thing: opaqueThingA, stuff: opaqueStuffG, },  // Note the G' s 
  { thing: opaqueThingA, stuff: opaqueStuffG, },  // Are next to
  { thing: opaqueThingA, stuff: opaqueStuffH, },  // each other
  { thing: opaqueThingA, stuff: opaqueStuffH, },
  { thing: opaqueThingC, stuff: opaqueStuffG, },
  { thing: opaqueThingC, stuff: opaqueStuffG, },
];

This would be trivial in C/C++ because I could just compare the addresses of the instances. 这在C / C ++中是微不足道的,因为我只能比较实例的地址。 Is there a way to do this in JavaScript without dirtying the objects with hacked on properties and without making temporary arrays just for sorting? 有没有办法在JavaScript中执行此操作而不会破坏具有被黑客攻击属性的对象,而无需仅为排序创建临时数组?

You cannot do it using Array.prototype.sort because the arguments sent to the comparator function are only objects and you need to keep track of the objects yourself somewhere. 您不能使用Array.prototype.sort来执行此操作,因为发送到比较器函数的参数只是对象,您需要在某处自己跟踪对象。

var objectA = {name: 'objectA'}, objectB = {name: 'objectB'}, objectC = {name: 'objectC'};
var original = [objectA, objectB, objectA, objectC, objectB, objectC, objectA];

var instanceSort = function (original) {
        var seen = [], comparator = function (a, b) {
                if (seen.indexOf(a) === -1) seen.push(a);
                if (seen.indexOf(b) === -1) seen.push(b);
                return seen.indexOf(a) - seen.indexOf(b);
        }
        return original.sort(comparator);
}

var sorted = instanceSort(original);
console.log(sorted);

If you need to call this function multiple times, you could add it to Array.prototype like so, instead of polluting the scope: 如果需要多次调用此函数,可以将其添加到Array.prototype ,而不是污染范围:

Array.prototype.instanceSort = function (original) { ... }

and then call it on your array like so: var sorted = original.instanceSort() 然后在你的数组上调用它,如下所示: var sorted = original.instanceSort()


@steady rain complained that this is inefficient, so here's an improved version: @steady rain抱怨这是低效的,所以这是一个改进的版本:

var instanceSort = function (original) {
    var i, o, comparator, sorted;

    for (i = original.length - 1; i >= 0; i--) {
        o = original[i];
        if (!o.hasOwnProperty('__instanceSortIndex')) o.__instanceSortIndex = i;
    }

    comparator = function (a, b) {
        return a.__instanceSortIndex - b.__instanceSortIndex;
    }

    sorted = original.sort(comparator);

    for (i = original.length - 1; i >= 0; i--) {
        delete original[i].__instanceSortIndex;
    }

    return sorted;
}

this assumes you'll never ever need to use a property called __instanceSortIndex on any object that might ever end up being sorted by this function. 这假设您永远不需要在任何可能最终被此函数排序的对象上使用名为__instanceSortIndex的属性。 It's a bit dirty in theory, but it's safe to use in practice. 它在理论上有点脏,但在实践中使用是安全的。


Here's another one, but it will only work if you're targeting modern browsers which support WeakMap and you like the idea of sending the function as argument to .sort() : 这是另一个,但它只有在你的目标是支持WeakMap现代浏览器并且你喜欢将函数作为参数发送到.sort()才会起作用:

var objectA = {name: 'objectA'}, objectB = {name: 'objectB'}, objectC = {name: 'objectC'};
var original = [objectA, objectB, objectA, objectC, objectB, objectC, objectA];

var instanceSort = function (a, b) {
        if (!instanceSort.history) {
                instanceSort.history = new WeakMap();
                instanceSort.uid = 0;
        }

        var h = instanceSort.history, aIndex, bIndex;
        if (h.has(a)) aIndex = h.get(a);
        else h.set(a, aIndex = ++instanceSort.uid);
        if (h.has(b)) bIndex = h.get(b);
        else h.set(b, bIndex = ++instanceSort.uid);

        return aIndex - bIndex;
}

var sorted = original.sort(instanceSort);

A WeakMap can hold existing object as keys but it will not add to the object reference count, so basically you can use it to store hidden properties without worrying that you're also holding references and creating memory leaks. WeakMap可以将现有对象保存为键,但它不会添加到对象引用计数,因此基本上您可以使用它来存储隐藏属性,而不必担心您还持有引用并创建内存泄漏。 In this case I am using a WeakMap inside the instanceSort comparator function to assign a unique integer identifier to each object it receives as argument and use the difference between identifiers as the "difference" between objects. 在这种情况下,我在instanceSort比较器函数中使用WeakMap为它作为参数接收的每个对象分配一个唯一的整数标识符,并使用标识符之间的差异作为对象之间的“差异”。

The downside is that you cannot use it for browsers older than IE11, Firefox 31 ESR, Safari 7.1, iOS 7, Konqueror (all versions) and Opera (all versions). 缺点是您不能将它用于早于IE11,Firefox 31 ESR,Safari 7.1,iOS 7,Konqueror(所有版本)和Opera(所有版本)的浏览器。 See the link above for detailed information regarding browsers which support WeakMap. 有关支持WeakMap的浏览器的详细信息,请参阅上面的链接。

My interpretation of your question is that you wanna sort by type so here is what I came up with : 我对你的问题的解释是,你想按类型排序,所以这就是我想出的:

function Test() {} // Dummy object
var test = [a = 1, b = 2, c = 3, a, "titi", b, c, new Test(), a, b, c, new Test(), "toto"]; // Test Array

function sortByType(cpy) {
    var arr = cpy.slice();
    var arrs = [];
    var ret = [];
    for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arrs.length; j++) {
            if (arrs[j][0].__proto__ === arr[i].__proto__) {
                arrs[j].push(arr[i]);
                arr[i] = null;
                break;
            }
        }
        if (arr[i] !== null) {
            arrs.push([arr[i]]);
            arr[i] = null;
        }
    }
    ret = Array.prototype.concat.apply([], arrs);
    return (ret);
}
test = sortByType(test); // [1, 2, 3, 1, 2, 3, 1, 2, 3, "titi", "toto", Test, Test]

I basically compare x.__proto__ to check whether object are from the same parent or not. 我基本上比较x.__proto__来检查对象是否来自同一个父对象。

Edit : Fixed a typo thanks to @Travis J Edit : Changed clone method 编辑:修复了错误,感谢@Travis J 编辑:更改了克隆方法

It seems to me you can use the native Array.prototype.sort() for primary/secondary grouping since the objects in the array are the same object used multiple times. 在我看来,您可以使用本机Array.prototype.sort()进行主要/次要分组,因为数组中的对象是多次使用的同一对象 The approach I've come up with does not modify the objects, but does use a temporary array and create a new, grouped array based on both the primary and secondary "sort" items. 我提出的方法不会修改对象,但会使用临时数组并根据主要和次要“排序”项创建新的分组数组。 I'm not sure how true sorting can be accomplished when the object is opaque since you have to have some criteria by which you can sort by. 当对象不透明时,我不确定如何实现真正的排序,因为你必须有一些可以排序的标准。

 //object setup //inner objects, note that "name" is a convention only used to identify sort order (secondary grouping) var innerObjA = { "name": "a" }; var innerObjB = { "name": "b" }; var innerObj1 = { "name": "1" }; var innerObj2 = { "name": "2" }; var innerObj3 = { "name": "3" }; //parenting objects (primary grouping) var obj1 = { "first": innerObjA, "second": innerObj1 }; var obj2 = { "first": innerObjA, "second": innerObj2 }; var obj3 = { "first": innerObjA, "second": innerObj1 }; var obj3 = { "first": innerObjB, "second": innerObj1 }; var obj4 = { "first": innerObjB, "second": innerObj1 }; var obj5 = { "first": innerObjB, "second": innerObj2 }; var obj6 = { "first": innerObjB, "second": innerObj3 }; //out of order array var original = [obj6, obj2, obj4, obj1, obj5, obj3]; //helper to show the order of these objects function showOrder(myArray){ myArray.forEach(function(index) { console.log(index.first.name, index.second.name); }); console.log('-----------'); } //helper to handle the native sort function doNativeSort(myArray, sortBy) { myArray.sort(function(a, b) { return a[sortBy]===b[sortBy]?0:1; }); return myArray; //returns so we can use in-line } showOrder(original); //primary sort is done by the property "first" within the objects // in our array, since the OP states that many instances of an // object are in the array more than once, the native JS sort will // work to GROUP items which is what we do here. doNativeSort(original, "first"); showOrder(original); //secondary sort is more challenging and requires temp arrays // to group the like items so that we can again use the native // sort, we'll use the reduce method to compare the array that // already has the first grouping/sorting applied to it. function myReduce(original) { var newArr = [], subGroup = []; //new stuff original.reduce(function(previousValue, currentValue) { if (previousValue.first === currentValue.first) { subGroup.push(currentValue); } else { if (subGroup.length > 0) { //concat the sorted sub-group onto the new array newArr = newArr.concat(doNativeSort(subGroup, "second")); } //starting the next subgroup subGroup = [currentValue]; } //becomes the previous value in the next invocation return currentValue; }, original[0]); //sort the final subGroup and add to the new array newArr = newArr.concat(doNativeSort(subGroup, "second")); return newArr; } var groupedArray = myReduce(original); showOrder(groupedArray); 

http://jsbin.com/yeboyosome/4/edit?js,console http://jsbin.com/yeboyosome/4/edit?js,console

I'd watch performance in general for this problem. 我会一直看这个问题的表现。 This approach could be inefficient for large arrays since we're defaulting to 1 if the objects are not equal in the native Array.prototype.sort() method (potentially forcing more invocations of the compareFunction). 这种方法对于大型数组来说可能效率低,因为如果对象在本机Array.prototype.sort()方法中不相等,则默认为1(可能会强制对compareFunction进行更多调用)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM