问题
I was trying to sort an array of divs so that if a
is below or to the left of b
, a
is before b
.
After a couple hours in CodePen, I realized that if an array is ten or more items in length, Chrome will sort the items out of order, at least with this compare function:
var array = [0,1,2,3,4,5,6,7,8,9,10];
array.sort(function(a, b) {
return -1;
});
Chrome returns:
[0, 2, 3, 4, 1, 6, 7, 8, 9, 10, 5]
See on CodePen
If you log a and b inside the sort function, it becomes obvious why it happens--it's just the algorithm Chrome uses. I know people use return a-b etc ... but let's turn to the following function ... The array is of jQuery objects containing divs. I want a to come before b if a is below or to the left of b. Any help??
EDIT: In response to some answers here, I rewrite function to output either 1
, -1
, or 0
. Still, I get unwanted results. See how in the output, the first object's right
property is greater than the second's left
property, and the first object's top
property is lower than the second's bottom
. According to the compare function, they should be in the opposite order.
var array = [
{
bottom:1181.8854675292969,
left:23.39583396911621,
right:72.39583396911621,
top:910.8854675292969,
},
{
bottom:1181.3750305175781,
left:78.39583587646484,
right:183.39583587646484,
top:1132.3750305175781
},
{
bottom:1182.6042175292969,
left:189.39584350585938,
right:349.3958435058594,
top:1021.6042175292969
},
{
bottom:1181.3750305175781,
left:355.3958435058594,
right:626.3958435058594,
top:1132.3750305175781
},
{
bottom:1133.2292175292969,
left:355.3958435058594,
right:632.3958435058594,
top:1132.2292175292969
},
{
bottom:1127.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:1022.0208435058594
},
{
bottom:1127.0208435058594,
left:355.3958435058594,
right:460.3958435058594,
top:1022.0208435058594
},
{
bottom:1127.0208435058594,
left:466.3958435058594,
right:571.3958435058594,
top:1022.0208435058594,
},
{
bottom:1016.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:911.0208435058594
},
{
bottom:1016.2395935058594,
left:189.39584350585938,
right:515.3958435058594,
top:800.2395935058594
},
{
bottom:1016.2395935058594,
left:521.3958740234375,
right:626.3958740234375,
top:800.2395935058594
},
{
bottom:906.0208435058594,
left:23.39583396911621,
right:183.3958339691162,
top:801.0208435058594
},
{
bottom:794.6041870117188,
left:23.39583396911621,
right:72.39583396911621,
top:634.6041870117188
},
{
bottom:795.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:690.0208435058594
},
{
bottom:794.0208435058594,
left:189.39584350585938,
right:404.3958435058594,
top:689.0208435058594
},
{
bottom:794.0208435058594,
left:410.3958435058594,
right:515.3958435058594,
top:689.0208435058594
},
{
bottom:794.0208435058594,
left:521.3958740234375,
right:626.3958740234375,
top:689.0208435058594
},
{
bottom:683.3750152587891,
left:78.39583587646484,
right:183.39583587646484,
top:634.3750152587891
},
{
bottom:684.6041870117188,
left:189.39584350585938,
right:349.3958435058594,
top:523.6041870117188
},
{
bottom:684.6041870117188,
left:355.3958435058594,
right:570.3958435058594,
top:523.6041870117188
},
{
bottom:629.0208435058594,
left:23.39583396911621,
right:183.3958339691162,
top:524.0208435058594
},
{
bottom:518.2395935058594,
left:23.39583396911621,
right:128.3958339691162,
top:302.2395935058594
},
{
bottom:517.8854217529297,
left:134.39584350585938,
right:405.3958435058594,
top:246.8854217529297
},
{
bottom:518.604175567627,
left:411.3958435058594,
right:626.3958435058594,
top:357.60417556762695
}
];
array.sort(function(a, b) {
if(a.bottom < b.top || a.left > b.right)
return 1;
if(a.bottom > b.top || a.left < b.right)
return -1;
return 0;
});
console.log(array[4],array[8]);
EDIT: Found a workaround for my purposes. I used forEach
to compare the items to each other and increment z-index
based on vertical and then horizontal order:
function setTileZIndex() {
var $tiles = $('.grid__item__wrap');
var coords = [];
$tiles.each(function(index) {
var topLeft = $(this).offset();
var obj = {
bottom: topLeft.top + $(this).height(),
left: topLeft.left,
top: topLeft.top,
right: topLeft.left + $(this).width(),
$this: $(this),
z: 9999
};
coords.push(obj);
});
coords.forEach(function(a) {
coords.forEach(function(b) {
if (a.bottom < b.top)
b.z += 4;
if (a.left > b.right)
b.z += 1;
})
});
coords.forEach(function(elt) {
elt.$this.css('z-index', elt.z);
});
}
回答1:
Your compare function must return:
- negative: when the first element should come before the second
- zero: when the order between elements doesn't matter
- positive: when the second element should come before the first.
Returning always -1 causes a random result.
I'm afraid it is not possible to do what you are trying to do, because the compare function must be consistent through all elements in the array. With the compare function you are using, it is possible to have f(a, b) = -1
, and f(b, a) = -1
, which is inconsistent: either a
or b
should come first.
回答2:
EDIT
It seems you've over-simplified in your original question. Here's an updated answer:
I want a to come before b if a is below or to the left of b. Any help??
In that case, make sure you're comparing the same edges of each object – ie compare a.left
with b.left
, and a.bottom
with b.bottom
...
const data = [
{ bottom:1181, left:23, right:72, top:910, },
{ bottom:906, left:23, right:183, top:801 },
{ bottom:1181, left:78, right:183, top:1132 },
{ bottom:1182, left:189, right:349, top:1021 },
{ bottom:1133, left:355, right:632, top:1132 },
{ bottom:795, left:78, right:183, top:690 },
{ bottom:1181, left:355, right:626, top:1132 },
{ bottom:1127, left:78, right:183, top:1022 },
{ bottom:1127, left:355, right:460, top:1022 },
{ bottom:1127, left:466, right:571, top:1022, },
{ bottom:1016, left:78, right:183, top:911 },
]
data.sort((a,b) => {
if (a.left < b.left || a.bottom < b.bottom)
return -1
else if (a.right > b.right || a.top > b.top)
return 1
else
return 0
})
console.log(data)
// [ { bottom: 906, left: 23, right: 183, top: 801 },
// { bottom: 1181, left: 23, right: 72, top: 910 },
// { bottom: 795, left: 78, right: 183, top: 690 },
// { bottom: 1016, left: 78, right: 183, top: 911 },
// { bottom: 1127, left: 78, right: 183, top: 1022 },
// { bottom: 1182, left: 189, right: 349, top: 1021 },
// { bottom: 1133, left: 355, right: 632, top: 1132 },
// { bottom: 1181, left: 78, right: 183, top: 1132 },
// { bottom: 1127, left: 355, right: 460, top: 1022 },
// { bottom: 1181, left: 355, right: 626, top: 1132 },
// { bottom: 1127, left: 466, right: 571, top: 1022 } ]
Original answer
I'm certain this has been answered somewhere else on this site, but your comparator must return -1
, 0
, and 1
values to get an intended result
-1
moves thea
to the left ofb
1
moves thea
to the right ofb
0
results in neithera
orb
changing positions
let sorted = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort((a,b) => {
if (a < b)
return -1
else if (a > b)
return 1
else
return 0
})
console.log(sorted)
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Or use super-terse but harder-to-read chained ternary expressions
let sorted = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort((a,b) =>
a < b ? -1 : a > b ? 1 : 0
)
console.log(sorted)
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Keep in mind, elements in the array are not necessarily compared in an order you might expect – ie, don't expect compare(0,1)
then compare(1,2)
, then compare(2,3)
, etc
let sorted = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort((a,b) => {
console.log(a,b)
return a < b ? -1 : (a > b ? 1 : 0)
})
// 0 10
// 0 5
// 10 5
// 2 5
// 3 5
// 4 5
// 1 5
// 6 5
// 9 5
// 8 5
// 7 5
// 0 2
// 2 3
// 3 4
// 4 1
// 3 1
// 2 1
// 0 1
// 6 7
// 7 8
// 8 9
// 9 10
console.log(sorted)
//=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
回答3:
Sorry, in my haste I probably didn't explain thoroughly. The only thing required of the resulting array is that, if a box is completely above OR to the right of another
(a.bottom < b.top || a.left > b.right)
it should come after the other box.
One set of conditions are
a.bottom < b.top || a.left > b.right ? 1 : -1
as verified by .reduceRight()
call.
var coords = [
{
bottom:1181.8854675292969,
left:23.39583396911621,
right:72.39583396911621,
top:910.8854675292969,
},
{
bottom:1181.3750305175781,
left:78.39583587646484,
right:183.39583587646484,
top:1132.3750305175781
},
{
bottom:1182.6042175292969,
left:189.39584350585938,
right:349.3958435058594,
top:1021.6042175292969
},
{
bottom:1181.3750305175781,
left:355.3958435058594,
right:626.3958435058594,
top:1132.3750305175781
},
{
bottom:1133.2292175292969,
left:355.3958435058594,
right:632.3958435058594,
top:1132.2292175292969
},
{
bottom:1127.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:1022.0208435058594
},
{
bottom:1127.0208435058594,
left:355.3958435058594,
right:460.3958435058594,
top:1022.0208435058594
},
{
bottom:1127.0208435058594,
left:466.3958435058594,
right:571.3958435058594,
top:1022.0208435058594,
},
{
bottom:1016.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:911.0208435058594
},
{
bottom:1016.2395935058594,
left:189.39584350585938,
right:515.3958435058594,
top:800.2395935058594
},
{
bottom:1016.2395935058594,
left:521.3958740234375,
right:626.3958740234375,
top:800.2395935058594
},
{
bottom:906.0208435058594,
left:23.39583396911621,
right:183.3958339691162,
top:801.0208435058594
},
{
bottom:794.6041870117188,
left:23.39583396911621,
right:72.39583396911621,
top:634.6041870117188
},
{
bottom:795.0208435058594,
left:78.39583587646484,
right:183.39583587646484,
top:690.0208435058594
},
{
bottom:794.0208435058594,
left:189.39584350585938,
right:404.3958435058594,
top:689.0208435058594
},
{
bottom:794.0208435058594,
left:410.3958435058594,
right:515.3958435058594,
top:689.0208435058594
},
{
bottom:794.0208435058594,
left:521.3958740234375,
right:626.3958740234375,
top:689.0208435058594
},
{
bottom:683.3750152587891,
left:78.39583587646484,
right:183.39583587646484,
top:634.3750152587891
},
{
bottom:684.6041870117188,
left:189.39584350585938,
right:349.3958435058594,
top:523.6041870117188
},
{
bottom:684.6041870117188,
left:355.3958435058594,
right:570.3958435058594,
top:523.6041870117188
},
{
bottom:629.0208435058594,
left:23.39583396911621,
right:183.3958339691162,
top:524.0208435058594
},
{
bottom:518.2395935058594,
left:23.39583396911621,
right:128.3958339691162,
top:302.2395935058594
},
{
bottom:517.8854217529297,
left:134.39584350585938,
right:405.3958435058594,
top:246.8854217529297
},
{
bottom:518.604175567627,
left:411.3958435058594,
right:626.3958435058594,
top:357.60417556762695
}
];
// a.bottom < b.top || a.left > b.right ? a.bottom > b.top || a.left < b.right ? 0 : 1 : -1
coords.sort((a, b) => a.bottom < b.top || a.left > b.right ? 1 : -1);
console.log(coords);
coords.reduceRight((a, b) => {console.log(a.bottom < b.top || a.left > b.right); return b});
回答4:
Here is a comparison function that will work for you.
var arr = [15, 4, 11, 19, 11];
function compareFn(a, b) {
return a > b ? 1 : a === b ? 0 : -1;
}
var sorted = arr.sort(compareFn);
console.log(sorted);
回答5:
Please test the code in Chrome and for example in Edge. In Crome, the sort is not stable, if a return value is the same for all comparisons.
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
array.sort(function(a, b) { return 0; });
console.log(array); // Chrome: [5, 0, 2, 3, 4, 1, 6, 7, 8, 9, 10]
// Edge: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
array.sort(function(a, b) { return 1; });
console.log(array); // Chrome: [5, 10, 0, 9, 8, 7, 6, 1, 4, 3, 2]
// Edge: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
array.sort(function(a, b) { return -1; });
console.log(array); // Chrome: [0, 2, 3, 4, 1, 6, 7, 8, 9, 10, 5]
// Edge: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
来源:https://stackoverflow.com/questions/43563981/chrome-array-sorting-numbers-out-of-order