Tengo una gran variedad de objetos, similar a esto:
[ { id: "some_id", timestamp: 12345 }, { id: "some_other_id", timestamp: 12347 }, { id: "some_id", timestamp: 12346 }, { id: "some_other_id", timestamp: 12348 }, ... ]Quiero ordenar la matriz de manera que haya "secciones" de objetos en la matriz, según la identificación que tenga el objeto. Dentro de cada sección, los objetos deben ordenarse de forma ascendente según la marca de tiempo. Las secciones en sí también deben ordenarse según la primera marca de tiempo de la sección. Entonces la matriz debería verse así:
[ // 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 }, ... ]También debería ser posible elegir entre ascender/descender en la función. Ahora mismo tengo esto:
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); } })Sin embargo, esto no ordena las secciones correctamente. ¿Algunas ideas?
No vas a poder hacerlo de una sola manera. Tendrá que ser varios pasos ya que no sabe cuál es el mínimo.
Una forma es combinar, ordenar y luego ordenar según el más bajo.
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);La otra forma es encontrar el más bajo y luego ordenarlo.
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);Esto requerirá dos pasadas a través de la matriz porque no puede ordenar por secciones hasta que sepa cuál debe ser el orden de las secciones y no lo sabrá hasta que haya visto todas las secciones y, por lo tanto, sepa cuál es la más baja o la más alta. la marca de tiempo es para cada sección (dependiendo de si está haciendo una ordenación ascendente o descendente).
Entonces, probablemente lo que tiene sentido es hacer un primer paso que recopile el valor extremo de cada sección y lo almacene en un objeto Map que puede usar como índice de clasificación de sección. Luego, puede ejecutar .sort() . Si las secciones son iguales, ordena por marca de tiempo. Si las secciones no son iguales, ordena por el valor en el índice de la sección.
function sortByIdAndTimestamp(data, sortAscending = true) { // create a Map object where keys are id values and values are the extreme // timestamp for that id const extremeTimestamp = new Map(); for (let item of data) { if (testExtreme(item.timestamp, extremeTimestamp.get(item.id), sortAscending)) { 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 extreme timestamp of the id result = extremeTimestamp.get(b.id) - extremeTimestamp.get(a.id); } if (sortAscending) { result = -result; } return result; }); return data; } function testExtreme(val, extremeSoFar, sortAscending) { // determine if this id's timestamp is more extreme // than what we already have for that section return (extremeSoFar === undefined) || (sortAscending ? val < extremeSoFar : val > extremeSoFar); } 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: "yet_another_id", timestamp: 1 }, { id: "yet_another_id", timestamp: 90000 }, ]; sortByIdAndTimestamp(sampleData, true); console.log(sampleData);Nota: Cuando dijiste que querías ordenar en orden ascendente o descendente, supuse que te referías a ambas secciones y a las marcas de tiempo. Por lo tanto, una ordenación ascendente tendría primero la sección de marca de tiempo más baja y luego los elementos dentro de cada sección se ordenarían por marca de tiempo de menor a mayor. Y, una ordenación descendente tendría primero la sección de marca de tiempo más alta y luego los elementos dentro de cada sección se ordenarían por marca de tiempo de mayor a menor.
EDITADO: aquí hay una forma de hacerlo si entendí correctamente lo que necesita
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: "X_other_id", timestamp: 12343 }, { id: "other_id", timestamp: 12349 } ] const groupById = (acc, item) => { acc[item.id] ? acc[item.id].push(item) : acc[item.id] = [item]; return acc; }; function sort(arr, bool) { const groups = Object.values(arr.reduce(groupById, {})) .map(group => group.sort((a, b) => bool ? a.timestamp - b.timestamp : b.timestamp - a.timestamp)) .sort((a, b) => bool ? a[0].timestamp - b[0].timestamp : b[0].timestamp - a[0].timestamp); return groups.flat() } console.log(sort(data, ascending = true))