Reputation: 85
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
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
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
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
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
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