Related
I have an object with a structure like below
const data = [
{ academicYearId: 1, classLevelId: 1, subjectId: 1, ...},
{ academicYearId: 1, classLevelId: 1, subjectId: 2, ...},
{ academicYearId: 1, classLevelId: 1, subjectId: 3, ...},
,,,
]
I need to create a function that will return unique columns e.g
const uniqueColumns = ( val, columns)=> {
//
}
const val = [
{ id: 1, name: 'n1', val: 1 },
{ id: 1, name: 'n1', val: 2 },
{ id: 2, name: 'n2', val: 1 },
{ id: 3, name: 'n2', val: 2 }
]
let result = uniqueColumns(val)
console.log(val)
/**
* Expected
* [{ id: 1, name: 'n1'}, { id: 2, name: 'n2'}, { id: 3, name: 'n2'}]
*/
}
I have tried to look at the various answers in the post How to get distinct values from an array of objects in JavaScript? and I have managed to come up with the below
const uniqueColumns = (val, columns) =>
([...new Set(
val.map(item =>
columns.reduce((prev, next) =>
({[next]: item[next], ...prev}), {})
).map(item => JSON.stringify(item)))
].map(item => JSON.parse(item)))
const val = [
{ id: 1, name: 'n1', val: 1 },
{ id: 1, name: 'n1', val: 2 },
{ id: 2, name: 'n2', val: 1 },
{ id: 3, name: 'n2', val: 2 }
]
const result = uniqueColumns(val, ['id', 'name'])
console.log(result)
What I was inquiring is if there is a better approach instead of having to Convert Object to string and back to object to achieve this
You can use array reduce method.
const val = [
{ id: 1, name: "n1", val: 1 },
{ id: 1, name: "n1", val: 2 },
{ id: 2, name: "n2", val: 1 },
{ id: 3, name: "n2", val: 2 },
];
const uniqueColumns = (val, columns) => {
let ret = val.reduce((p, c) => {
let obj = {};
columns.forEach((x) => (obj[x] = c[x]));
let key = Object.values(obj);
if (!p[key]) p[key] = obj;
return p;
}, {});
return Object.values(ret);
};
const result = uniqueColumns(val, ["id", "name"]);
console.log(result);
Let's say I have below array :
[{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
I want this :
[{
id: 1,
name: "header"
}, {
id: 2,
name: "section",
child: [{
{
id: 3,
name: "input"
},
{
id: 5,
name: "image"
},
}],
}, {
id: 7,
name: "header"
}, {
id: 8,
name: "section",
child: [{
{
id: 9,
name: "input"
},
{
id: 10,
name: "date"
},
}]
}]
if I find start_section and end_section then it will form a new object , How do I change the array by grouping by the key specified in the example above in javascript?
If I get it right, you want something like this? It's simple approach with for loop and some flags:
const arr = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
];
// Set final array
let finalArray = [];
// Set sub object for groups (Childs)
let subObj = {};
// Flag for sub section stuff
let inSubSection = false;
// Loop array
for(let i = 0; i < arr.length; i++) {
if(arr[i].name === "end_section") {
// If we have end_section
// Set flag off
inSubSection = false;
// Push sub object to final array
finalArray.push(subObj);
} else if(arr[i].name === "start_section") {
// If we get start_section
// Set flag on
inSubSection = true;
// Set new sub object, set childs array in it
subObj = {
id: arr[i].id,
name: "section",
child: []
};
} else if(inSubSection) {
// If we have active flag (true)
// Push child to section array
subObj.child.push({
id: arr[i].id,
name: arr[i].name
});
} else {
// Everything else push straight to final array
finalArray.push(arr[i]);
}
}
// Log
console.log(finalArray);
you can Array.reduce function
let array = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
let outPut = array.reduce( (acc, cur, i, arr) => {
if (cur.name == "start_section") {
//find the end element
let endIndex = arr.slice(i).findIndex( e => e.name == "end_section") + i ;
//splice the child elements from base array
let child = arr.splice(i + 1, endIndex - 1 );
//remove last element that has "end_section"
child.splice(-1);
//append child
cur.child = child;
//sert the name as "section"
cur.name = "section";
}
//add to accumulator
acc.push(cur);
return acc;
}, []);
console.log(outPut);
Assign at least one unique item to each users at random. e.g if 5 items & 5 users, each users get 1 items. if 4 items & 5 users, only 4 users get 1 items each. if 7 items & 4 users, then 3 users get 2 items each & 1 user get 1 item and so on. The script below was meant to achieved this but got a wrong result.
assignItemsToUsers() {
let items = [
{
id: 1,
name: "item1",
tag: 1900
},
{
id: 2,
name: "item2",
tag: 1876
},
{
id: 3,
name: "item3",
tag: 1575
},
{
id: 4,
name: "item4",
tag: 4783
},
{
id: 5,
name: "item5",
tag: 67894
},
{
id: 6,
name: "item6",
tag: 66789
},
{
id: 7,
name: "item7",
tag: 67890
},
{
id: 8,
name: "item8",
tag: 87654
},
{
id: 9,
name: "item9",
tag: 94948
}
];
let users = [
{
id: 1,
name: "user1",
userID: 38494
},
{
id: 2,
name: "user2",
userID: 84844
},
{
id: 3,
name: "user3",
userID: 47483
},
{
id: 4,
name: "user4",
userID: 83735
}
];
let results = [];
let tempResults = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const randomUser = users[Math.floor(Math.random() * users.length)];//pick user at random
randomUser.tempitem = item;//asign item to this user
tempResults.push(randomUser);//push to tempResults
}
//Group assigned items to their respective user
for (let i = 0; i < users.length; i++) {
const user = users[i];
let myluckyitems = [];
for (let index = 0; index < tempResults.length; index++) {
//check where tempResults[index].id == user.id
const assigneduser = tempResults[index];
if (user.id == assigneduser.id) {
//True: this was the assigned user.
myluckyitems.push(assigneduser.tempitem);
}
}
//all items assigned, then append to user object & push to results
if (myluckyitems.length > 0) {
user.items = myluckyitems;
results.push(user);
}
}
console.log(results);
}
Actual Result
results = [
{
id: 1,
name: user1,
userID: 38494,
items: [
{
id: 1,
name: item1,
tag: 1900
},
{
id: 1,
name: item1,
tag: 1900
}
],
tempitem: {
id: 1,
name: item1,
tag: 1900
},
},
{
id: 2,
name: user2,
userID: 84844,
items: [
{
id: 2,
name: item2,
tag: 1876
},
{
id: 2,
name: item2,
tag: 1876
},
{
id: 2,
name: item2,
tag: 1876
},
],
tempitem: {
id: 2,
name: item2,
tag: 1876
},
},
{
id: 3,
name: user3,
userID: 47483,
items: [
{
id: 3,
name: item3,
tag: 1575
},
{
id: 3,
name: item3,
tag: 1575
}
],
tempitem: {
id: 3,
name: item3,
tag: 1575
},
},
{
id: 4,
name: user4,
userID: 83735,
items: [
{
id: 4,
name: item4,
tag: 4783
},
{
id: 4,
name: item4,
tag: 4783
}
],
tempitem: {
id: 4,
name: item4,
tag: 4783
},
}
];
Expected Results
results = [
{
id: 1,
name: user1,
userID: 38494,
items: [
{
id: 1,
name: item1,
tag: 1900
},
{
id: 5,
name: item5,
tag: 67894
},
{
id: 9,
name: item9,
tag: 94948
}
],
},
{
id: 2,
name: user2,
userID: 84844,
items: [
{
id: 2,
name: item2,
tag: 1876
},
{
id: 6,
name: item6,
tag: 66789
},
],
},
{
id: 3,
name: user3,
userID: 47483,
items: [
{
id: 3,
name: item3,
tag: 1575
},
{
id: 7,
name: item7,
tag: 67890
},
],
},
{
id: 4,
name: user4,
userID: 83735,
items: [
{
id: 4,
name: item4,
tag: 4783
},
{
id: 8,
name: item8,
tag: 87654
},
],
}
];
The difference is that, the actual results returns repeated items for each users and not all items were assigned to users, it also appends the tempitem property to each user which is not needed.
The expected results, assigns unique items to each users without repetition and also ensure that an item must be assigned to a user.
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function assign() {
const itemsLength = items.length;
for (var i=itemsLength+1, shuffledArray=[]; i--;) shuffledArray.push(i);
shuffledArray = shuffleArray(shuffledArray);
const eachUserItems = itemsLength/users.length;
Object.entries(users).map(([key, value]) => {
for(i = 0; i < Math.round(eachUserItems); i++) {
users[key]['items'] = users[key]['items'] || [];
var popNewIndex = shuffledArray.pop();
var objectToInsert = items.find((i) => i.id == popNewIndex + 1);
users[key]['items'].push(objectToInsert);
}
});
return users;
}
This would give you expected items always assigned randomly to users
ps.
Also simple idea here is that rather than randomising on every loop we would make a random list of values that we would pop one by one. We can use randomise value to fetch value out of list by id and assign it. As list is supposed to have unique values which are randomised it would make sure no value is assigned again to any other user.
You can do something like that (excuse me for possible mistakes, I didnt test it)
const users = [...]
const items = [...]
const results = []
const random_func = () => {
let available_users = [...users]
items.forEach(item => {
// if available users are zero, re-populate the array (it means that everyone has a item
if(available_users.length === 0) available_users = [...users]
//get a random user from available users array
const random_user_index = Math.floor(Math.random() * available_users.length)
const temp_user = available_users(random_user_index)
//remove user from available users
available_users.splice(i, 1);
//check if user already exists into results array
if(results.length > 0) {
objIndex = results.findIndex((obj => obj.id == temp_user.id));
if(objIndex === -1) results.push({...temp_user, items: [{...item}]}
else results[index].items.push(item)
}else {
results.push({...temp_user, items: [{...item}]}
}
})
}
Check the following code. It's tested and produces the expected result.
const minItems = Math.round(items.length / users.length);
const assignedItems = [];
const usersWithItems = users.map((user) => {
user.items = new Array(minItems).fill(0).map(() => {
let isNew = false;
let item;
do {
item = items[Math.floor(Math.random() * items.length)];
if(!assignedItems.includes(item.id))
isNew = true;
} while(isNew == false);
if(item){
assignedItems.push(item.id);
return item;
}
});
return user;
})
const balanceItems = items.filter(item => !assignedItems.includes(item.id));
balanceItems.forEach((item) => {
const randomUserNo = Math.floor(Math.random() * users.length);
usersWithItems[randomUserNo].items.push(item);
})
console.log(usersWithItems);
Lets's try to simplify your task, lets imagine you have unknown number of elements
const items = [1, 5, 6, 7, 3, 4, 2, 3, 2];
and unknown number of users
const users = [{ name: "Tom", items: []}, { name: "John", items: [] }, name: "Steve", items: [] }];
and you have to assign those items to user so your code would be
const items = [1, 5, 6, 7, 3, 4, 2, 3, 2];
const users = [{ name: "Tom", items: []}, { name: "John", items: [] }, { name: "Steve", items: [] }];
let currentUserIndex = 0;
while (items.length) {
const randomIndex = Math.floor(Math.random() * items.length);
users[currentUserIndex].items.push(items[randomIndex]);
items.splice(randomIndex, 1);
if (currentUserIndex === users.length - 1) {
currentUserIndex = 0;
} else {
currentUserIndex++;
}
}
I have a specific case and I don't even know if it is possible to achieve.
Given the input array.
var originalArr = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[{ ID: 1, name: 'Lettuce' }, { ID: 2, name: 'Brocoli' }]
];
I would like to iterate over the inner arrays and pick the ID's from objects then create new one in place of array. So my output should look something like this.
var output = [
{
'1': {
name: 'Lettuce',
ID: 1
},
'2': {
name: 'Brocoli',
ID: 2
}
},
{
'3': {
name: 'Beef',
ID: 3
},
'4': {
name: 'Macaroni',
ID: 4
},
'5': {
name: 'Sauce#1'
}
}
];
It is easy to iterate over the inner arrays with map but how can I create new Object in place of the array and have its key value dynamically pulled up ? And is it even possible given my input to produce the desired output.
Use map and reduce
originalArr.map( s => //iterate outer array
s.reduce( (acc, c) => ( //iterate inner array using reduce
acc[c.ID] = c, acc //assign the id as key to accumulator and return the accumulator
) , {}) //initialize accumulator to {}
)
Demo
var originalArr = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[{ ID: 1, name: 'Lettuce' }, { ID: 2, name: 'Brocoli' }]
];
var output = originalArr.map( s => s.reduce( (acc, c) => ( acc[c.ID] = c, acc ) , {}) );
console.log(output);
You can achieve using recursion with pure javascript
var originalArr = [
[{
ID: 3,
name: 'Beef'
}, {
ID: 4,
name: 'Macaroni'
}, {
ID: 5,
name: 'Sauce#1'
}],
[{
ID: 1,
name: 'Lettuce'
}, {
ID: 2,
name: 'Brocoli'
}]
]
function bindInObject(object, array) {
for (var i = 0; i < array.length; i++) {
var it = array[i];
if (it instanceof Array) {
bindInObject(object, it);
} else {
var id = it.ID;
object[id] = it;
}
}
}
var output = {};
bindInObject(output, originalArr);
console.log(output)
const original_array = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[
{ ID: 1, name: 'Lettuce' },
{ ID: 2, name: 'Brocoli' }
]
]
let new_array = []
for (let i=0; i < original_array.length; i++) {
if (original_array[i + 1]) new_array =
new_array.concat(original_array[i].concat(original_array[i+1]))
}
let output = []
for (let i=0; i<new_array.length; i++) {
output.push({[new_array[i].ID]: new_array[i]})
}
My code:
function convert(arr, parent) {
var out = [];
for(var i in arr) {
if(arr[i].parent == parent) {
var children = convert(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out; //return Object.assign({}, out);tried this, but i lose parents childrens arrays
};
arras = [
{id: 1, name: "parent1", parent: null},
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
{id: 4, name: "parent2", parent: null},
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
console.log(convert(arras, null));
How final result should look
{
parent1: [
{name: "children1"},
{name: "children2"}
],
parent2: [
{name: "children3},
{name: "children4"}
]
}
What my output looks so far:
[
{id: 1, name: "parent1", parent: null}: [
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
],
{id: 4, name: "parent2", parent: null}: [
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
]
So firstly, what I have to do is convert main array to object, when I tend to do that, I lose both parent object arrays...Also need to change the way console displays objects, any help is appreciated.
You could build a tree with check if parent is a root node or not.
var data = [{ id: 1, name: "parent1", parent: null }, { id: 2, name: "children1", parent: 1 }, { id: 3, name: "children2", parent: 1 }, { id: 4, name: "parent2", parent: null }, { id: 5, name: "children3", parent: 4 }, { id: 6, name: "children4", parent: 4 }],
tree = function (data, root) {
var r = {},
o = {};
data.forEach(function (a) {
if (a.parent === root) {
r[a.name] = [];
o[a.id] = r[a.name];
} else {
o[a.parent] = o[a.parent] || [];
o[a.parent].push({ name: a.name });
}
});
return r;
}(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this.
function convert(arr) {
var parents = {};
for (var i in arr) {
if (arr[i].parent === null) {
parents[arr[i].id] = arr[i].name
}
}
var out = {}
for (i in arr) {
if (arr[i].parent !== null) {
var parentName = parents[arr[i].parent];
if (out.hasOwnProperty(parentName)) {
out[parentName].push(arr[i].name)
} else {
out[parentName] = [arr[i].name]
}
}
}
return out;
};
arras = [{
id: 1,
name: "parent1",
parent: null
},
{
id: 2,
name: "children1",
parent: 1
},
{
id: 3,
name: "children2",
parent: 1
},
{
id: 4,
name: "parent2",
parent: null
},
{
id: 5,
name: "children3",
parent: 4
},
{
id: 6,
name: "children4",
parent: 4
}
]
//console.log(convert(arras, null));
alert(JSON.stringify(convert(arras)));
But notice for multilevel it doesn't work correctly. If your need it, your must save map for all possible parent list
arras.forEach(function(el){
if(el.parent){
el.parent=arras.find(e=>e.id==el.parent)||(console.error("no parent:"+el.parent),undefined);
}
});
//resolved parent/childs....
var newmodel = arras.reduce(function(obj,el){
if(el.parent){
//child
obj[el.parent.name]=obj[el.parent.name]||[];//create new parent if neccessary
obj[el.parent.name].push({name:el.name});
}else{
//parent
obj[el.name]=obj[el.name]||[];
}
return obj;
},{});
http://jsbin.com/renicijufi/edit?console
Another way:
var arrays = [
{id: 1, name: 'parent1', parent: null},
{id: 2, name: 'children1', parent: 1},
{id: 3, name: 'children2', parent: 1},
{id: 4, name: 'parent2', parent: null},
{id: 5, name: 'children3', parent: 4},
{id: 6, name: 'children4', parent: 4}
];
// First, reduce the input arrays to id based map
// This step help easy to select any element by id.
arrays = arrays.reduce(function (map, el) {
map[el.id] = el;
return map;
}, {});
var result = Object.values(arrays).reduce(function (result, el) {
if (!el.parent) {
result[el.name] = [];
} else {
result[arrays[el.parent].name].push(el.name);
}
return result;
}, {});
console.log(result);
I think this meets your requirement
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push(person);
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Edit :
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push({name : person.name});
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Hope this helps. :)