Group by Object ID's in Javascript - javascript

I have an array of ID's and organizations like so:
var ids = ['1','2', '3'];
var orgs =
[
{ name: "Org 1", id: 1 },
{ name: "Org 2", id: 2 },
{ name: "Org 3", id: 2 }
]
I want to loop through these to output something like this:
{
1: [
{name: "Org 1", id: 1}
],
2: [
{name: "Org 2", id: 2},
{name: "Org 3", id: 2}
]
}
I tried this without success:
var results = orgs.forEach(function (org) {
if (results[org.id]) {
results.push(org)
} else {
results[org.id] = [org]
};
});

If you don't want to use a library like Underscore, Ramda, or Lo-Dash, then it's simple enough to write this using reduce:
var results = orgs.reduce(function(results, org) {
(results[org.id] = results[org.id] || []).push(org);
return results;
}, {})

you should use underscore and just return your id
http://underscorejs.org/#groupBy
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
// => {1: [1.3], 2: [2.1, 2.4]}
you might also want to take a look at lo-dash

Related

Most efficient way to sort a collection of arrays based off the order of a seperate array

Yes I am aware of similar questions but it doesn't quite cover my use case here.
I'm having trouble finding a way to bring the time complexity of this down
I have two objects like so
const people = [
{
name: 'Steve',
id: 1,
fruitInBasket: 6
},
{
name: 'James',
id: 2,
fruitInBasket: 4
}
]
const homes = [
{
id: 1,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Steve',
id: 1
}
]
},
{
id: 2,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Steve',
id: 1
}
]
}
]
so one is a collection of people with a count of fruit in a basket and the other is a collection of homes and within each home is the same users as in the people collection.
Now I want to order the users in each home based on the count of fruitInBasket so I have done this
// create an empty table to store the order of the people
let orderTable = {};
// order the people based off the count in fruitInBasket using lodash orderBy
people = orderBy(people, ['fruitInBasket'], ['desc']);
// create the table
orderTable = people.reduce((acc, item, index) => {
return {
...acc,
[item.id]: index
}
}, {});
// order the people in each home based on the order in the `orderTable`
homes.forEach((home) => {
let members = [];
home.familyMembers.forEach((member) => {
let i = orderTable[member.id];
members[i] = member;
});
home.familyMembers = members;
})
so immediately you can see a nested for loop - which is never ideal.. but I can't figure out a way around this. This method has to sort through quite a lot of data and I am noticing huge performance issues.
Any help would be appreciated!
This should be O(N log N). Its performance bottleneck is the one time sort. Everything else is just O(N) iteration. Some microoptimizations still possible.
Generate an ordered map lookup table.
Just move arrays based on that mapping.
There is a hpc sort library that is measured around 10-40x faster than built-in JavaScript on benchmarks that can be added to increase performance.
I'm not sure, but is the familyMember table the same for every home object? Can you not just copy the same familyMember array to every object or do they have different properties?
Extra optimization per home could be to convert above table to index to index mapping, so that native level array indexing will be used for subsequent ordering.
const orderMap = Object.fromEntries(people.sort((x,y)=>x.fruitInBasket-y.fruitInBasket).map(({id},i)=>[id,i]))
// O(N)+O(NlogN)
homes.forEach(home=>{
const {familyMembers:fms} = home
const arr = new Array(fms.length)
//may want to prefill to maintain PACKED array: https://v8.dev/blog/elements-kinds#avoid-creating-holes
for(const fm of fms) arr[ orderMap[fm.id] ] = fm
home.familyMembers = arr
})
// map lookup O(N)
console.log(homes)
<script>
const people = [
{
name: 'Steve',
id: 1,
fruitInBasket: 6
},
{
name: 'James',
id: 2,
fruitInBasket: 9
}
]
const homes = [
{
id: 1,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Steve',
id: 1
}
]
},
{
id: 2,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Steve',
id: 1
}
]
}
]
</script>
You can filter and sort:
const people = [
{
name: 'Steve',
id: 1,
fruitInBasket: 6
},
{
name: 'James',
id: 2,
fruitInBasket: 4
},
{
name: 'Cesar',
id: 3,
fruitInBasket: 14
}
]
const homes = [
{
id: 1,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Cesar',
id: 3
},
{
name: 'Steve',
id: 1
}
]
},
{
id: 2,
familyMembers: [
{
name: 'James',
id: 2
},
{
name: 'Steve',
id: 1
}
]
}
]
homes.forEach(function(home){
home.familyMembers.sort((a,b)=>people.find(x=>x.id == a.id).fruitInBasket - people.find(x=>x.id == b.id).fruitInBasket)
})
console.log(homes)
Explanations:
You will iterate through homes:
homes.forEach(function(home){
You will sort the members:
.familyMembers.sort((a,b)
To sort, you have to get the fruits of the members, so you find the correct ID then take the correct property:
people.find(x=>x.id == a.id).fruitInBasket
Then you compare:
(a,b)=>people.find(x=>x.id == a.id).fruitInBasket - people.find(x=>x.id == b.id).fruitInBasket
If what you're looking for is performance, you should change people strucutre:
const people = {
1: {
name: 'Steve',
fruitInBasket: 6
},
2: {
name: 'James',
fruitInBasket: 4
}
}
This way you can retrieve fruits more easily:
people[id].fruits
Also, if your "id" is defined somewhere, don't define it in another place. Your homes should look like this:
const homes = {
1: {
familyMembers: [1, 2, 3]
},
2: {
familyMembers: [1,2]
}
}
So your algorithm goes like this:
const people = {
1: {
name: 'Steve',
fruitInBasket: 6
},
3: {
name: 'James',
fruitInBasket: 4
},
2: {
name: 'Cesar',
fruitInBasket: 9114
}
}
const homes = {
1: {
familyMembers: [1, 2, 3]
},
2: {
familyMembers: [1,2]
}
}
Object.keys(homes).forEach(function(k){
homes[k].familyMembers.sort((a,b)=>people[a].fruitInBasket - people[b].fruitInBasket)
})
console.log(homes)

Creating nested json object based on given condition

I have a flat list (array of objects), like next one:
var myList = [
{id:1, name:"ABC", type:"level_1"},
{id:2, name:"XYZ", type:"level_1"},
{id:1, name:"ABC_level 2", type:"level_2", level_one_id:1},
{id:2, name:"XYZ_level 2", type:"level_2", level_one_id:2},
{id:1, name:"ABC_level 3", type:"level_3", level_two_id:1},
{id:2, name:"XYZ_level 3", type:"level_3", level_two_id:2},
];
Then, I have to group them in such a way that I can create a hierarchy of levels (which I tried to do in the below lines of code):
var myList = [
{id:1, name:"ABC", type:"level_1"},
{id:2, name:"XYZ", type:"level_1"},
{id:1, name:"ABC_level 2", type:"level_2", level_one_id:1},
{id:2, name:"XYZ_level 2", type:"level_2", level_one_id:2},
{id:1, name:"ABC_level 3", type:"level_3", level_two_id:1},
{id:2, name:"XYZ_level 3", type:"level_3", level_two_id:2},
];
var myNestedList = {
levels: []
};
//-----------pushing level1----------
myList.forEach((res => {
if (res.type == "level_1") {
myNestedList.levels.push(res);
}
}));
//-----------pushing level 2---------
myNestedList.levels.forEach((res) => {
myList.forEach((val) => {
if (val.type == "level_2" && val.level_one_id == res.id) {
res["level_2"] = [] || res["level_2"];
res["level_2"].push(val);
}
})
})
//-----------pushing level 3---------
myNestedList.levels.forEach((res) => {
res["level_2"].forEach((val) => {
myList.forEach((lastlevel) => {
if (lastlevel.type == "level_3" && lastlevel.level_two_id == val.id) {
val["level_3"] = [] || val["level_3"];
val["level_3"].push(lastlevel);
}
})
})
})
console.log(myNestedList);
Although I'm able to achieve the result, I'm sure this code can be more precise and meaningful. Can we make use of lodash here and get this code shorter?
Any help would be much appreciated. Thanks!
You could take a virtual unique id for the object and for referencing the parents and collect the items in a tree.
This approach works with unsorted data as well.
var data = [{ id: 1, name: "ABC", type: "level_1" }, { id: 2, name: "XYZ", type: "level_1" }, { id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1 }, { id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2 }, { id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1 }, { id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2 }],
tree = function (data) {
var t = {};
data.forEach(o => {
var level = o.type.match(/\d+$/)[0],
parent = o[Object.keys(o).filter(k => k.startsWith('level_'))[0]] || 0,
parentId = `${level - 1}.${parent}`,
id = `${level}.${o.id}`,
children = `level_${level}`;
Object.assign(t[id] = t[id] || {}, o);
t[parentId] = t[parentId] || {};
t[parentId][children] = t[parentId][children] || [];
t[parentId][children].push(t[id]);
});
return t['0.0'].level_1;
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I can't make any sense of this data representing a real tree. But I can see it turning into something like a list of lists, one for each base id, something like this:
[
[
{id: 1, name: "ABC", type: "level_1"},
{id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1},
{id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1}
],
[
{id: 2, name: "XYZ", type: "level_1"},
{id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2},
{id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2}
]
]
If that format is useful, then this code could help you get there:
// utility function
const group = (fn) => (xs) =>
Object .values (xs .reduce ((a, x) => ({...a, [fn (x)]: (a [fn (x)] || []) .concat (x)}), {}))
// helper function
const numericSuffix = str => Number (str .type .match (/(\d+)$/) [1])
// main function -- I don't have a sense of what a good name for this would be
const foo = (xs) =>
group (o => o.id) (xs)
.map (x => x .sort ((a, b) => numericSuffix(a) - numericSuffix(b)))
// data
const myList = [{id: 1, name: "ABC", type: "level_1"}, {id: 2, name: "XYZ", type: "level_1"}, {id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1}, {id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2}, {id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1}, {id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2}]
// demo
console .log (foo (myList))
.as-console-wrapper {max-height: 100% !important; top: 0}
We use a custom group function as well as one that extracts the numeric end of a string (to be used in sorting so that level_10 comes after level_9 and not before level_2) group could be replaced by Underscore, lodash or Ramda groupBy functions, but you'd probably then have to call Object.values() on the results.
The main function groups the data on their ids, then sorts the group by that numeric suffix.
Note that this technique only makes sense if there is only one element for a given id at any particular level. If there could be more, and you really need a tree, I don't see how your input structure could determine future nesting.

Accessing nested array in a json

let ages = data
.filter(isDog)
.map(dogYears)
.reduce(sum);
mL/hr
i want to find the best way of accessing array elements in a javascript object.
Eg: I want to find the first faculty name & first specializations for each course.
var students =
{
deptartment:[
{
name:'Computer Science',
age:20,
Course:[
{ id: 100000
name:'Object Oriented Programming',
faculty:[
{
id:123,
name:'John',
Specialization: [
{name: 'science'},
{name: 'Physics'}
]
}
]
},
{ id: 100001
name:'C#',
faculty:[
{
id:124,
name:'Denis',
Specialization: [
{name: 'Ecnonomics'},
{name: 'Physics'}
]
}
]
}
],
}
]
};
I know i can get the faculty name and specialization by
var courses= deptartment && deptartment.Course ;
var facultyWithSpecialization= {};
if(courses){
courses.forEach(course =>{
var fname = course.faculty && course.faculty[0].name;
var s= course.faculty && course.faculty.Specialization;
facultyWithSpecialization[fname] = s && s[0].name;
})
}
use Object.assign({}, deptartment.Course) instead of department.Course
tried to use the below code but it doesn't make much difference.
var courses=Object.values(Object.assign({}, deptartment.Course));
var fname = Object.values(Object.assign({}, course.faculty[0].Specialization[0]));
Expecting
'John': 'science'
'Denis': 'Ecnonomics'
You can try this. There were many error in the object including spelling mistakes and formatting
var students = {
deptartment: [{
name: 'Computer Science',
age: 20,
Course: [{
id: 100000,
name: 'Object Oriented Programming',
faculty: [{
id: 123,
name: 'John',
Specialization: [{
name: 'science'
},
{
name: 'Physics'
}
]
},
{
id: 124,
name: 'Denis',
Specialization: [{
name: 'Ecnonomics'
},
{
name: 'Physics'
}
]
}
]
}],
}]
}
var obj = {};
students.deptartment.forEach((e) => {
e.Course.forEach((k) => {
k.faculty.forEach((l) => {
obj[l.name] = l.Specialization[0].name
})
})
})
console.log(obj)
I think you meant department instead of deptartment.
I modified a bit your JSON as it was a bit buggy:
var students = {
departments:[
{
name:'Computer Science',
age:20,
Courses:[
{ id: 100000,
name:'Object Oriented Programming',
faculty:[
{
id:123,
name:'John',
Specialization: [
{name: 'science'},
{name: 'Physics'}
]
},
{
id:124,
name:'Denis',
Specialization: [
{name: 'Ecnonomics'},
{name: 'Physics'}
]
}
]
}
],
}]
}
You can use map to achieve this nesting:
students.departments.map(
department => department.Courses.map(
course => course.faculty.map(
student => ({
name: student.name,
specialization: student.Specialization[0].name // check nulls here!
})
)
)
)

Search/Filter nested Array Javascript/Lodash

For my React.js project I would like to create a search-filter of a nested Array. Users will search with an input-field.
var dataExample = [
{
type: "human", details: [
{id: 1, name: "Peter", description: "friendly, black-hair"},
{id: 5, name: "Susan", description: "blond"}
]
},
{
type: "animal", details: [
{id: 2, name: "Will", description: "lazy, cute"},
{id: 3, name: "Bonny", description: "beautiful"}
]
}
];
In my search-input-field I want to look for "name" or something in "description". The data structure of the array should remain the same.
The output when I'm searching for "friendly" or "Peter" should be:
[
{
type: "human", details: [
{id: 1, name: "Peter", description: "friendly, black-hair"}
]
}
];
Now I tried something like this:
let myfilter = dataExample.filter((data) => {
data.details.filter((items) => {
return (items.type.indexOf("human") !== -1 || //input of user
items.description.indexOf("friendly"))
})
})
Unfortunately, this is not how it works. Can anybody help me? Lodash would be no problem, too. Thank you so much.
You can use array#reduce with array#filter and to check for your word you can use string#incldues.
const dataExample = [ { type: "human", details: [ {id: 1, name: "Peter", description: "friendly, black-hair"}, {id: 5, name: "Susan", description: "blond"} ] }, { type: "animal",details: [ {id: 2, name: "Will", description: "lazy, cute"}, {id: 3, name: "Bonny", description: "beautiful"} ] } ],
term = 'Peter',
result = dataExample.reduce((r, {type,details}) => {
let o = details.filter(({name,description}) => name.includes(term) || description.includes(term));
if(o && o.length)
r.push({type, details : [...o]});
return r;
},[]);
console.log(result);
Here are some examples without lodash.
var dataAll = [
{
type: "human",
details: [
{id: 1, name: "Peter", description: "friendly, black-hair"},
{id: 5, name: "Susan", description: "blond"}
]
},
{
type: "animal",
details: [
{id: 2, name: "Will", description: "lazy, cute"},
{id: 3, name: "Bonny", description: "beautiful"}
]
}
];
var entryTypeFilter = data => data.type.indexOf("hum") !== -1;
var entryDetailDescFilter = data => data.description.indexOf("friend") !== -1;
var entryDetailsMapper = data => {
return {
type: data.type,
details: data.details.filter(entryDetailDescFilter)
};
};
var entryNoDetailsFilter = data => data.details && data.details.length !== 0;
var dataFilteredByType = dataAll.filter(entryTypeFilter);
var dataFilteredByDesc = dataAll.map(entryDetailsMapper);
var dataFilteredByTypeAndDesc = dataAll.filter(entryTypeFilter).map(entryDetailsMapper);
var dataFilteredByDescTrimmingEmptyDetailEntries = dataAll.map(entryDetailsMapper).filter(entryNoDetailsFilter);
In modern javascript you might want to search on how to use the ... keyword for the mapping callback functions.

Categorize Similar items into separate objects from Array lists

I have an array of items that I get from API as a response body.
data = [{id: 1, category: "kitchen", name: "noodles"},
{id: 2, category: "general", name: "Wi-Fi"},
{id: 3, category: "sports", name: "Football"},]
I want to iterate over the arrays, and get the data like :
var categorized = {
kitchen: [{id: 1, category: "kitchen", name: "noodles"}],
general : [{id: 2, category: "general", name: "Wi-Fi"}],
sports : [{id: 3, category: "sports", name: "Football"}]
};
Is there any lodash methods, or any ES6 shortcuts for this ?
In answer to your question 'is there a lodash method?' Yes: https://lodash.com/docs/4.17.4#groupBy. For your specific example:
const categorized = _.groupBy(data, 'category');
Edit: You could roll your own groupBy type function with ES6 as in another example. But if you are using lodash anyway this is a whole lot cleaner.
I used array.reduce to get the structure
var data = [{
id: 1,
category: "kitchen",
name: "noodles"
}, {
id: 2,
category: "general",
name: "Wi-Fi"
}, {
id: 3,
category: "sports",
name: "Football"
}]
var newData = data.reduce(function(obj, v, i) {
obj[v.category] = obj[v.category] || [];
obj[v.category].push(v);
return obj;
}, {});
console.log(newData);
In ES6 you could so using:
var newData = data.reduce((obj, v, i)=> {
obj[v.category] = obj[v.category] || [];
obj[v.category].push(v);
return obj;
}, {});
console.log(newData);

Categories