I am trying to sort an array of objects by a property. I run:
array.sort(function(a, b){
var textA = a.name.toUpperCase();
var textB = b.name.toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1: 0
});
To alphabetically sort the array objects first and then I run an array.sort with a custom pare function as below:
array.sort(function(a, b){
if(a.name === b.name){
return -1;
}
return 1;
});
It seems to work with anything object that does not have a duplicate, however, as soon as there are doubles it pushes them all to the end of the array instead of just the extras.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'}
{name: 'Joe'},
{name: 'Joe'}
]
Expected Output:
- Amy
- Clark
- Dan
- Dave
- Joe
- Amy
- Clark
- Joe
Actual Result:
- Dan
- Dave
- Amy
- Amy
- Clark
- Clark
- Joe
- Joe
Sort Code To try and get Expected Result
array.sort(function(a,b){
if(a.name === b.name){return -1}
return 1;
});
I have a feeling the array.sort with a pare function can handle this however I keep playing with return values of 0, -1, 1 and cannot seem to get it to work fully as I would like.
Update
Expected Result Criteria:
If an object has the same name the duplicate should go to the end of the array. For example if there are two 'Amy' one stays at the begining of the array and the duplicate goes to the end. So that all first occurrences of the names wil be at the begining of the array and all the doubles, triples etc will will be reordered each time at the end of the array. So that it could potentially arrange alhpabetical multiple items.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
]
Expected result:
Amy Clark Dan Dave Joe Amy - Duplicate Clark - Duplicate Joe - Duplicate Clark - Had a third Joe - Had a third
As you can see it orders the first occurrence of all names alphabetically. Then orders the second occurrence alphabetically, and then the third. Until all duplicates are resolved.
After talking in ments it has e to my understanding that it cannot be done in an array.sort function alone. Sort alone with a pare function seems to be great for single or grouping doubles but not for putting doubles at the end of the array.
I am trying to sort an array of objects by a property. I run:
array.sort(function(a, b){
var textA = a.name.toUpperCase();
var textB = b.name.toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1: 0
});
To alphabetically sort the array objects first and then I run an array.sort with a custom pare function as below:
array.sort(function(a, b){
if(a.name === b.name){
return -1;
}
return 1;
});
It seems to work with anything object that does not have a duplicate, however, as soon as there are doubles it pushes them all to the end of the array instead of just the extras.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'}
{name: 'Joe'},
{name: 'Joe'}
]
Expected Output:
- Amy
- Clark
- Dan
- Dave
- Joe
- Amy
- Clark
- Joe
Actual Result:
- Dan
- Dave
- Amy
- Amy
- Clark
- Clark
- Joe
- Joe
Sort Code To try and get Expected Result
array.sort(function(a,b){
if(a.name === b.name){return -1}
return 1;
});
I have a feeling the array.sort with a pare function can handle this however I keep playing with return values of 0, -1, 1 and cannot seem to get it to work fully as I would like.
Update
Expected Result Criteria:
If an object has the same name the duplicate should go to the end of the array. For example if there are two 'Amy' one stays at the begining of the array and the duplicate goes to the end. So that all first occurrences of the names wil be at the begining of the array and all the doubles, triples etc will will be reordered each time at the end of the array. So that it could potentially arrange alhpabetical multiple items.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
]
Expected result:
Amy Clark Dan Dave Joe Amy - Duplicate Clark - Duplicate Joe - Duplicate Clark - Had a third Joe - Had a third
As you can see it orders the first occurrence of all names alphabetically. Then orders the second occurrence alphabetically, and then the third. Until all duplicates are resolved.
After talking in ments it has e to my understanding that it cannot be done in an array.sort function alone. Sort alone with a pare function seems to be great for single or grouping doubles but not for putting doubles at the end of the array.
Share Improve this question edited Nov 10, 2018 at 14:20 halfer 20.4k19 gold badges109 silver badges202 bronze badges asked Dec 29, 2017 at 15:30 L1ghtk3iraL1ghtk3ira 3,1999 gold badges38 silver badges70 bronze badges 8- 1 what is the wanted output? the expected output? – Nina Scholz Commented Dec 29, 2017 at 15:34
- 2 Wait why should the second "Amy" be so far down in the list? The name "Amy" should be before "Dan" and "Joe" in all cases, or else the question doesn't make sense. – Pointy Commented Dec 29, 2017 at 15:35
- See stackoverflow./a/6712058/6383857 – StaticBeagle Commented Dec 29, 2017 at 15:36
- That is only single sort alphabetically. This is alphabetical with duplicates at end of the array. – L1ghtk3ira Commented Dec 29, 2017 at 16:20
- @L1ghtk3ira "duplicates at the end of the array" — what does that mean? Like, in exact terms, what is it that you expect? – Pointy Commented Dec 29, 2017 at 16:36
5 Answers
Reset to default 6Your parator function is incorrect. The function must:
- Return a negative number when the first argument should sort before the second;
- Return a positive number when the first argument should sort after the second;
- Return zero when the two items have equivalent sort keys.
Because yours is not consistent, the sort process gets confused. For your case, the simplest thing to use is the .localeCompare()
function, which returns exactly the sort of result you need:
array.sort(function(a, b) { return a.name.localeCompare(b.name); });
From your "expected output", your ordering criteria are unclear. In any case, the sort parator, whatever it does, has to be consistent: when two items are passed to it in either order, the function should report the same ordering.
edit if the original ordering has some semantic meaning, and the "doubles" (I'd call them "duplicates") should sort further down in the array, you can add another property to each object that captures that original status:
var keyMap = {};
array.forEach(function(item) {
if (item.name in keyMap)
item.position = ++keyMap[item.name];
else
keyMap[item.name] = item.position = 1;
});
Now you can sort:
array.sort(function(a, b) {
var c = a.position - b.position;
if (c) return c;
return a.name.localeCompare(b.name);
});
If the "position" values are the same, the items will be ordered by name. Items that were duplicates in the original array will be sorted after items that weren't (and triplicates will be sorted after those, etc).
You could use sorting with map by using a temporary object with a hash table for the same group array. Take from it the length of the used array as group for sorting.
The sorting happens with the group and index.
The result is mapped with index of the sorted temporary array.
Tge first part generates an array with an index of the original array and their group which is taken from pushing a value into the same group. Actually we need oly the array length after pushing of the group. If more items are in the same group, the items will be sorted later.
[ { index: 0, // Amy group: 1 }, { index: 1, // Amy group: 2 }, { index: 2, // Dan group: 1 }, { index: 3, // Joe group: 1 }, { index: 4, // Joe group: 2 } ]
The above given array is then sorted by group and index, both ascending.
At the last part, a new array is mapped with the index value of the sorted array.
var array = [{ name: 'Amy' }, { name: 'Amy' }, { name: 'Dan' }, { name: 'Joe' }, { name: 'Joe' }],
groups = Object.create(null),
result = array
// this part is only necessary if the names should be in ascending order
// for keeping the given order, remove the part until next ment
.sort(function (a, b) {
return a.name.localeCompare(b.name);
})
// remove until here, if necessary
.map(function (a, i) {
return { index: i, group: (groups[a.name] = groups[a.name] || []).push(0) };
})
.sort(function (a, b) {
return a.group - b.group || a.index - b.index;
})
.map(function (o) {
return array[o.index];
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Example for unsorted data.
var array = [{ name: 'Joe', i: 0 }, { name: 'Dan', i: 1 }, { name: 'Amy', i: 2 }, { name: 'Joe', i: 3 }, { name: 'Amy', i: 4 }],
groups = Object.create(null),
result = array
.map(function (a, i) {
return {
index: i,
group: (groups[a.name] = groups[a.name] || []).push(0),
value: a.name
};
})
.sort(function (a, b) {
return a.group - b.group || a.value.localeCompare(b.value);
})
.map(function (o) {
return array[o.index];
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may join duplicates first and count their occurences:
const array = [
{name: 'Amy'},
{name: 'Amy'},
{name: 'Dan'},
{name: 'Joe'},
{name: 'Joe'}
];
const counted = [], byName = {};
for(var {name} of array){
if(byName[name]){
byName[name].count++;
}else{
counted.push(byName[name] = {name, count:1});
}
}
Now that the names are unique we can sort them alphabetically:
counted.sort((a, b) => a.name.localeCompare(b.name));
Finally, we need to spread the names again:
const result = [];
while(counted.length){
for(var i = 0; i < counted.length; i++){
const name = counted[i];
result.push(name.name);
name.count--;
if(!name.count){
counted.splice(i, 1);
i--;
}
}
}
function pareSimple(a, b) {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
}
function pareAlphabetic(a, b) {
return pareSimple(a.name.toUpperCase(), b.name.toUpperCase());
}
let input = [
{ name: 'Amy' },
{ name: 'Amy' },
{ name: 'Clark' },
{ name: 'Clark' },
{ name: 'Dan' },
{ name: 'Clark' },
{ name: 'Dave' },
{ name: 'Joe' },
{ name: 'Joe' },
];
let output = input
.sort(pareAlphabetic)
.reduce(function(acc, curr) {
let rank = 0
let prev = acc.length > 0 ? acc[acc.length-1] : null
if (prev && pareAlphabetic(prev.value, curr) === 0) {
rank = prev.rank + 1
}
acc.push({ value: curr, rank: rank });
return acc
}, [])
// now we have an array like this
// [
// { value: Amy, rank: 0},
// { value: Amy, rank: 1},
// { value: Clark, rank: 0},
// ...]
// Now let's sort it by rank AND name
.sort(function(a, b) {
let result = pareSimple(a.rank, b.rank);
if (result !== 0) {
return result;
}
return pareAlphabetic(a.value, b.value);
})
// we have to unpack it all back:
.map(function(obj) {
return obj.value;
});
console.log(output);
// [ { name: 'Amy' },
// { name: 'Clark' },
// { name: 'Dan' },
// { name: 'Dave' },
// { name: 'Joe' },
// { name: 'Amy' },
// { name: 'Clark' },
// { name: 'Joe' },
// { name: 'Clark' } ]
A little late to the party but this should definitely do it:
var arr = [
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'}
];
const o = arr.reduce(
(acc,item)=>{
(acc[item.name]===undefined)
? acc[item.name]=1
: acc[item.name]+=1
return acc;
}
,{}
);
const highest = Object.keys(o).reduce(
(acc,item)=>
(o[item]>acc)
? o[item]
: acc
,1
);
const sort = (all,level=1,results=[]) => {
const dub = [""," - Duplicate"," - Had a third"," - Had a fourth"];
if(level>highest){
return results;
}
results = results.concat(
all.filter(
item=>
//if you don't want Clark and Joe to show up 3 times:
// level===1
// ? level<=o[item]
// : level===o[item]
level<=o[item]
)
.filter((item,index,all)=>all.indexOf(item)===index)
.sort()
.map(x=>
x+
(dub[level-1]===undefined
? " - more than four"
: dub[level-1]
)
)
);
return sort(all,level+1,results)
}
console.log(
sort(
arr.map(x=>x.name)
)
);