Categorisation of objects by comparing two objects in javascript - javascript

I am trying to categorise the objects by comparing two objects say data and categories
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category":["A","1a", "2a"],
"B_category":["1b", "2b"],
"C_category":["1c", "2c"],
"D_category":["1d", "2d"]
};
I want to group the data based on the category object, when there is no match the group should be others and the resultant data should be like
const resultData = [
{ group: 'Others', name: '777', count: 456 },
{ group: 'A_category', name: '1a', count: 154 },
{ group: 'B_category', name: '1b', count: 765 },
{ group: 'C_category', name: '1c', count: 7877 }
]
I used the function but not able to achieve the result
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
for (let [key, value] of Object.entries(categories)) {
value.includes(dataKey) ? resultData.push({"group": key,...data[dataKey]}) : resultData.push({"group": "Others",...data[dataKey]}) ;
break;
}
}
)
}
restructure(data,categories);

You can try this as well. Iterate over your data entries and find whether the key exists in any of the categories object data and push it into the array with found category as group or push it with Others as group as shown in the below code
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
Object.entries(data).map(([key, val])=>{
let group = Object.keys(categories).find(category=>categories[category].includes(key)) || 'Others'
resultData.push({
group,
...val
})
})
console.log(resultData)

Instead of for loop you need to use filter as let category = Object.entries(categories).filter(([key, value]) => value.includes(dataKey));.
If category.length > 0 then category is available else use Others.
Try it below.
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
let category = Object.entries(categories)
.filter(([key, value]) => value.includes(dataKey));
resultData.push({
"group": category.length > 0 ? category[0][0] : "Others",
...data[dataKey]
});
})
}
restructure(data, categories);
console.log(resultData);

That's because you're breaking out of the loop regardless of whether you found the category or not. Your for loop will only execute once then breaks immediately. If the first category object matches, it is used, if not "Others" is assigned and the loop exits without checking the rest of the categories. Only break out of the loop if the lookup is successful:
for (let [key, value] of Object.entries(categories)) {
if(value.includes(dataKey)) { // if this is the category
resultData.push({ "group": key, ...data[dataKey] }); // use it ...
return; // ... and break the loop and the current iteration of forEach. The current object is handled
}
}
resultData.push({ "group": "Others", ...data[dataKey] }); // if the return statement above is never reached, that means the category was not found, assign "Others"
BTW, you can use other array methods to shorten things out like so:
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
Then use like so:
const resultData = restructure(data, categories);
My method uses find to try to find a category key that contains the name of the object, if find fails, it returns null at which point, the || "Others" part is evaluated and "Others" will be used as the group name (Does JavaScript have "Short-circuit" evaluation?).
Demo:
const data = {"777":{"name":"777","count":456},"1a":{"name":"1a","count":154},"1b":{"name":"1b","count":765},"1c":{"name":"1c","count":7877}};
const categories = {"A_category":["A","1a","2a"],"B_category":["1b","2b"],"C_category":["1c","2c"],"D_category":["1d","2d"]};
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
const resultData = restructure(data, categories);
console.log(resultData);

Related

want to display all maximum 'levelNumber' of objects in an array

arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
my result array should have objects with max levelNumber i.e 3 in this case.
it should look like:
resultArr = [
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
note that here levelNumber can be anything..
please help me with the generic nodejs code to get duplicate max value objects
You can first find the max level of all the objects in the array and then filter the array
arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
const maxLevel = String(Math.max(...arr1.map(obj => Number(obj.levelNumber))))
const maxLevelObjects = arr1.filter(obj => obj.levelNumber === maxLevel)
console.log(maxLevelObjects);
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
];
const levelNumbers = data.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
const highestLevelItems = data.filter((item) => item.levelNumber == maxLevelNumber);
console.log(highestLevelItems);
/* output
[
{ levelNumber: '3', name: 'abc' },
{ levelNumber: '3', name: 'raks' }
]
*/
EDIT
As #nat mentioned in comment:
if I add one more object in the array, with name = 'raks & levelNumber = '4' then it should display maximum levelNumber wrt that particular name. i.e.
{ "levelNumber": "3", "name": "abc" }, { "levelNumber": "4", "name": "raks" }
To achieve this, you have to:
make a Set of names
make a separate empty array to hold final result
repeat the above process for each name and add result in the array
return complete result
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
},
{
"levelNumber": "4",
"name": "raks",
},
{
"levelNumber": "5",
"name": "raks",
}
];
// 1.
const names = new Set(data.map((item) => item.name)); // Set is used to get only unique items
// 2.
const result = []; // For normal JS
// const result: Array<{levelNumber: string, name: string}> = []; // For TS
// 3.
names.forEach((name) => {
/* minify data (filter items with only particular name) e.g. [{levelNumber: '2', name: 'abc'}, {levelNumber: '3', name: 'abc'}] */
const minifiedData = data.filter((item) => item.name === name);
/* same process, now for minified array */
const levelNumbers = minifiedData.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
minifiedData.forEach((item) => {
if (item.levelNumber == maxLevelNumber)
result.push(item); // push every matching item (item with highest level) in final result
});
});
// 4.
console.log(result);
const arr1 = [
{
levelNumber: '2',
name: 'abc',
},
{
levelNumber: '3',
name: 'abc',
},
{
levelNumber: '3',
name: 'raks',
},
];
const getHighLevelElements = (array) => {
if (array.length === 0) return null;
array.sort((elem1, elem2) => {
if (Number(elem1.levelNumber) < Number(elem2.levelNumber)) {
return 1;
}
if (Number(elem1.levelNumber) > Number(elem2.levelNumber)) {
return -1;
}
return 0;
});
return array.filter((elem) => elem.levelNumber === array[0].levelNumber);
};
const resultArr = getHighLevelElements([...arr1]);
console.log(resultArr);
I would first have a variable called highestLevel to store the highest level number found in the array of objects (will be used later while looping), loop through the whole array and checking every key levelNumber and storing that number IF highestLevel is lower than the value of the current object levelNumber.
After I've looped through the array and got the actual highestLevel number, I would loop through again and only get the objects that are equivalent to my variable highestLevel
You can just iterate one time over arr1 with Array.prototype.reduce()
Code:
const arr1 = [{levelNumber: '2',name: 'abc',},{levelNumber: '3',name: 'abc',},{levelNumber: '3',name: 'raks'}]
const result = arr1.reduce((a, c) => !a.length || +c.levelNumber === +a[0].levelNumber
? [...a, c]
: +c.levelNumber > +a[0].levelNumber
? [c]
: a,
[])
console.log(result)

Loop through an object and only return certain keys together with their values

Given the following object, how can I loop through this object inorder to obtain both keys and values but only for the following keys:
"myName": "Demo"
"active": "Y"
"myCode": "123456789"
"myType": 1
let a = {
"values": {
"myName": "Demo",
"active": "Y",
"myCode": "123456789",
"myType": 1,
"myGroups": [
{
"myGroupName": "Group 1",
"myTypes": [
{
"myTypeName": "323232",
"myTypeId": "1"
}
]
},
{
"myGroupName": "Group 2",
"myTypes": [
{
"myTypeName": "523232",
"myTypeId": "2"
}
]
}
]
}
}
I have tried:
for (const [key, value] of Object.entries(a.values)) {
console.log(`${key}: ${value}`);
For}
but this will return all keys with their values.
You can use a dictionary (array) to contain the keys you want to extract the properties for, and then reduce over the values with Object.entries to produce a new object matching only those entries included in the dictionary.
let a = {
"values": {
"myName": "Demo",
"active": "Y",
"myCode": "123456789",
"myType": 1,
"myGroups": [{
"myGroupName": "Group 1",
"myTypes": [{
"myTypeName": "323232",
"myTypeId": "1"
}]
},
{
"myGroupName": "Group 2",
"myTypes": [{
"myTypeName": "523232",
"myTypeId": "2"
}]
}
]
}
}
const arr = [ 'myName', 'active', 'myCode', 'myType' ];
const out = Object.entries(a.values).reduce((acc, [key, value]) => {
if (arr.includes(key)) acc[key] = value;
return acc;
}, {});
console.log(out);
The best answer would be to set up an array of the desired keys and then iterate over that array instead of an array of the original object's entries. This is how you would achieve that:
let a = {
values: {
myName: "Demo",
active: "Y",
myCode: "123456789",
myType: 1,
myGroups: [{
myGroupName: "Group 1",
myTypes: [{
myTypeName: "323232",
myTypeId: "1"
}]
}, {
myGroupName: "Group 2",
myTypes: [{
myTypeName: "523232",
myTypeId: "2"
}]
}]
}
};
const keys = ['myName', 'active', 'myCode', 'myType'];
const cherryPick = (obj, keys) => keys.reduce((a,c) => (a[c] = obj[c], a), {});
console.log(cherryPick(a.values, keys));
The above example will work for many provided keys. If a key does not exist in the supplied object, its value will be undefined. If you want to only keep properties which have values, simply add an optional filter to the cherryPick() function, like this:
let test = {
a: 1,
b: 2
};
const keys = ['a', 'b', 'c'];
const cherryPick = (obj, keys, filter = 0) => keys.filter(key => filter ? obj[key] : 1).reduce((acc,key) => (acc[key] = obj[key], acc), {});
console.log('STORE undefined :: cherryPick(test, keys)', cherryPick(test, keys));
console.log('FILTER undefined :: cherryPick(test, keys, 1)', cherryPick(test, keys, true));
/* Ignore this */ .as-console-wrapper { min-height: 100%; }

Mapping and filtering a nested object

I'm trying to find all objects that contain filter with the Name of Industry and the Value of Tech. Here's my object:
const obj = [{
"Id": 1,
"Name": "Video Games",
"Labels": [{
"Name": "Industry",
"Value": "TMT"
},
{
"Name": "Analyst",
"Value": "Jen Cardage"
}
]
},
{
"Id": 2,
"Name": "Software",
"Labels": [],
},
{
"Id": 3,
"Name": "Internet",
"Labels": [{
"Name": "Industry",
"Value": "Tech"
},
{
"Name": "Analyst",
"Value": "Mike Smith"
}
]
}
]
This gets me objects with non-empty Labels, in this case, Ids 1 and 3.
const containsLabels = obj.filter(({Labels}) => Labels.length > 0);
However, I can't seem to figure out how to chain another map and/or filter onto containsLabels to map over and check against the Names and Value contained therein.
How can I get the object with Id: 3 out of containsLabels? My running code is here.
Labels is an array (as is obj), so running find() nested should do the trick:
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);
I forked your fiddle with the working version.
It is unnecessary to filter for objects where Label.length > 0, because find() will return null when running on an empty array.
If Labels is not always present, the above code would throw an error. Instead, you can use the optional chaining operator (?.), or the traditional &&:
const containsLabels = obj.filter(({ Labels }) =>
Labels?.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);
// Or...
const containsLabels = obj.filter(({ Labels }) =>
Labels && Labels.find(({ Name, Value }) => Name === 'Industry' && Value === 'Tech')
);
You could do find directly in the first filter
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(Label => Label.Value === 'Tech')
)
console.log(containsLabels)
Runnable example
const obj = [
{
Id: 1,
Name: 'Video Games',
Labels: [
{
Name: 'Industry',
Value: 'TMT'
},
{
Name: 'Analyst',
Value: 'Jen Cardage'
}
]
},
{
Id: 2,
Name: 'Software',
Labels: []
},
{
Id: 3,
Name: 'Internet',
Labels: [
{
Name: 'Industry',
Value: 'Tech'
},
{
Name: 'Analyst',
Value: 'Mike Smith'
}
]
}
]
const containsLabels = obj.filter(({ Labels }) =>
Labels.find(Label => Label.Value === 'Tech')
)
console.log(containsLabels)
Or just continue chaining if you want, because filter returns the new array of the same elements but filtered
const containsLabels = obj
.filter(({ Labels }) => Labels.length > 0)
.filter(({ Labels }) => Labels.find(Label => Label.Value === 'Tech'))

filter object by two nested values

I'm facing a problem with filter method. On my page there's an input to search matches by team names. Filter value is being stored to React state. Matches object looks like this:
[
{
"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": [
{
[...]
}
]
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": [
{
[...]
}
]
},
"localDate": "2020-01-01",
"localTime": "00:00:00",
"referee": null,
"commentator1": null,
"commentator2": null,
"streamer": null,
"stage": {
"id": 2,
"name": "GROUPSTAGE"
},
"onLive": true,
"finished": false
},
]
I tried tons of methods to filter matches by team name, for example:
let criteria = {
teamBlue: {
name: this.state.filter
},
teamRed: {
name: this.state.filter
}
};
let filteredMatches = this.state.matches.filter(function(item) {
for (let key in criteria) {
if (item[key] === undefined || item[key] !== criteria[key])
return false;
}
return true;
});
console.log(filteredMatches);
but none of them worked.
Is there any way to filter these matches so when I type "blue" into my input, it will show all matches where team name contains "blue"?
Thanks in advance!
Try updating the condition to:
if (!item[key] || item[key].name !== criteria[key].name)
let filteredMatches = this.state.matches.filter(function(item) {
let flag = true;
for (let key in criteria) {
// update this to
if (!item[key] || item[key].name !== criteria[key].name)
flag = false;
}
return flag;
});
The name property is missing :
if (key in item && item[key].name !== criteria[key].name)
You're comparing objects with === which will return false. You either need to use a deep comparison method from a library, or implement it yourself like below:
const matches = [ {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": []
},
}, {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRead",
"playerList": []
},
}]
const criteria = {
teamBlue: {
name: 'nameForTeamBlue',
},
teamRed: {
name: 'nameForTeamRed',
}
}
const filteredMatches = matches.filter((item) => {
const allCriteriaMatched = Object.entries(criteria)
.every(([key, value]) => {
const matched = Object.entries(value).every(([criteriaKey, criteriaValue]) => {
const itemValue = item[key][criteriaKey]
const matched = itemValue == criteriaValue
if (!matched) console.log('Item %s does not matched criteria %s. Item\'s value is %s, but criteria value is %s', item[key]['id'], criteriaKey, itemValue, criteriaValue, criteriaValue)
return matched
})
if (!matched) return false
return true
}, {})
return allCriteriaMatched
})
console.log(filteredMatches);
Basically, you just need to go 1 level deeper :D if your criteria can have multiple nested objects, then there's no point doing it manually. You can try to map criteria to run against matches so that you don't use === on objects, but only primitives.

JavaScript - sort 2 object arrays on the same field

In JavaScript I have 2 object arrays that have the same objects but are in a different order. I'm trying to figure out how to sort one array based on the order of the other. There is a unique field they both share (sortField below) I'm just failing on figuring out how to sort with it. Here's an example of my arrays:
sorter array:
[
{
"displayName": "Party",
"sortField": "com.uniqueXbd",
"elementId": "PtyListPanel"
}, {
"displayName": "Group",
"sortField": "com.uniqueARd",
"elementId": "GrpListPaneARd"
}, {
"displayName": "Leader",
"sortField": "com.uniqueEcF",
"elementId": "LeaderListPaneEcF"
}
]
needsSorted array:
[
{
"displayName": "Group",
"sortField": "com.uniqueARd",
"elementId": "GrpListPaneARd"
}, {
"displayName": "Leader",
"sortField": "com.uniqueEcF",
"elementId": "LeaderListPanel"
}, {
"displayName": "Party",
"sortField": "com.uniqueXbd",
"elementId": "PtyListPaneEcF"
}
]
I'm guessing it's going to look something like this?
needsSorted.sort((a, b) => {
if(sorter.sortField...){
return 1
})
Thanks
const output = [];
sortedArray.forEach( sortedItem => {
const matchingItem = unsortedArray.find( unsortedItem => unsortedItem.sortField === sortedItem.sortField );
if(matchingItem){
output.push(matchingItem);
}
});
Since you know the second array is the order you want the items from the first array to be in, you should loop through it. Then find the matching item from the first list, and push it into your output in that order.
You can make a sorting lookup that maps the sort key to the index in the original array. Then in your sort, you can look it up for both objects in the comparison.
This replaces the repeated need to lookup the index in the original array for each comparison with a constant time object lookup so it should be more performant for larger arrays at the expense of the space for the lookup object.
let sortObj = [{"displayName": "Party","sortField": "com.uniqueXbd","elementId": "PtyListPanel"}, {"displayName": "Group","sortField": "com.uniqueARd","elementId": "GrpListPaneARd"}, {"displayName": "Leader","sortField": "com.uniqueEcF","elementId": "LeaderListPaneEcF"}]
let needsSorted = [{"displayName": "Group","sortField": "com.uniqueARd","elementId": "GrpListPaneARd"}, {"displayName": "Leader","sortField": "com.uniqueEcF","elementId": "LeaderListPanel"}, {"displayName": "Party","sortField": "com.uniqueXbd","elementId": "PtyListPaneEcF"}]
let sortLookup = sortObj.reduce((obj, item, idx) => {
obj[item.sortField] = idx
return obj
}, {})
needsSorted.sort((a,b) => sortLookup[a.sortField] - sortLookup[b.sortField])
console.log(needsSorted)
var obj = [
{
"one": 1,
"two": 9
}, {
"one": 3,
"two": 5
}, {
"one": 1,
"two": 2
}
];
var obj = [
{
"one": 1,
"two": 2,
}, {
"one": 1,
"two": 9
}, {
"one": 3,
"two": 5
}
];
obj.sort(function(a, b) {
return a["one"] - b["one"] || a["two"] - b["two"];
});
const sortedIndexes = sorter.map(i => i.sortField);
needsSorted.sort((a, b) => {
const aIndex = sortedIndexes.findIndex((i) => i === a.sortField);
const bIndex = sortedIndexes.findIndex((i) => i === b.sortField);
return aIndex - bIndex;
})
Given that you just want to compare the two arrays and make sure they are still the same, I would go about it differently:
const first = sorted.sort((a, b) => a.localCompare(b))
const second = needsSorting.sort((a, b) => a.localCompare(b))
if (JSON.stringify(first) != JSON.stringify(second)) {
console.log("the array was modified!");
}
const sortOrder = sorted.map(item => item.sortField);
needsSorted.sort((a, b) => {
return sortOrder.indexOf(a.sortField) > sortOrder.indexOf(b.sortField) ? 1 : -1;
});
const fields = sorted.map(x => x.sortField);
const value = x => fields.indexOf(x.sortField);
needSorted.sort((a, b) => value(a) - value(b));
console.log(needSorted);
const sorted = [
{
displayName: "Party",
sortField: "com.uniqueXbd",
elementId: "PtyListPanel"
},
{
displayName: "Group",
sortField: "com.uniqueARd",
elementId: "GrpListPaneARd"
},
{
displayName: "Leader",
sortField: "com.uniqueEcF",
elementId: "LeaderListPaneEcF"
}
];
const needSorted = [
{
displayName: "Group",
sortField: "com.uniqueARd",
elementId: "GrpListPaneARd"
},
{
displayName: "Leader",
sortField: "com.uniqueEcF",
elementId: "LeaderListPanel"
},
{
displayName: "Party",
sortField: "com.uniqueXbd",
elementId: "PtyListPaneEcF"
}
];
const fields = sorted.map(x => x.sortField);
const value = x => fields.indexOf(x.sortField);
needSorted.sort((a, b) => value(a) - value(b));
console.log(needSorted);

Categories

Resources