简体   繁体   中英

Js: What is the most efficient way to sort an array of objects into sections?

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?

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);

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);

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))

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);

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM