Virat
Virat

Reputation: 85

How to arrange the array objects in a sequence

I am new to javascript programming, I have a scenario as below -

I have a input of array list of elements --> [abc,xyz,123] and output will look as follows in array object

[
{
"label" : "positive",
"id" : "abc"
},
{
"label" : "positive",
"id" : "xyz"
},
{
"label" : "negative",
"id" : "abc"
},

{
"label" : "positive and negative",
"id" : "abc"
},

{
"label" : "neg",
"id" : "123"
},

{
"label" : "positive",
"id" : "xyz"
}
]

Now I will have to sequence the output based on input sequence -

here abc is first element xyz is second element 123 is third element

so my output should something look like, all the id= "abc" should be first then "xyz" objects should display and then "123" objects

[
{
"label" : "positive",
"id" : "abc"
},
{
"label" : "negative",
"id" : "abc"
},

{
"label" : "positive and negative",
"id" : "abc"
},

{
"label" : "positive",
"id" : "xyz"
},
{
"label" : "positive",
"id" : "xyz"
},

{
"label" : "neg",
"id" : "123"
}
]

Any suggestions on how we can achieve this, please

Upvotes: 2

Views: 2134

Answers (5)

Nick Parsons
Nick Parsons

Reputation: 50874

You could first group your array of objects by id using a Map (an object-like data structure that lets you store key-value pairs). The key would be the id, and the values would be an array of objects which have that key as their id. Once you have grouped all objects into a Map, you can use .flatMap() on your array [abc,xyz,123]. Creating the Map first (rather than search for the items for each element in your array) allows you to keep this linear:

const target = ['abc', 'xyz', '123'];
const arr = [{ "label": "positive", "id": "abc" }, { "label": "positive", "id": "xyz" }, { "label": "negative", "id": "abc" }, { "label": "positive and negative", "id": "abc" }, { "label": "neg", "id": "123" }, { "label": "positive", "id": "xyz" } ];

const grouped = arr.reduce((acc, obj) => {
  const seen = acc.get(obj.id) || []; // get the seen array, if it doesn't exist, default it []
  return acc.set(obj.id, seen.concat(obj)); // add the current object to its seen array, and update that Map
}, new Map);

const res =  target.flatMap(id => grouped.get(id)); // convert every value in arr to it's corresponding array from the Map held at `id`
console.log(res);

If you don't feel comfortable using Maps, reduce and flatMap, you might find the below approach more understandable. It employs the same logic as above, just in a more imperative way:

const target = ['abc', 'xyz', '123'];
const arr = [{ "label": "positive", "id": "abc" }, { "label": "positive", "id": "xyz" }, { "label": "negative", "id": "abc" }, { "label": "positive and negative", "id": "abc" }, { "label": "neg", "id": "123" }, { "label": "positive", "id": "xyz" } ];

const grouped = {};
for(const obj of arr) {
  const seen = grouped[obj.id] || [];
  grouped[obj.id] = seen.concat(obj);
}

const res = [];
for(const id of target) {
  res.push(...grouped[id]);
}
console.log(res);

The above works by creating an object called grouped. The idea is to store the id value from each object in arr as a key within this object. An object cannot have duplicate keys though, so if we encounter an object from arr with an id that is already a key in the object, we can instead add the object as a value to the array held at the key. If the id of an object from arr doesn't appear in grouped, we can add it, and set it's value to an array containing the current object. The idea is to create an object of this form:

{ 
  "123": [ { "label": "neg", "id": "123" } ], 
  "abc": [ { "label": "positive", "id": "abc" }, { "label": "negative", "id": "abc" }, { "label": "positive and negative", "id": "abc" } ], 
  "xyz": [ { "label": "positive", "id": "xyz" }, { "label": "positive", "id": "xyz" } ] 
}

While building the above grouped object, this line checks to see if the above grouped object already contains the current object id, and if it does, it sets seen to the array held at that key, otherwise, it sets seen to an empty array:

const seen = grouped[obj.id] || [];

We then update the seen array and the grouped object to add current object:

grouped[obj.id] = seen.concat(obj);

Now that everything is grouped, we can use this object to obtain the objects associated with a particular id using grouped[id]. In this case, we want to convert every id in target to its associated objects. This can be done by looping through the ids in your target array using a for...of loop, and then using the grouped object to grab the objects associated with that id. We can then push all the elements from the array we obtain from grouped into the res array by using:

res.push(...grouped[id]);

This pushes every element from the array returned by grouped[id] into the res array. This is different from using res.push(grouped[id]), as this would push the entire grouped array (not just the elements) into the res array. By using the spread syntax (...), we instead pass the elements from the grouped array as individual arguments to .push(), allowing us to push just the elements.

Upvotes: 3

Lakshya Thakur
Lakshya Thakur

Reputation: 8316

Okay the following might be me getting over-board but this will give you a O(n) time complexity. Here we first build the LinkedList as we loop through your input array. LinkedList helps us with O(1) insertion of the nodes if we insert by the means of node reference. We just keep storing the latest node reference as per the id in a Map.

Finally we convert the entries in that LinkedList to an array.

I haven't thoroughly tested it but I think it should work.

Of course, I am using space to build up additional LinkedList and a Map (which you would have to anyway do with array implementations shared by others);

const input = [{
    "label": "positive",
    "id": "abc"
  },
  {
    "label": "positive",
    "id": "xyz"
  },
  {
    "label": "negative",
    "id": "abc"
  },

  {
    "label": "positive and negative",
    "id": "abc"
  },

  {
    "label": "neg",
    "id": "123"
  },

  {
    "label": "positive",
    "id": "xyz"
  },
]

// Class Begin
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

class LinkedList {
  constructor(data) {
    this.head = new Node(data);
    this.tail = this.head;
  }

  convertToArr() {
    let temp = this.head;
    const output = [];

    while (temp !== null) {
      output.push(temp.data);
      temp = temp.next;
    }

    return output;
  }

  insertAfterNode(node, newNode) {
    let previousNext = node.next;
    node.next = newNode;
    newNode.next = previousNext;
  }


  insertAtEnd(newNode) {
    this.tail.next = newNode;
    this.tail = newNode;
  }

}
// Class End


// Function to transform input to output.
function transform(input) {
  if (!input.length)
    return [];

  const ll = new LinkedList(input[0]);
  const nodeCache = new Map()
  nodeCache.set(input[0].id, ll.head);

  for (let index = 1; index < input.length; index++) {
    const data = input[index];
    const newNode = new Node(data);

    if (nodeCache.has(data.id))
      ll.insertAfterNode(nodeCache.get(data.id), newNode);
    else
      ll.insertAtEnd(newNode);

    nodeCache.set(data.id, newNode);
  }
  const output = ll.convertToArr();
  return output;
}

const result = transform(input);
console.log(result);

Upvotes: 0

Scott Sauyet
Scott Sauyet

Reputation: 50807

This is a somewhat generic solution for the problem. sortByKey takes your array of sortKeys and returns a function that takes your input and sorts by them. sortByKey also optionally takes a function that extracts the key you want to match from your input. The default is to take the id property, but you can overwrite that with whatever you like. You can use it like this:

const sortByKey = (sortKeys, keyGen = ({id}) => id) => (input) => 
  sortKeys .flatMap (key => input.filter (x => keyGen(x) == key))

const sortKeys = ['abc', 'xyz', '123']
const input = [{label: "positive", id: "abc"}, {label: "positive", id: "xyz"}, {label: "negative", id: "abc"}, {label: "positive and negative", id: "abc"}, {label: "neg", id: "123"}, {label: "positive", id: "xyz"}]

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

But if you choose, you can also name the intermediate function, like this:

const mySort = sortByKey (['abc', 'xyz', '123'])
// ... later
mySort (input)

In either case, note that this function returns a new array, sorted the way you want. It does not modify the original array; we're not barbarians here.

Upvotes: 0

Chase
Chase

Reputation: 3126

You are looking for a custom sort function. Your sort function makes decisions based on where an id value was first seen in the original input array.

const input = [
  {label: "positive", id: "abc"},
  {label: "positive", id: "xyz"},
  {label: "negative", id: "abc"},
  {label: "positive and negative", id: "abc"},
  {label: "neg", id: "123"},
  {label: "positive", id: "xyz"},
];

const firstIdPos = input.reduce((a, c, i) => {
  a[c.id] = a[c.id] ?? i;
  return a;
}, {});

console.log({firstIdPos});

console.log(input.sort((a, b) => firstIdPos[a.id] - firstIdPos[b.id]));

Upvotes: 0

Abito Prakash
Abito Prakash

Reputation: 4770

You can do something like this.

We first create a priority map like {abc: 0, xyz: 1, 123: 2} from our required sequence ["abc", "xyz", "123"] and then use this map to sort the items in the array

const data = [{ "label": "positive", "id": "abc" }, { "label": "positive", "id": "xyz" }, { "label": "negative", "id": "abc" }, { "label": "positive and negative", "id": "abc" }, { "label": "neg", "id": "123" }, { "label": "positive", "id": "xyz" } ];
const sequence = ["abc", "xyz", "123"];

const sortData = (list, order) => {
  const priority = {};
  order.forEach((item, index) => (priority[item] = index));
  return list.sort((itemA, itemB) => priority[itemA.id] - priority[itemB.id]);
};

console.log(sortData(data, sequence));

Upvotes: 1

Related Questions