Related
I have the following object:
{
4: {
1: [
{ order: 1, name: 'Test 4' }
]
},
0: {
15: [
{ order: 7, name: 'Test 1' },
{ order: 3, name: 'Test 3' },
],
12: {
{ order: 1, name: 'Test 2' }
}
}
}
Essentially what I am trying to achieve is to order this by the keys and then order further by the order property from within the nested value. So in turn I get the following output:
{
0: {
12: {
{ order: 1, name: 'Test 2' }
},
15: [
{ order: 3, name: 'Test 3' },
{ order: 7, name: 'Test 1' },
]
},
4: {
1: [
{ order: 1, name: 'Test 4' }
]
}
}
I then want to completely flatten this so it's without any of the outer object and just the data within the order, the outcome would then be:
[
{ name: 'Test 2' },
{ name: 'Test 3' },
{ name: 'Test 1' },
{ name: 'Test 4' }
]
I imagine this would be some kind of recursive operation which I need to do and I originally did it with something like the following but it got a bit messy:
Object.keys(obj)
.sort()
.reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {});
Anotner one sorting approach
const obj = {4:{1:[{order:1,name:'Test 4'}]},0:{15:[{order:7,name:'Test 1'},{order:3,name:'Test 3'},],12:[{order:1,name:'Test 2'}]}};
const result = Object.entries(obj).flatMap(([u1, v1]) =>
Object.entries(v1).flatMap(([u2, v2]) =>
v2.map((v3) => ({ key: u1*1_000 + u2 + v3.order/1_000, item: v3 }))
)
)
.sort(({ key: a }, { key: b }) => a - b)
.map(({ item }) => item);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
You can sort each obj by keys using Object.keys(obj).sort() and then access each element by its key.
Do this 2 times to get the array of object
const obj = {
4: {
1: [
{ order: 1, name: 'Test 4' }
]
},
0: {
15: [
{ order: 7, name: 'Test 1' },
{ order: 3, name: 'Test 3' },
],
12: [
{ order: 1, name: 'Test 2' }
]
}
}
let flatItems = []
const keys = Object.keys(obj).sort()
for (const key of keys){
const subObj = obj[key]
const subKeys = Object.keys(subObj).sort()
for(const subKey of subKeys){
flatItems = flatItems.concat(subObj[subKey].sort((a, b) => a.order - b.order))
}
}
console.log(flatItems)
The integer properties (in the range of 32 bit unsigned integers) don't need sorting, as iteration over them (e.g. via Object.values) is by specification already sorted by those integer keys. So the logic needs to focus only on sorting the inner objects, and it will be fine.
const flatSort = obj => Array.isArray(obj)
? [...obj].sort((a, b) => a.order - b.order).map(a => a.name)
: Object.values(obj).flatMap(flatSort);
const obj = { 4: { 1: [ { order: 1, name: 'Test 4' } ] }, 0: { 15: [ { order: 7, name: 'Test 1' }, { order: 3, name: 'Test 3' }, ], 12: [ { order: 1, name: 'Test 2' } ] } };
const res = flatSort(obj);
console.log(res);
Object keys can’t easily be sorted, the iteration order depends on different rules and isn’t straightforward to work with. As a result you’re better off converting your object into an array of key-value pair arrays (entries), and then sort that array rather than trying to sort your object (sorting is required as your object can have non-array index keys such as -1).
To do this, you can create a recursive function (sortEntries) that takes your object and grabs the entries from it. Using the key component of the entries, you can sort based on that. Once you've sorted the entries, you can .flatMap() the result of recursively sorting your nested object value by calling the sortEntries function again. For the base case (termination case), you can stop the recursion once you've found a value that is an array, which for that you can sort by the order property for your objects and then .map() each object to extract only the name property. Each array returned by .map() will be merged together into one resulting array due to the previous .flatMap() call:
const obj = { 4: { 1: [ { order: 1, name: 'Test 4' } ] }, 0: { 15: [ { order: 7, name: 'Test 1' }, { order: 3, name: 'Test 3' }, ], 12: [ { order: 1, name: 'Test 2' } ] } };
const sortEntries = (obj) => {
return Array.isArray(obj)
? obj.slice().sort((a, b) => a.order - b.order).map(({name}) => ({name}))
: Object.entries(obj).sort(([a], [b]) => a - b).flatMap(([, val]) => sortEntries(val));
}
const res = sortEntries(obj);
console.log(res);
Full Key/Value object sorting (array values included):
function sortKeys(object) {
if (Array.isArray(object))
return object.sort().map((obj) => (typeof obj === "object" ? sortKeys(obj) : obj));
const sortedObj = {};
const keys = Object.keys(object).sort();
for (var index in keys) {
const key = keys[index];
if (typeof object[key] === "object") {
sortedObj[key] = sortKeys(object[key]);
} else {
sortedObj[key] = object[key];
}
}
return sortedObj;
}
I've got objects with an id as a string. Each object can be the child of another object. Relations can be guessed from IDs. For exemple:
[
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
]
In this exemple, root object is id: 1, which has 2 childrens id: 1:2 and id: 1:4. Finaly, id: 1:2 has a children id: 1:2:6.
I would like to convert this array to another array where childrens are embeded into parents, so the previous array would result in:
[
{
id: '1',
children: [
{
id: '1:2',
children: [
{ id: '1:2:6', children: [] }
],
},
{
id: '1:4',
children: [],
}
],
}
]
I can use ES6. I tried for hours to find a solution using all sort of loops but I can't figure this out. Any help would be appreciated!
You could iterate the objects and reduce ids by looking for an object at the actual level. If not found create a new object. Then return the children.
var data = [{ id: '1:2:6', ids: ['1', '2', '6'] }, { id: '1:4', ids: ['1', '4'] }, { id: '1', ids: ['1'] }, { id: '1:2', ids: ['1', '2'] }],
tree = data.reduce((r, { ids }) => {
ids.reduce((t, _, i, a) => {
var id = a.slice(0, i + 1).join(':'),
temp = t.find(o => o.id === id);
if (!temp) t.push(temp = { id, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think the iterative approach is more readable so maybe provided solution will help you understand how it is done (though it is lightly inspired by Nina's answer)
So what we do is iterate over all objects in the list.
Initially, for each object, we set the current nodeList as the final tree. And we iterate over the length of ids.
First, we create the id form the list of ids by dividing the ids array into incrementally larger chunks with slice (['1'], ['1','2'], ['1', '2', '6') and concatenate to a string with :. So we get 1, 1:2, 1:2:6 ids for the first item.
Next, we find a node in currentNodelist by previously constructed id. If we cannot find the node that means we have not added it yet so we need to create and add it (If we find it then we don't need to do add it).
In the next step, we need to go deeper into the tree so we assign the currently created(or the one that we found) node's children as currentNodelist. With this, we traverse the tree deeper by provided ids.
let objs = [
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
];
let tree = [];
for (let i = 0; i < objs.length; i++) {
let obj = objs[i];
let currentNodeList = tree;
for (let j = 0; j < obj.ids.length; j++) {
let id = obj.ids.slice(0, j + 1).join(':');
let currentNode = currentNodeList.find((node) => node.id === id);
if (!currentNode) {
currentNode = {id, children: []};
currentNodeList.push(currentNode);
}
currentNodeList = currentNode.children;
}
}
console.log(tree);
I created a simple gif that shows what is happening in the first 2 iterations. The arrow is pointing to currentListNode.
Figure out your algorithm "on paper" first. Let's start with an empty tree and take the first entry:
[1, 2, 6].
Node 1: add 1 to the tree - it's now the root and the last visited node.
Node 2: add child 2 to node 1.
Node 6: add child 6 to node 2.
When processing the next entry, [1, 4], 1 is already in the tree - just add 4 to it.
When processing the last entry, [1, 2], be mindful that 2 is also already in the tree.
P.S. It's "children" not "childs".
Building a tree using recursive algorithms
var jsonTree = [{ id: '1:2:6', ids: ['1', '2', '6'] },{ id: '1:4', ids: ['1', '4'] },{ id: '1', ids: ['1'] },{ id: '1:2', ids: ['1', '2'] },]
var newJsonTree = [];
var currentElement = {id: '1',childs: []}
newJsonTree.push(currentElement)
function buildTree(jsonTree, currentElement){
for(var i=0;i<jsonTree.length;i++){
var parent = jsonTree[i];
for(var j=0;j<jsonTree.length;j++){
var child = jsonTree[j];
if(child['visited'] != true && child['id'] != currentElement['id'] && child['id'].indexOf(currentElement['id']) == 0 ){
if(child['id'].split(":").length == currentElement['id'].split(":").length+1){
var newElement = {}
newElement['id'] = child['id'];
newElement['childs'] = [];
currentElement['childs'].push(newElement);
child['visited'] = true;
buildTree(jsonTree, newElement);
}
}
}
}
}
buildTree(jsonTree, currentElement);
document.write(JSON.stringify(newJsonTree));
result:
[{"id":"1","childs":[{"id":"1:4","childs":[]},{"id":"1:2","childs":[{"id":"1:2:6","childs":[]}]}]}]
I have an array of multi dimensional objects:
var arr = [
{
id: '10c',
name: 'item 1'
children: [
{id: '11v', name: 'Item 1 child 1'},
{id: '12c', name: 'Item 1 child 2'}
]
},
{
id: '13v',
name: 'item 2'
children: [
{id: '26e', name: 'Item 2 child 1'},
{id: '7a', name: 'Item 2 child 2'}
]
}
]
and another object of data:
var array = [
{id: '12c', name: 'New name 1'},
{id: '26e', name: 'New name 2'},
{id: '11v', name: 'New name 3'},
];
If I want to update the name value of the respective objects in arr, based on the id value in array, how would be the best way of doing that?
arr might be more than 2 levels deep, so I would like to be able to do it without having to nest multiple forEach
Try this:
function update(items, id, name) {
var item;
for (var i = 0; i < items.length; i++) {
item = items[i];
if (item.id === id) {
item.name = name;
return;
}
if (item.children) {
update(item.children, id, name);
}
}
}
Then:
update(arr, '11v', 'test');
Updating by array:
array.forEach(function (item) {
update(arr, item.id, item.name);
});
Use a recursive solution and make use of the inherent mutability of Javascript.
First build up a flat map representation of all the objects.
function createObjectMap(array) {
var map = {};
array.forEach(function(obj) {
map[obj.id] = obj;
if(obj.children) {
Object.assign(map, createObjectMap(obj.children));
}
});
return map;
}
Now you've got a map of id -> object you can use. They reference the same mutable objects, so you can mutate them in place and know that they'll also update in the original data structure.
function update(map, updates) {
updates.forEach(function(update) {
map[update.id].name = update.name;
});
}
This conversion to O(1) lookup time means you don't have to write any searching logic.
You could use angular $filter to do the same
//to get element
function getFiltered(collection, obj){
var filtered = $filter('filter')(collection, obj, true);
if(filtered[0])
return filtered[0]
if(collection.children)
return getFiltered(collection.children, obj, true);
}
angular.forEach(array, function(element){
var filtered = getFiltered(array, { id: element.id })
if(filtered[0]) filtered.name = element.name;
})
I have an array. I need to group this array by groups and sort by position. I tied to create a new array with group names as keys and values as sorted array grouped by group, but didn't work well. How can I do this?
a = [
{id:1,name:'qw'group:'C',name:'hite',position:'1'},
{id:2,name:'qwe'group:'B',name:'ite',position:'2'},
{id:3,name:'qwer'group:'A',name:'ite',position:'3'},
{id:4,name:'qer'group:'D',name:'te',position:'4'},
{id:5,name:'wer'group:'C',name:'whit',position:'5'},
{id:6,name:'er'group:'B',name:'whi',position:'6'},
]
function groupDo(array){
var groups = [];
for (var i in array){
groups[array[i].group] = array[i].group;
}
for (var i in array){
if (groups[array[i].group] == array[i].group){
groups[array[i].group] = array[i];
}
}
}
Here's a simple straight forward answer:
var sortByPosition = function(obj1, obj2) {
return obj1.position - obj2.position;
};
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = {};
for (var i = 0; i < arr.length; i += 1) {
if(!grouped[arr[i].group]) {
grouped[arr[i].group] = [];
}
grouped[arr[i].group].push(arr[i]);
}
for (var group in grouped) {
grouped[group] = grouped[group].sort(sortByPosition);
}
console.log(grouped);
When you want to do stuff like this though, it's usually recommended to use a utility library like lodash or underscore.js, so that you don't have to "reinvent the wheel". Here's how it would look like using one of these libraries:
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = _.groupBy(arr, 'group');
for (var group in grouped) {
_.sortBy(grouped[group], 'position');
}
console.log(grouped);
Here ya go!
a = [
{id:1,name:'qw',group:'C',name:'hite',position:'1'},
{id:2,name:'qwe',group:'B',name:'ite',position:'2'},
{id:3,name:'qwer',group:'A',name:'ite',position:'3'},
{id:4,name:'qer',group:'D',name:'te',position:'4'},
{id:5,name:'wer',group:'C',name:'whit',position:'5'},
{id:6,name:'er',group:'B',name:'whi',position:'6'},
]
function groupAndSort(array, groupField, sortField) {
var groups = {}; // This object will end being keyed by groups, and elements will be arrays of the rows within the given array, which have been sorted by the sortField
// Put all the rows into groups
for (var i = 0; i < array.length; i++) {
var row = array[i];
var groupValue = row[groupField];
groups[groupValue] = groups[groupValue] || [];
groups[groupValue].push(row);
}
// Sort each group
for (var groupValue in groups) {
groups[groupValue] = groups[groupValue].sort(function(a, b) {
return a[sortField] - b[sortField];
});
}
// Return the results
return groups;
}
var groupedAndSorted = groupAndSort(a, "group", "position");
If you want to group objects, first think about what the resulting data would look like. Maybe something like this?
var grouped = {
A : [
{id:3,name:'qwer', group:'A',name:'ite',position:'3'}
],
B : [],
C : [],
D : []
};
And so on. To transform a list into an object, consider using .reduce().
.reduce() takes a function as its first argument, and a resulting object as the second. The function iterates through each element of the array and reduces it into the given object.
var data = [
{id:1,name:'qw', group:'C',name:'hite',position:'1'},
{id:2,name:'qwe', group:'B',name:'ite',position:'2'},
{id:3,name:'qwer', group:'A',name:'ite',position:'3'},
{id:4,name:'qer', group:'D',name:'te',position:'4'},
{id:5,name:'wer', group:'C',name:'whit',position:'5'},
{id:6,name:'er', group:'B',name:'whi',position:'6'},
]
// acc is the accumulated object, x is each element of the array
data.reduce(function(acc, x) {
// first check if the given group is in the object
acc[x.group] = acc[x.group] ? acc[x.group].concat(x) : [x];
return acc;
}, {}); // this is the resulting object
Now all you need to do is use the built in sort to order the resulting arrays. You could do this by iterating through the keys of the resulting object and applying .sort() to each array. .sort() takes a function as an argument which accesses the data and provides a comparison function.
// a and b are elements of the array
array.sort(function(a, b) {
if (a.position > b.position) {
return -1;
} else if (b.position > a.position) {
return 1;
} else {
return 0;
}
});
And you would implement it like so
var result = Object.keys(data).map(function(d){
return d.sort(f); // f is the function above
});
I am trying to push objects into my array. The problem is some objects are duplicated.
My codes are as following:
var obj = {
'obj1 {
'id':'1', 'title':'title 1'
},
'obj2 {
'id':'2', 'title':'title 2'
},
'obj3 {
'id':'3', 'title':'title 3'
}, //duplicated
'obj3 {
'id':'3', 'title':'title 3'
},
'obj4 {
'id':'4', 'title':'title 4'
}
// and many more..
}
var arr= [];
for (i in obj){
arr.push(obj[i])
}
I am not sure how to find out the duplicated obj and only push the unique objects into my arr.
Can someone help me out? Thanks a lot!
If your objects are stored in an array (it's hard to tell via your example) you could use the following function to get unique objects from that array based on one or more properties of the objects stored:
// get unique object members by property name(s)
function unique(arr, props) {
var results = [],
seen = [];
for (var i=0; i < arr.length; i++){
var key = "";
for (var j=0; j < props.length; j++) {
key += "" + arr[i][props[j]];
}
if (seen.indexOf(key) == -1) {
seen.push(key);
results.push(arr[i]);
}
}
return results;
}
var obj = [
{ 'id': 1, 'title': 'title 1' },
{ 'id': 2, 'title': 'title 2' },
{ 'id': 3, 'title': 'title 3' },
{ 'id': 3, 'title': 'title 3' },
{ 'id': 4, 'title': 'title 4' }
];
var results = unique(obj, [ 'id', 'title' ]);
// results => [ obj[0], obj[1], obj[2], obj[4] ]
You can dedupe this way if performance isn't an issue.
var deduped = {};
for (var i in obj) {
deduped[JSON.stringify(obj[i])] = obj[i];
}