Related
How do I implement this properly?
const tree = buildTree(1, shuffleArray([
{ type: 'string', source_id: 1, name: 'foo', value: 'asdf' },
{ type: 'integer', source_id: 1, name: 'bar', value: 123 },
{ type: 'object', source_id: 1, name: 'nested', value: 2 },
{ type: 'object', source_id: 2, name: 'nested', value: 3, array: true },
{ type: 'boolean', source_id: 3, name: 'random', value: true },
{ type: 'string', source_id: 3, name: 'another', value: 'hello' },
{ type: 'object', source_id: 2, name: 'nested', value: 4, array: true },
{ type: 'boolean', source_id: 4, name: 'random', value: false },
{ type: 'string', source_id: 4, name: 'another', value: 'world' },
{ type: 'object', source_id: 2, name: 'nested', value: 5, array: true },
{ type: 'boolean', source_id: 5, name: 'random', value: true },
{ type: 'string', source_id: 5, name: 'another', value: 'awesome' },
]))
function buildTree(startId, array) {
const map = array.reduce((m, x) => {
m[x.source_id] = m[x.source_id] ?? {}
if (x.array) {
m[x.source_id][x.name] = m[x.source_id][x.name] ?? []
m[x.source_id][x.name].push({ id: x.value })
} else {
m[x.source_id][x.name] = x.value
}
return m
}, {})
// ??? getting lost...
}
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array
}
where the "expected tree" would be something like this:
const expectedTree = {
id: 1,
foo: 'asdf',
bar: 123,
nested: {
id: 2,
nested: [
{
id: 3,
random: true,
another: 'hello'
},
{
id: 4,
random: false,
another: 'world'
},
{
id: 5,
random: true,
another: 'awesome'
}
]
}
}
The shuffleArray is just to show that the records could be in any order, and the id (source_id) property is not necessarily in incremental order (actually in my case they are UUIDs with the hierarchy not really in any particular order). Each "record" in buildTree is a "property" record basically like this:
create table object_properties {
uuid id;
uuid source_id; // the object which has this property
string name; // the property name
uuid value; // the property value object
}
// ...and same for boolean, integer, etc. properties
create table string_properties {
uuid id;
uuid source_id; // the object which has this property
string name; // the property name
string value; // the property value string
}
In my buildTree I can kind of imagine creating a map from the source_id (the base object node which has property name), to the names, to the values. But then maybe iterating over the source IDs, looking for objects nested inside the name values, and converting them to objects instead of just IDs. But this is getting hard to comprehend and I'm sure there is an easier way somehow.
What is an algorithm to build an "object tree" from this flat list of records?
In my situation, I am fetching a bunch of deeply nested property objects, recursively, and need to stitch back together an object tree out of them.
It looks like the name "nested" plays a special role. When it occurs, the corresponding value property does not hold a literal value to assign to the named property (as is the case with other names), but is a reference to an existing source_id value.
This means your code needs to deal with that name specifically and then establish the parent-child relationship. This relationship is further influenced by the array property.
I would define buildTree as follows, making use of a Map, which is built first using its constructor argument:
function buildTree(startId, arr) {
const map = new Map(arr.map(({source_id}) => [source_id, { id: source_id }]));
for (const {source_id, name, value, array} of arr) {
if (name !== "nested") {
map.get(source_id)[name] = value;
} else if (array) {
(map.get(source_id).nested ??= []).push(map.get(value));
} else {
map.get(source_id).nested = map.get(value);
}
}
return map.get(startId);
}
// Code below has not changed
function shuffleArray(array) { for (var i = array.length - 1, j, temp; i > 0; i--) {j = Math.floor(Math.random() * (i + 1));temp = array[i];array[i] = array[j];array[j] = temp;} return array;}
const tree = buildTree(1, shuffleArray([{ type: 'string', source_id: 1, name: 'foo', value: 'asdf' },{ type: 'integer', source_id: 1, name: 'bar', value: 123 },{ type: 'object', source_id: 1, name: 'nested', value: 2 },{ type: 'object', source_id: 2, name: 'nested', value: 3, array: true },{ type: 'boolean', source_id: 3, name: 'random', value: true },{ type: 'string', source_id: 3, name: 'another', value: 'hello' },{ type: 'object', source_id: 2, name: 'nested', value: 4, array: true },{ type: 'boolean', source_id: 4, name: 'random', value: false },{ type: 'string', source_id: 4, name: 'another', value: 'world' },{ type: 'object', source_id: 2, name: 'nested', value: 5, array: true },{ type: 'boolean', source_id: 5, name: 'random', value: true },{ type: 'string', source_id: 5, name: 'another', value: 'awesome' },]))
console.log(tree);
Note that the order in which objects are pushed into arrays is defined by the original order of the objects. Since this input array is shuffled, the output may show arrays in different ordering on separate runs. Something similar holds for object keys (see Object property order)
You should try Array.prototype.group(). Please refer below document.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/group
const inventory = [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
{ name: 'bananas', type: 'fruit', quantity: 0 },
{ name: 'goat', type: 'meat', quantity: 23 },
{ name: 'cherries', type: 'fruit', quantity: 5 },
{ name: 'fish', type: 'meat', quantity: 22 }
];
const result = inventory.group(({ type }) => type);
/* Result is:
{
vegetables: [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
],
fruit: [
{ name: "bananas", type: "fruit", quantity: 0 },
{ name: "cherries", type: "fruit", quantity: 5 }
],
meat: [
{ name: "goat", type: "meat", quantity: 23 },
{ name: "fish", type: "meat", quantity: 22 }
]
}
*/
In React.js I have array of object:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
],
},
],
},
],
},
]
I have an array of objects, and I want to update this array with some object, for example something like this:
const onAddFile = () => {
const newFile = {
type: 'file',
name: 'SXfile',
id: "100,
}
const folderToPush = files: (1) [{…}],
id: "6",
name: "name 1 ",
type: "folder" <---this is folder which I have selected via click, which I have id of folder and array of files that I have push newFile. and update state instantly.
folderToPush.files.push(newFile)
}
expected output is something like this:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
{ type: 'file', name: 'SXfile.jsx', id: '100' }, <--- here is pushed object
],
},
],
},
],
},
]
I know it have to be some recursive function to map through whole array, but I have no idea how to implement this. Is there a way to make it, with useState only to see changes instantly?
Given a flat level array of objects, what's the most efficient and modern way to nest them based on a parent and id property? The top level objects have no parentId, and there's no limit to nest levels.
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS'
}, {
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina'
}, {
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows'
}, {
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
}, {
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave'
}, {
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP'
}, {
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}]
Where parentId matches up to a corresponding id field. Ideally transforming them to include a children array field along the lines of:
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS',
children: [
{
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina',
children: [
{
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
},
{
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave',
children: [
{
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows',
children: [
{
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
},
{
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP',
children: [
{
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}]
Thoughts appreciated!
You could use reduce in recursive function that will pass down the current element id and compare it with parent id in nested calls.
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
function nested(data, pid = undefined) {
return data.reduce((r, e) => {
if (e.parentId == pid) {
const obj = { ...e }
const children = nested(data, e.id);
if (children.length) obj.children = children;
r.push(obj)
}
return r;
}, [])
}
const result = nested(data);
console.log(result)
The reducer approach by Nenad works, but is pretty inefficient as it iterates through the data list n^2 times. Here is an O(n) solution:
function buildTree(data) {
const store = new Map(); // stores data indexed by it's id
const rels = new Map(); // stores array of children associated with id
const roots = []; // stores root nodes
data.forEach(d => {
store.set(d.id, d);
!rels.get(d.id) ? rels.set(d.id, []) : undefined; // noOp.;
if (!d.parentId) {
roots.push(d.id)
return;
}
const parent = rels.get(d.parentId) || [];
parent.push(d.id);
rels.set(d.parentId, parent);
});
function build(id) {
const data = store.get(id);
const children = rels.get(id);
if (children.length === 0) {
return {...data}
}
return {...data, children: children.map(c => build(c)) };
}
return roots.map(r => build(r));
}
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
console.log(JSON.stringify(buildTree(data), null, 2))
Edit Note:
Earlier answer was class based. Removed that for simplicity. You can further optimize the space storage by changing store to be index based.
I have an object I want to filter so that trainers with the most electric-type pokemon are listed first, but trainers without any electric-type pokemon are still present (represented as an empty array)
Here's my object:
obj = {
trainer1: [
{ name: 'pikachu', type: 'electric', id: 25 },
{ name: 'zapdos', type: 'electric', id: 145 },
{ name: 'psyduck', type: 'water', id: 54 },
],
trainer2: [
{ name: 'eevee', type: 'normal', id: 133 },
{ name: 'magmar', type: 'fire', id: 126 }
],
trainer3: [
{ name: 'ditto', type: 'normal', id: 132 },
{ name: 'magnemite', type: 'electric', id: 81 }
]
}
Becomes this object:
obj = {
trainer1: [
{ name: 'pikachu', type: 'electric', id: 25 },
{ name: 'zapdos', type: 'electric', id: 145 }
],
trainer3: [
{ name: 'magnemite', type: 'electric', id: 81 }
]
trainer2: [] // Array still present, but empty
}
I know reduce would come in handy here but I'm not sure how to set it up correctly.
This may be the bruteforce solution and there will be better solution than this but i think you can do it like the following way.
const tempArr = Object.keys(obj).map(key=>{
return {
key:key,
value:obj[key].filter(pokemon=>pokemon.type==='electric')
}
})
let newObj = {}
tempArr.sort((a,b)=>b.value.length-a.value.length)
tempArr.forEach(item=>{
newObj[item.key] = item.value
})
console.log(newObj)
I want to merge value to array if it have a same id.
I have one array of objects like this.
[
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
]
and I want to merge key favorite from string to array.
I want output like this
[
{
id: 'Tony',
type: 'hero',
favorite: ['Rosie', 'Lisa', 'Jisoo'],
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Steve',
type: 'hero',
favorite: ['Jennie'],
}
and i try to write code like this: (from: Sum similar keys in an array of objects )
var obj = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
];
var holder = {};
const ar = []
obj.forEach(function (d) {
if (holder.hasOwnProperty(d.id)) {
holder[d.id] = ar.push(holder[d.id] + d.favorite);
} else {
holder[d.id] = d.favorite;
}
});
var obj2 = [];
for (var prop in holder) {
obj2.push({ name: prop, favorite: holder[prop] });
}
console.log(obj2);
but out put is
[ { name: 'Tony', favorite: 2 },
{ name: 'Jane', favorite: null },
{ name: 'Steve', favorite: 'Jennie' } ]
How can i do this ?
You can do this with a single Array.reduce and would most likely be the most simple and performant approach:
var data = [ { id: 'Tony', type: 'hero', favorite: 'Rosie', }, { id: 'Jane', type: 'human', favorite: null, }, { id: 'Tony', type: 'hero', favorite: 'Lisa', }, { id: 'Steve', type: 'hero', favorite: 'Jennie', }, { id: 'Tony', type: 'hero', favorite: 'Jisoo', }, ]
let result = data.reduce((r, {id,type,favorite}) => {
r[id] = r[id] || {id, type, favorite: []}
r[id].favorite.push(favorite)
return r
}, {})
console.log(Object.values(result))
The idea is to "group by" the id and then keep pushing to the favorites array on each iteration.
For ES5 you can do it in similar fashion:
var data = [ { id: 'Tony', type: 'hero', favorite: 'Rosie', }, { id: 'Jane', type: 'human', favorite: null, }, { id: 'Tony', type: 'hero', favorite: 'Lisa', }, { id: 'Steve', type: 'hero', favorite: 'Jennie', }, { id: 'Tony', type: 'hero', favorite: 'Jisoo', }, ]
let result = data.reduce(function(r, c){
r[c.id] = r[c.id] || {id: c.id, type: c.type, favorite: []}
r[c.id].favorite.push(c.favorite)
return r
}, {})
console.log(Object.values(result))
There really is no need for lodash to achieve this.
Group by the id with _.group(), and then merge each group using _.map() and _.mergeWith(), and collect favorite to an array:
const data = [{"id":"Tony","type":"hero","favorite":"Rosie"},{"id":"Jane","type":"human","favorite":null},{"id":"Tony","type":"hero","favorite":"Lisa"},{"id":"Steve","type":"hero","favorite":"Jennie"},{"id":"Tony","type":"hero","favorite":"Jisoo"}]
const result = _(data)
.groupBy('id')
.map(g => _.mergeWith({}, ...g, (o, s, k) => {
if(k !== 'favorite') return // non favorite key are not collected to an array
if(_.isNil(s)) return o // don't add null or undefined to array
return [].concat(o || [], s) // concat other values to array
}))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Use map and Set to get this result.
First we create a new unique Set using the id field.
We seed a new new array from this and call the map method.
const arr = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
];
const masterArr = [...new Set(arr.map((e) => e.id))].map(a => ({
id: a,
type: arr.find(x => x.id === a).type,
favorites: arr.filter(x => x.id === a).map(y => y.favorite)
}));
console.log(masterArr);
You can use reduce on the array.
const arr = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
]
const out = arr.reduce((result, el)=> {
const id = result.findIndex(e => e.id ===el.id)
if(id> -1){
result[id] = {...result[id], favorite: [...result[id].favorite, el.favorite]}
} else {result.push({...el, favorite: [el.favorite].filter(x => x) })}
return result
}, [])
console.log(out)
Just loop over the array and construct a new array after converting favorite attribute into an array, then for next elements with the same id, push to favorite array
let users = [{id: 'Tony', type: 'hero', favorite: 'Rosie',}, {id: 'Jane', type: 'human', favorite: null,}, {id: 'Tony', type: 'hero', favorite: 'Lisa',}, {id: 'Steve', type: 'hero', favorite: 'Jennie',}, {id: 'Tony', type: 'hero', favorite: 'Jisoo',},];
// To combine friends of the same user in an array at property friendNames
let merged = {};
for (let user of users) {
if (typeof merged[user.id] === 'undefined') {
user.favorite = [user.favorite];
merged[user.id] = user;
} else {
merged[user.id].favorite.push(user.favorite)
}
}
console.log(Object.values(merged));