[英]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.