NOTE: This is the first question that I ever created in StackOverflow.
I was trying to implement layer manager in javascript with this value:
var layers = [
{
id: "layer_1",
name: "Layer 1",
child: [
{
id: "layer_1A",
name: "Layer 1A",
child: [
{
id: "layer_1A1",
name: "Layer 1A1"
},
{
id: "layer_1A2",
name: "Layer 1A2"
}
]
},
{
id: "layer_1B",
name: "Layer 1B"
}
]
},
{
id: "layer_2",
name: "Layer 2"
},
{
id: "layer_3",
name: "Layer 3"
}
]
and save the result into this:
var indexSelector = {};
here is the code:
var value = [-1];
function read_layers(obj) {
// Checks the array (layer/child)
if (Array.isArray(obj)) {
for (let layer of obj) {
read_layers(layer);
}
}
// Checks the object
else if (typeof obj == "object") {
for (let layer of Object.entries(obj)) {
// Checks the childs
if (layer[0] == "layers" || layer[0] == "child") {
value.push(-1);
read_layers(obj[layer[0]]);
value.pop()
}
// Checks the object keys
else if (layer[0] == "id") {
if (!indexSelector.hasOwnProperty(layer[1])) {
++value[value.length-1];
indexSelector[layer[1]] = value; // Result
}
}
}
}
}
read_layers(layers);
I want the expected result to looks like this:
{
layer_1: [ 0 ]
layer_1A: [ 0, 0 ]
layer_1A1: [ 0, 0, 0 ]
layer_1A2: [ 0, 0, 1 ]
layer_1B: [ 0, 1 ]
layer_2: [ 1 ]
layer_3: [ 2 ]
}
But here is the problem result:
{
layer_1: [ 2 ]
layer_1A: [ 2 ]
layer_1A1: [ 2 ]
layer_1A2: [ 2 ]
layer_1B: [ 2 ]
layer_2: [ 2 ]
layer_3: [ 2 ]
}
How to fix this problem with different object values? Thanks.
NOTE: All assignment (with operator = ) of objects is applied with theirs reference, in javascript, like in as java.
So, at line indexSelector[layer[1]] = value; // Result , the value is assigned with its reference, not the whole value.
That's why the values of the result are all same.
Solution: Use indexSelector[layer[1]] = Object.assign({}, value);.
It'll solve your problem.
Related
I have been searching for quite a while but cannot find an answer to my issue. The problem is pretty simple; I have an array of objects, each containing another array of objects. I want to get the cumulative length of all arrays inside all objects.
Here is some sample data:
const items = [
{
id: 1,
title: "Test 1",
data: [
{
...
},
{
...
},
]
},
{
id: 2,
title: "Test 2",
data: [
{
...
},
]
}
]
In this sample, the length should be 3 since there is 2 objects inside the first object's data property and 1 object inside the second object's data property.
pretty simple
const items =
[ { id: 1, title: "Test 1", data: [{a:1},{a:1} ] }
, { id: 2, title: "Test 2", data: [{a:1},{a:1},{a:1},{a:1}] }
]
console.log('cumulative length ', items.reduce((a,c)=>a+c.data.length,0) )
All you need to do is loop through all items you got, check the length of the data array of each item and add that length to a variable. Here is a working snippet:
const items = [
{
id: 1,
title: "Test 1",
data: [
{
},
{
},
]
},
{
id: 2,
title: "Test 2",
data: [
{
},
]
}
];
// Initialize the count variable as 0
var count = 0;
// Pass through each item
items.forEach((item)=>{
// Adding the count of data of each item
count += item.data.length;
});
// Outputting the count
console.log(count);
If you want to use for of loop that works too.
const countObjects = (arrayInput) => {
let totalCount = 0
for(let item of items) {
totalCount += item.data.length
}
return totalCount
}
console.log(countObjects(items))
data = [
{
name: "Parent Level 1",
questions: [
{
name: "question 1"
}
],
children: [
{
name: "Child 1 - P1",
questions: [
{
name: "ability to code"
},
{
name: "ability to do something"
}
],
children: [
{
name: "Child -2 P1",
questions: [
{
name: "figure out"
}
]
}
]
}
]
},
{
name : 'Parent Level 2',
questions : [
{name : 'question 1 P-2'}
]
},
{
name : 'Parent Level 3',
children: [
{
name : 'Child Level -1 P-3',
children: [
{
name : 'Child Level 2- P-3',
questions : [
{
name : 'Question level 2
}
]
}
]
questions: [
{name : 'hello there'}
]
}
]
}
];
Problem:
I need to perform a keyword search on the question, and if a question is found at a node - 3 let’s say then we need to return that node and all the parent nodes of that object.
For example, if I search for 'hello there', the final tree should be:
[
{
name : 'Parent Level 3',
children: [
{
name : 'Child Level -1 P-3',
children: [
{
name : 'Child Level 2- P-3',
questions : []
}
]
questions: [
{name : 'hello there'}
]
}
]
}
];
We can have children or questions [] at any node.
I am able to find the questions matching the search string, but I am not able to remove the unwanted nodes from the tree. Here is the code for that:
searchNode (data) {
for (let d of data) {
this.search(d)
}
}
search(data) {
let search = 'ability'
if(!!data.questions && data.questions.length > 0) {
data.questions = data.questions.filter((question) => {
return question.name.includes(search)
})
}
if(data.children && data.children.length > 0) {
searchNode(data.children)
}
}
search(data)
This should work for you. The demo code is in stackblitz. Check the result in the console.
Stackblitz demo
searchString = 'hello';
filteredData = [];
ngOnInit(): void {
this.filteredData = [];
this.data.forEach(node => {
if (this.checkQtn(node)) {
this.filteredData.push(node);
}
});
console.log(this.filteredData);
}
checkQtn(node): Boolean {
let response: Boolean = false;
if (node.questions) {
let qtns = [];
qtns = node.questions;
qtns.forEach(el => {
const eachQtn: string = el.name;
if (eachQtn.includes(this.searchString)) {
response = true;
}
});
}
if (!response && node.children) {
for (let i = 0; i < node.children.length && !response; i++) {
response = this.checkQtn(node.children[i]);
}
}
return response;
}
Given the following data structure:
[
{
"name":"root",
"children":[
{
"name":"de",
"children":[
{
"name":"de",
"children":[
{
"name":"project-1",
"children":[
]
},
{
"name":"project-2",
"children":[
]
}
]
}
]
}
]
}
]
Expected:
[
{
"name":"project-1",
"children":[
]
},
{
"name":"project-2",
"children":[
]
}
]
I want to remove a level if there is only one child. In this example I want to have a new array that only contains the children of the "root" level without root itself.
I would do that with reduce but still cant wrap my head around reduce in combination with recursion. Any ideas?
You can simply use map and flatten arrays afterwards.
.map(o => o.children).flat()
EDIT: updated answer after figuring out the real question
Still you can use map and flatten logic but in a recursive manner.
function removeSingleChildElms (o) {
if (!o.children) return
if (o.children.length === 1) {
return o.children.map(removeSingleChildElms).flat()
} else {
return o.children
}
}
EDIT2:
Some explanation: The problem is transforming array of object(s) into array of different objects. I don't choose reduce, because the problem doesn't care about relationship/logic among sibling elements. It's just about transforming, hence map will just work good enough.
The problem asks to 'skip' objects with 1 child. This is recurring part, meaning: If you see an object satisfying this condition you go deeper for mapping. In any other valid condition, children stay same (else case)
Tree transformation can be made easy by breaking the task down into two parts:
a function for transforming a single node
a function for transforming an array of nodes
To transform a single node, we write transform1
if there are no children, we have found a leaf node, return the singleton node
if there is just one child, drop the node and return the transformation of its only child
otherwise, the node has multiple children, call our second function transformAll
const transform1 = ({ children = [], ...node }) =>
children.length === 0 // leaf
? [ node ]
: children.length === 1 // singleton
? transform1 (...children)
: transformAll (children) // default
To transform an array of nodes, we write transformAll -
const transformAll = (arr = []) =>
arr .flatMap (transform1)
As you can see, transformAll calls transform1, which also calls transformAll. This technique is called mutual recursion and it's a great way to process recursive data structures like the one proposed in your question.
To ensure our function works properly, I've modified the tree to contain more data scenarios. Note, our program works for any nodes with a children property. All other properties are displayed in the result -
const data =
[ { name: "a"
, children:
[ { name: "a.a"
, children:
[ { name: "a.a.a"
, children: []
}
, { name: "a.a.b"
, foo: 123
, children: []
}
]
}
]
}
, { name: "b"
, children:
[ { name: "b.a"
, children:
[ { name: "b.a.a"
, children: []
}
, { name: "b.a.b"
, children: []
}
]
}
, { name: "b.b"
, children: []
}
]
}
, { name: "c"
, children: []
}
]
We can run transformAll on your data to transform all of the nodes -
transformAll (data)
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// , { name: 'b.a.a' }
// , { name: 'b.a.b' }
// , { name: 'b.b' }
// , { name: 'c' }
// ]
Or to transform a single node, we call transform1 -
transform1 (data[0])
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// ]
transform1 (data[2])
// [ { name: 'c' } ]
Expand the snippet below to verify the results in your own browser -
const data =
[ { name: "a"
, children:
[ { name: "a.a"
, children:
[ { name: "a.a.a"
, children: []
}
, { name: "a.a.b"
, foo: 123
, children: []
}
]
}
]
}
, { name: "b"
, children:
[ { name: "b.a"
, children:
[ { name: "b.a.a"
, children: []
}
, { name: "b.a.b"
, children: []
}
]
}
, { name: "b.b"
, children: []
}
]
}
, { name: "c"
, children: []
}
]
const transform1 = ({ children = [], ...node }) =>
children.length === 0 // leaf
? [ node ]
: children.length === 1 // singleton
? transform1 (...children)
: transformAll (children) // default
const transformAll = (arr = []) =>
arr .flatMap (transform1)
console .log (transformAll (data))
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// , { name: 'b.a.a' }
// , { name: 'b.a.b' }
// , { name: 'b.b' }
// , { name: 'c' }
// ]
Actually I have a state data which is an object , It has following structutre,
{ one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }, three : [ { abc:5 }, { abc: 6 }]] }
So its like an arry of objects in an state object .
Now, I want to create an array of objects which will have all these objects .
So I want to have it like,
[{ abc:1 }, { abc: 2 },{ abc:3 }, { abc: 4 },{ abc:5 }, { abc: 6 }]
The way I tried is using for loop.
let quizCriteriaObj = [];
let low = this.props.lowQuizData["Low"];
let High = this.props.lowQuizData["High"];
let Medium = this.props.lowQuizData["Medium"];
console.log("data is ", low);
for (let i = 0; i <= low.length - 1; i++) {
quizCriteriaObj.push(low[i]);
}
for (let i = 0; i <= High.length - 1; i++) {
quizCriteriaObj.push(High[i]);
}
for (let i = 0; i <= Medium.length - 1; i++) {
quizCriteriaObj.push(Medium[i]);
}
console.log(quizCriteriaObj);
I have taken each field aside from that object and using a for loop on every field. SO, It is working for me. But , I think this is not a proper solution for me .Is there any thing That I am doing wrong ?
Modern javascript makes this trivial
Array.prototype.flat
Please note: Array.prototype.flat is a stage 3 TC39 proposal, so is not part of the ECMAScript specification (yet)
It is supported in all modern browsers (and can be polyfilled for Microsofts attempts at browsers, both Internet Explorer and Edgey)
Note: I assume you mistyped the "source" object, because as it was, it was invalid
let obj = {
one : [
{ abc:1 },
{ abc: 2 }
],
two : [
{ abc:3 },
{ abc: 4 }
],
three : [
{ abc:5 },
{ abc: 6 }
]
}
let ftw = Object.values(obj).flat(); //<== single line of code is all you need
console.log(JSON.stringify(ftw))
You can use Object.values to convert the object into an array. Use spread syntax and concat() to flatten the array
var obj ={"one":[{"abc":1},{"abc":2}],"two":[{"abc":3},{"abc":4}],"three":[{"abc":5},{"abc":6}]}
var result = [].concat(...Object.values(obj));
console.log(result);
You can use a for...in loop
let obj = { one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }], three : [ { abc:5 }, { abc: 6 }] };
let result = [];
for (let key in obj) result = [...result, ...obj[key]];
console.log(result);
Using lodash is simple:
const obj = { one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }, three : [ { abc:5 }, { abc: 6 }]] }
_.flatten(obj)
// return => [{ abc:1 }, { abc: 2 },{ abc:3 }, { abc: 4 },{ abc:5 }, { abc: 6 }]
Good question you can't simply spread the arrays so a good one liner
for this would be.
state = {
one : [ { abc:1 }, { abc: 2 }],
two : [ { abc:3 }, { abc: 4 }],
three : [ { abc:5 }, { abc: 6 }]
}
var result = Object.values(state).flat() // solution
console.log(result)
I need to sort my array, he is like this:
x = {
'Abr/2017': [ { id: 1 } ],
'Fev/2018': [ { id: 1 } ],
'Jul/2017': [ { id: 1 } ],
'Abr/2018': [ { id: 1 } ],
'Fev/2017': [ { id: 1 } ],
'Jul/2018': [ { id: 1 } ],
'Dez/2019': [ { id: 1 } ]
}
and I need him to be sorted first in the year and then in the months
Because your data is represented as object, you have to convert it to array as a first step. Then you can sort. Later you can map result array to whatever you want.
Important thing is that if you want to keep this data in some order then you have to use array structure
let x = {
'Abr/2017': [ { id: 1 } ],
'Fev/2018': [ { id: 1 } ],
'Jul/2017': [ { id: 1 } ],
'Abr/2018': [ { id: 1 } ],
'Fev/2017': [ { id: 1 } ],
'Jul/2018': [ { id: 1 } ],
'Dez/2019': [ { id: 1 } ]
}
// this function transform your object to array representation
// where your actual key (e.g. Fev/2017) is stored in helper
// property called key
function toArray(obj) {
return Object.keys(obj).reduce((arr, key) => {
arr.push({ key, data: obj[key] })
return arr
}, [])
}
function byYearAndMonth() {
// month definition - help with sorting
let monthOrder = {
'Jan': 1,
'Fev': 2,
'Mar': 3,
'Abr': 4,
'Mai': 5,
'Jun': 6,
'Jul': 7,
'Ago': 8,
'Set': 9,
'Out': 10,
'Nov': 11,
'Dez': 12
}
let mapDate = function([month, year]) {
return [monthOrder[month], Number(year)]
}
// actual sorting function
return function(a, b) {
const [aMonth, aYear] = mapDate(a.key.split('/'))
const [bMonth, bYear] = mapDate(b.key.split('/'))
if(aYear < bYear) {
return -1
}
if(aYear > bYear) {
return 1
}
if(aMonth < bMonth) {
return -1
}
if(aMonth > bMonth) {
return 1
}
return 0
}
}
// lets try how it works
let xArray = toArray(x)
xArray.sort(byYearAndMonth());
var result = xArray.map(x => x)
// with map (or reduce) you can transform it to whatever you want
// I'm just returning exactly the same object
console.log(result)