Shawn
Shawn

Reputation: 2366

Javascript sort array by "groups" and maintain order

I have a dataset that looks like this:

[
{
    "name": "Item1",
    "section": "section1",
    "total": 3,
}, {
    "name": "Item1",
    "section": "section2",
    "total": 4,
}{
    "name": "Item1",
    "section": "section3",
    "total": 7,
}, {
    "name": "Item2",
    "section": "section1",
    "total": 1,
}, {
    "name": "Item2",
    "section": "section2",
    "total": 2,
}, {
    "name": "Item2",
    "section": "section3",
    "total": 3,
    }
]

I need to sort the array by only the total value in the section 3 item, but maintain the order (section1, section2, then section 3) per name. So for this example Item2 should move all 3 of it's rows above Item1. I've tried sorting by multiple items, but that doesn't maintain the ordering that I need. Should I just get the smallest/biggest, grab the related items and put them into a new array and repeat or is there a more logical way to accomplish this?

I'm also using angular and primeng grid if there's something I can leverage in there.

Upvotes: 2

Views: 8407

Answers (5)

Nina Scholz
Nina Scholz

Reputation: 386654

You could

  • collect all object of the same group and get the total for sorting,
  • sort by the groups total value,
  • get a flat array of all objects.

const
    data = [{ name: "Item1", section: "section1", total: 3 }, { name: "Item1", section: "section2", total: 4 }, { name: "Item1", section: "section3", total: 7 }, { name: "Item2", section: "section1", total: 1 }, { name: "Item2", section: "section2", total: 2 }, { name: "Item2", section: "section3", total: 3 }],
    result = Object
        .values(data.reduce((r, o) => {
            r[o.name] = r[o.name] || { payload: [] };
            r[o.name].payload.push(o);
            if (o.section === 'section3') r[o.name].total = o.total;
            return r;
        }, {}))
        .sort(({ total: a }, { total: b }) => a - b)
        .flatMap(({ payload }) => payload);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

xdeepakv
xdeepakv

Reputation: 8135

Priority sort

const data = [{"name":"Item1","section":"section1","total":3},{"name":"Item1","section":"section2","total":4},{"name":"Item1","section":"section3","total":7},{"name":"Item2","section":"section1","total":1},{"name":"Item2","section":"section2","total":2},{"name":"Item2","section":"section3","total":3}];
console.log(
  data.sort((a, b) => {
    const diff = a.total - b.total;
    if (diff) return diff;
    return b.section.localeCompare(a.section);
  })
);
.as-console-row {color: blue!important}

Upvotes: 2

Rick Su
Rick Su

Reputation: 16440

You first need to "Group" the data set by name and then do the sorting by total.

let items = [{
  "name": "Item1",
  "section": "section1",
  "total": 3,
}, {
  "name": "Item1",
  "section": "section2",
  "total": 4,
}, {
  "name": "Item1",
  "section": "section3",
  "total": 7,
}, {
  "name": "Item2",
  "section": "section1",
  "total": 1,
}, {
  "name": "Item2",
  "section": "section2",
  "total": 2,
}, {
  "name": "Item2",
  "section": "section3",
  "total": 3,
}];

let groups = {};

for (let item of items) {
  if (!groups[item.name]) {
    groups[item.name] = {
      data: []
    };
  }

  // Grouping by `name`
  groups[item.name].data.push(item);

  // Store the `total`
  if (item.section == "section3") {
    groups[item.name].key = item.total;
  }
}

// sort the groups, this will maintain the order of sections (1,2 and 3) in each group
let sortedGroups = Object.values(groups).sort((a, b) => {
  return a.key - b.key; // ascending
});

// then flatten the groups
let flatten = [].concat(...sortedGroups.map(x => x.data));

console.log(flatten);

Upvotes: 0

Tanmay_vijay
Tanmay_vijay

Reputation: 619

var array = [
{
    "name": "Item1",
    "section": "section1",
    "total": 3,
}, {
    "name": "Item1",
    "section": "section2",
    "total": 4,
},{
    "name": "Item1",
    "section": "section3",
    "total": 7,
}, {
    "name": "Item2",
    "section": "section1",
    "total": 1,
}, {
    "name": "Item2",
    "section": "section2",
    "total": 2,
}, {
    "name": "Item2",
    "section": "section3",
    "total": 3,
    }
];

array = array.sort((o1, o2)=>{

if(o1.section === o2.section && o1.section === 'section3') {
   return o1.total - o2.total;
} else {
  return o1.section === 'section3' ? 1 : -1;
}
});

console.log(array);

Here's the output

[
  {
    "name": "Item1",
    "section": "section1",
    "total": 3
  },
  {
    "name": "Item1",
    "section": "section2",
    "total": 4
  },
  {
    "name": "Item2",
    "section": "section1",
    "total": 1
  },
  {
    "name": "Item2",
    "section": "section2",
    "total": 2
  },
  {
    "name": "Item2",
    "section": "section3",
    "total": 3
  },
  {
    "name": "Item1",
    "section": "section3",
    "total": 7
  }
]

Upvotes: 0

Kurt Hamilton
Kurt Hamilton

Reputation: 13515

I would create a map using the name as the key and the total as the value for items where section equals section3. You can then sort using the map.

This will sort all items by the value of the total in section3, and preserve the original sort order where the sort value matches.

const map = new Map<string, number>(this.data
  .filter(x => x.section === 'section3')
  .map(x => [ x.name, x.total ]));

this.sorted = this.data.slice()
  .sort((a, b) => map.get(a.name) - map.get(b.name));

This does rely on the data being structured and ordered as your have specified in your question.

DEMO: https://stackblitz.com/edit/angular-fsswdq

Upvotes: 3

Related Questions