javascriptsortinggrouping

Sort array on two different values and keep flat array structure


I have this array

const rows = [
    {data: [{text: "A",value: 100}, {text: "B",value: 74}], group: "Elephant"},
    {data: [{text: "C",value: 63}, {text: "D",value: 1}], group: "Elephant"},
    {data: [{text: "E",value: 37}, {text: "F",value: 54}], group: "Penguin"},
    {data: [{text: "G", value: 72}, {text: "H", value: 74}], group: "Lion"},
    {data: [{text: "K", value: 76}, {text: "L", value: 38}], group: "Zebra"},
    {data: [{text: "M", value: 68}, {text: "N", value: 21}], group: "Lion"},
];

And these sort functions

const _sortFunction = (
    a,
    b,
    columnIndex,
    sortOrder
) => {
    if (a && a.data && b && b.data) {
        if (a.data[columnIndex].value === b.data[columnIndex].value) {
            return 0;
        } else {
            if (sortOrder === "ASC") return a.data[columnIndex].value > b.data[columnIndex].value ? -1 : 1;
            if (sortOrder === "DESC") return a.data[columnIndex].value < b.data[columnIndex].value ? -1 : 1;
            return 0;
        }
    } else {
        return 0;
    }
};

const sortRowsByColumn = (rows, sortProps) => {
    if (sortProps && rows) {
        const {indexOfColumnToSort, sortOrder} = sortProps;
        const rowsCopy = JSON.parse(JSON.stringify(rows))
        return rowsCopy.sort((a, b) => _sortFunction(a, b, indexOfColumnToSort, sortOrder));
    }
    return rows;
};

Which gives me console.log(sortRowsByColumn(rows, {indexOfColumnToSort: 1, sortOrder: "ASC"}))

[
{data: [{text: "A", value: 100}, {text: "B", value: 74}], group: "Elephant"}, 
{data: [{text: "G", value: 72}, {text: "H", value: 74}], group: "Lion"}, 
{data: [{text: "E", value: 37}, {text: "F", value: 54}], group: "Penguin"}, 
{data: [{ text: "K", value: 76}, {text: "L", value: 38}], group: "Zebra"}, 
{data: [{text: "M", value: 68}, {text: "N", value: 21}], group: "Lion"}, 
{data: [{text: "C", value: 63}, {text: "D", value: 1}],group: "Elephant"}
]

However, what I cannot get my head around is the following: The updated sortRowsByColumn() function should

  1. sort the rows array based on the group key (alphabetically)
  2. then sort based on the column index for each group

all of this without loosing the original array structure. Basically like a grouping and sorting for each group, without modifiying the array structure or the original array. The result should look like this

[
{data: [{text: "A",value: 100}, {text: "B", value: 74}], group: "Elephant"}, 
{data: [{text: "C", value: 63}, {text: "D", value: 1}], group: "Elephant"}, 
{data: [{text: "G", value: 72}, {text: "H", value: 74}], group: "Lion"}, 
{data: [{text: "M", value: 68}, {text: "N", value: 21}], group: "Lion"},
{data: [{text: "E",value: 37}, {text: "F", value: 54}], group: "Penguin"}, 
{data: [{text: "K", value: 76}, {text: "L", value: 38}], group: "Zebra"}, 
]

Solution

  • Achieving this is immediate by using a TrueSet and an appropriate representation function.

    To arrange your items first by group and then by data[index].value, simply represent each item (each row) using such a couple of properties: [item.group, item.data[index].value] within a TrueSet. Nothing else is required.

    const
        index = 1,
        repr = item => [item.group, item.data[index].value],
    
        got = TrueSet.of(repr, ORDER.ASCENDING, ORDER.DESCENDING)
            .letAll(data);
    

    Please note that in your _sortFunction() you are mistakenly referring to ASC for what is actually a descending order. Therefore, I utilize the ORDER.DESCENDING comparator for values. However, the groups are sorted using the ORDER.ASCENDING comparator.

    You can check the validity of the proposed solution by:

    import { TrueSet } from "@ut8pia/classifier/queue/TrueSet.js";
    import { ORDER } from "@ut8pia/classifier/global.js";
    import assert from "assert";
    
    const 
            data = [
                {data: [{text: "A",value: 100}, {text: "B",value: 74}], group: "Elephant"},
                {data: [{text: "C",value: 63}, {text: "D",value: 1}], group: "Elephant"},
                {data: [{text: "E",value: 37}, {text: "F",value: 54}], group: "Penguin"},
                {data: [{text: "G", value: 72}, {text: "H", value: 74}], group: "Lion"},
                {data: [{text: "K", value: 76}, {text: "L", value: 38}], group: "Zebra"},
                {data: [{text: "M", value: 68}, {text: "N", value: 21}], group: "Lion"},
            ],
    
            expected = [            
                {data: [{text: "A",value: 100}, {text: "B", value: 74}], group: "Elephant"}, 
                {data: [{text: "C", value: 63}, {text: "D", value: 1}], group: "Elephant"}, 
                {data: [{text: "G", value: 72}, {text: "H", value: 74}], group: "Lion"}, 
                {data: [{text: "M", value: 68}, {text: "N", value: 21}], group: "Lion"},
                {data: [{text: "E",value: 37}, {text: "F", value: 54}], group: "Penguin"}, 
                {data: [{text: "K", value: 76}, {text: "L", value: 38}], group: "Zebra"}, 
            ];
    
    assert.deepEqual(got.toArray(), expected)
    

    It's important to highlight that the TrueSet is a sorted collection - a queue, of simple items. The method toArray() returns your original items, irrespective of their representation in the TrueSet internal tree structure. Nonetheless, such structure - a Classifier, allows grouping and querying your original items based on their representation.

    To become acquainted with the concepts of TrueSet, Classifier, and the representation function, you might want to explore the ut8pia/classifier library.