I have a large array of objects, similar to this:
[
{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 12347
},
{
id: "some_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 12348
},
...
]
I want to sort the array in a way, that there are "sections" of objects in the array, depending which id the object has. Inside each section, the objects should be sorted ascending depending on the timestamp. The sections themselves should also be sorted depending on the first timestamp of the section. So the array should look like this:
[
// section: some_id
{
id: "some_id",
timestamp: 12345
},
{
id: "some_id",
timestamp: 12348
},
// section: some_other_id, comes ofter some_id section because 12346 > 12345
{
id: "some_other_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 12347
},
...
]
It should also be possible to chose between ascending/descending in the function. Right now i have this:
elements.sort((a, b) => {
if (a.id === b.id) {
if (sortAscending) {
return a.timestamp > b.timestamp ? -1 : 1;
} else {
return a.timestamp > b.timestamp ? 1 : -1;
}
} else {
return a.id.localeCompare(b.id);
}
})
This doesn't sort the sections correctly however. Any ideas?
CodePudding user response:
You are not going to be able to do it in one sort. It is going to have to be multiple steps since you do not know what the minimum is.
One way is to combine, sort and than sort based on the lowest.
const data = [
{
id: "a",
timestamp: 4
},
{
id: "b",
timestamp: 3
},
{
id: "a",
timestamp: 2
},
{
id: "b",
timestamp: 1
},
];
const x = data.reduce((a,o) => {
a[o.id] = a[o.id] || [];
a[o.id].push(o);
return a;
}, {});
const v = Object.values(x);
v.forEach(x => x.sort((a,b) => a.timestamp > b.timestamp ? 1 : -1))
v.sort((a,b)=>a[0].timestamp > b[0].timestamp ? 1 : -1);
const sorted = v.flat();
console.log(sorted);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
The other way is find the lowest and then sort with that.
const data = [{
id: "a",
timestamp: 4
},
{
id: "b",
timestamp: 3
},
{
id: "a",
timestamp: 2
},
{
id: "b",
timestamp: 1
},
];
const smallest = data.reduce((a ,o) => {
a[o.id] = Math.min(a[o.id] === undefined? Number.POSITIVE_INFINITY : a[o.id], o.timestamp);
return a;
}, {});
data.sort((a,b) => {
return a.id === b.id ?
(a.timestamp > b.timestamp ? 1 : -1)
: smallest[a.id] > smallest[b.id] ? 1 : -1;
})
console.log(data);
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
You were pretty close with your code. Here's one way to do it if I understood correctly what you need
const data = [{
id: "some_id",
timestamp: 12348
},
{
id: "some_other_id",
timestamp: 12346
},
{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 12347
},
{
id: "other_id",
timestamp: 12343
},
{
id: "other_id",
timestamp: 12349
}
]
function sort(arr, bool) {
arr.sort((a, b) => {
return bool ? a.id.localeCompare(b.id) || a.timestamp - b.timestamp : a.id.localeCompare(b.id) || b.timestamp - a.timestamp
})
return arr
}
console.log(sort(data, ascending=true))
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
This will require two passes through the array because you can't sort by sections until you know what the order of the sections should be and you don't know that until you've seen all the sections and thus know what the lowest or highest timestamp is for each section (depending upon whether you're doing ascending or descending sort).
So, probably what makes sense is to make a first pass that collects the extreme value for each section and stores it into a Map object that you can use as a section sort index. Then, you can run .sort()
. If the sections are the same, you sort by timestamp. If the sections are not the same, you sort by the value in the section index.
function sortByIdAndTimestamp(data, sortAscending = true) {
// create a Map object where keys are id values and values are the extreme
// timestamp for that id (highest or lowest depending upon sort order)
const extremeTimestamp = new Map();
for (let item of data) {
const extremeSoFar = extremeTimestamp.get(item.id);
// determine if this id's timestamp is more extreme
// than what we already have for that section
const needsSet = (extremeSoFar === undefined) || (sortAscending ?
item.timestamp < extremeSoFar :
item.timestamp > extremeSoFar);
// if it is more extreme, save it
if (needsSet) {
extremeTimestamp.set(item.id, item.timestamp);
}
}
// now just do a dual key sort
data.sort((a, b) => {
let result;
if (a.id === b.id) {
// if id is the same, just sort by timestamp
result = b.timestamp - a.timestamp;
} else {
// if id is not the same sort by the timestamp of the id
result = extremeTimestamp.get(b.id) - extremeTimestamp.get(a.id);
}
if (sortAscending) {
result = -result;
}
return result;
});
return data;
}
const sampleData = [{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 123
},
{
id: "some_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 99999
},
{
id: "some_third_id",
timestamp: 1
},
{
id: "some_third_id",
timestamp: 90000
},
];
sortByIdAndTimestamp(sampleData, true);
console.log(sampleData);
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Note: When you said you wanted to sort in either ascending or descending order, I assumed you meant that for both sections and for timestamps. So, an ascending sort would have the lowest timestamp section first and then the items within each section would be sorted by timestamp from low to high. And, a descending sort would have the highest timestamp section first and then items within each section would be sorted by timestamp from high to low.