[英]Can you sort an array of opaque object instances in JavaScript? NOT by a property
这不是物业分类的问题!
假设我有一个对象实例数组。 许多实例不止一次出现在数组中。
var array = [
opaqueObjectA,
opaqueObjectB,
opaqueObjectA,
opaqueObjectC,
opaqueObjectB,
opaqueObjectC,
opaqueObjectA,
];
我不关心顺序,我关心的是同一个实例的对象最终彼此相邻。 换句话说,在排序之后,可能会有一个结果
var array = [
opaqueObjectB,
opaqueObjectB,
opaqueObjectC,
opaqueObjectC,
opaqueObjectA,
opaqueObjectA,
opaqueObjectA,
];
我不在乎A或者B或C是否排在第一位,我只关心同一个实例的对象在排序后是否彼此相邻。
所以问题是
在JavaScript的sort
保证来处理这种情况?
如果不是我该怎么办? sort函数要求我返回-1,如果a <b,1是a> b,0如果a === b但是给定对象是不透明的,并且由于我无法访问指针值或其他东西,我没有什么可以比较它们得到小于或大于结果,只有相同的结果。
我可以为每个不透明的对象添加一些sortId,但是向对象添加属性似乎有点不好,我不知道我是否在考虑一个属性。 我可以创建另一组对象,每个对象都有一个id和一个实例的引用,对它们进行排序,然后将它们的实例收集到一个新数组中。 对于必须构建整个对象数组进行排序,这似乎相当蹩脚。
实际上我也希望能够按多个实例进行排序,这是一个属性,但仍无法比较。 例:
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, },
];
我希望能够先用东西,然后按东西对它们进行排序。 所以一个可能的结果是
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, },
];
这在C / C ++中是微不足道的,因为我只能比较实例的地址。 有没有办法在JavaScript中执行此操作而不会破坏具有被黑客攻击属性的对象,而无需仅为排序创建临时数组?
您不能使用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);
如果需要多次调用此函数,可以将其添加到Array.prototype
,而不是污染范围:
Array.prototype.instanceSort = function (original) { ... }
然后在你的数组上调用它,如下所示: var sorted = original.instanceSort()
@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;
}
这假设您永远不需要在任何可能最终被此函数排序的对象上使用名为__instanceSortIndex
的属性。 它在理论上有点脏,但在实践中使用是安全的。
这是另一个,但它只有在你的目标是支持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);
WeakMap
可以将现有对象保存为键,但它不会添加到对象引用计数,因此基本上您可以使用它来存储隐藏属性,而不必担心您还持有引用并创建内存泄漏。 在这种情况下,我在instanceSort
比较器函数中使用WeakMap
为它作为参数接收的每个对象分配一个唯一的整数标识符,并使用标识符之间的差异作为对象之间的“差异”。
缺点是您不能将它用于早于IE11,Firefox 31 ESR,Safari 7.1,iOS 7,Konqueror(所有版本)和Opera(所有版本)的浏览器。 有关支持WeakMap的浏览器的详细信息,请参阅上面的链接。
我对你的问题的解释是,你想按类型排序,所以这就是我想出的:
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]
我基本上比较x.__proto__
来检查对象是否来自同一个父对象。
编辑:修复了错误,感谢@Travis J 编辑:更改了克隆方法
在我看来,您可以使用本机Array.prototype.sort()进行主要/次要分组,因为数组中的对象是多次使用的同一对象 。 我提出的方法不会修改对象,但会使用临时数组并根据主要和次要“排序”项创建新的分组数组。 当对象不透明时,我不确定如何实现真正的排序,因为你必须有一些可以排序的标准。
//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
我会一直看这个问题的表现。 这种方法对于大型数组来说可能效率低,因为如果对象在本机Array.prototype.sort()方法中不相等,则默认为1(可能会强制对compareFunction进行更多调用)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.