0

I have an array of objects:

[{
    id : 1,
    tag: "video"
 },
 {
    id : 2,
    tag: "music"
 },
 {
    id : 3,
    tag: "video"
 },
 {
    id : 4,
    tag: "music"
 },
 {
    id : 5,
    tag: "video"
 }]

I want to sort this array based on 2 factors:

  1. The initial position of the element
  2. The tag

Basically I should group the tags together but in the same time keep the order of the items added, the output should look like this:

[{
    id : 1,
    tag: "video"
 },
 {
    id : 3,
    tag: "video"
 },
 {
    id : 5,
    tag: "video"
 },
 {
    id : 2,
    tag: "music"
 },
 {
    id : 4,
    tag: "music"
 }]

As you can see, now they are grouped by tag name but the initial order is kept. The item with id=1 is before the item with id=2 because it was added first, and so on.

Note that I can't use the id field for this sort since the real id is not an integer, it's a unique string which can not be compared. So instead of the id I should use the original position index.

My only solution which does not looks very good is to make a big block of code to make a for on the initial array and create a new array with the items placed in the perfect positions by checking the last item with the same tag that was added in the original array and grab it's position and add it into the new array on that position.

Any optimal solution for this? Thanks

paulalexandru
  • 9,218
  • 7
  • 66
  • 94
  • I've reopened it because, it's not sorted alphabetically. It's more of a grouping then a sorting question. – Ori Drori Oct 04 '17 at 10:40

3 Answers3

1

You can reduce the array into sub arrays in the correct order and then flatten them by applying Array#concat:

var data = [{"id":1,"tag":"video"},{"id":2,"tag":"music"},{"id":3,"tag":"video"},{"id":4,"tag":"music"},{"id":5,"tag":"video"}];

var helper = Object.create(null);
var result = [].concat.apply([], data.reduce(function(r, o) {
  var arr;
  
  if(helper[o.tag] === undefined) {
     helper[o.tag] = r.push([]) - 1;
  }
  
  arr = r[helper[o.tag]];
  
  arr.push(o);
  
  return r;
}, []));

console.log(result);

And an ES6 solution using a Map:

const data = [{"id":1,"tag":"video"},{"id":2,"tag":"music"},{"id":3,"tag":"video"},{"id":4,"tag":"music"},{"id":5,"tag":"video"}];

const result = [].concat(...data.reduce((r, o) => {
  const arr = r.get(o.tag) || [];
  arr.push(o);
  return r.set(o.tag, arr);
}, new Map()).values());

console.log(result);
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • For some odd reason the stackoverflow snippet doesn't run the code / shows the result. The code itself works. – Ori Drori Oct 04 '17 at 12:37
  • The name of the prop doesn't matter (as long as you update the code), and the content doesn't matter as well if it's a primitive (String, Number, etc...). – Ori Drori Oct 04 '17 at 12:45
  • It's not a problem in the code. It's a problem in the stackoverflow snippet sandbox. If it's in the chrome console, it works in chrome :) – Ori Drori Oct 04 '17 at 12:52
  • They are doing the same thing. If browser support is not a problem, I would go with the ES6, since it's more compact, and doesn't require an external helper object. – Ori Drori Oct 04 '17 at 13:06
  • Have you updated all `o.tag` statements to `o.event` statements? - `const result = [].concat(...data.reduce((r, o) => { const arr = r.get(o.event) || []; arr.push(o); return r.set(o.event, arr); }, new Map()).values());` – Ori Drori Oct 04 '17 at 13:11
1

You could use sorting with map and an object for the first appearance of a tag for its position.

The first map

generates an array with index and position poperties, which reflects the first sort by position and the second by index.

[
    {
        index: 0,
        pos: 0
    },
    {
        index: 1,
        pos: 1
    },
    {
        index: 2,
        pos: 0
    },
    {
        index: 3,
        pos: 1
    },
    {
        index: 4,
        pos: 0
    }
]

sort

takes position and index property of the objects and sort the temporary array with it.

The second map

takes the temporary array and renders the result by taking the object from the original array and returns the item at the given index.

var data = [{ id : 1, tag: "video" }, { id : 2, tag: "music" }, { id : 3, tag: "video" }, { id : 4, tag: "music" }, { id : 5, tag: "video" }],
    pos = Object.create(null),
    result = data
        .map(function (o, i) {
            (o.tag in pos) || (pos[o.tag] = i);
            return { index: i, pos: pos[o.tag] };
        })
        .sort(function (a, b) {
            return a.pos - b.pos || a.index - b.index;
        })
        .map(function (o) {
            return data[o.index];
        });

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

var arr = [{
    id : 1,
    tag: "video"
 },
 {
    id : 2,
    tag: "music"
 },
 {
    id : 3,
    tag: "video"
 },
 {
    id : 4,
    tag: "music"
 },
 {
    id : 5,
    tag: "video"
 }]


const result = arr
.map((item, i) => ({ index: i, id: item.id, tag:item.tag }))
.sort((a, b) =>  a.tag < b.tag )

console.log(result)
serkan
  • 6,885
  • 4
  • 41
  • 49