Get the branch of collection without the sibling elements, searching by property - javascript

I have the object with the next structure:
let array = [
{
name: 'Name1',
items: [
{
name: 'Name1.1',
items: [
{ id: '1', name: 'Name1.1.1' },
{ id: '2', name: 'Name1.1.2' },
{ id: '3', name: 'Name1.1.3' },
...
],
},
{
name: 'Name1.2',
items: [
{ id: '4', name: 'Name1.2.1' },
{ id: '5', name: 'Name1.2.2' },
],
},
],
},
{
name: 'Name2',
items: [
{
name: 'Name2.1',
items: [
{ id: '6', name: 'Name2.1.1' },
{ id: '7', name: 'Name2.1.2' },
],
},
],
},
];
I want to get the branch without the sibling elements, searching by id. The desired result is the next structure by id = '4':
let array = [
{
name: 'Name1',
items: [
{
name: 'Name1.2',
items: [
{ id: '4', name: 'Name1.2.1' },
],
},
],
}
];
I could find only the end element of the tree ({ id: '4', name: 'Name1.2.1' }). But I don't understand how to get intermediate structures of the tree.
const test = (data, id) => {
if (!data || !data.length) return null;
for (var j = 0; j < data.length; j++) {
var result = data[j].items
? test(data[j].items, id)
: data[j].id
? data[j].id === id
? data[j]
: undefined
: undefined;
if (result !== undefined) {
return result;
}
}
return undefined;
};
test(array, '4');

You should indeed take a recursive approach, but your function currently can only return an id value (a string) or null or undefined. It never returns an array, yet that is what you expect to get.
When a solution is found as a base case, you need to wrap that solution in an array and plain object, each time you get out of the recursion tree.
Here is a working solution:
function getPath(forest, targetid) {
for (let root of forest) {
if (root.id === targetid) return [root]; // base case
let items = root.items && getPath(root.items, targetid);
if (items) return [{ ...root, items }]; // wrap!
}
}
// Example run:
let array = [{name: 'Name1',items: [{name: 'Name1.1',items: [{ id: '1', name: 'Name1.1.1' },{ id: '2', name: 'Name1.1.2' },{ id: '3', name: 'Name1.1.3' },],},{name: 'Name1.2',items: [{ id: '4', name: 'Name1.2.1' },{ id: '5', name: 'Name1.2.2' },],},],},{name: 'Name2',items: [{name: 'Name2.1',items: [{ id: '6', name: 'Name2.1.1' },{ id: '7', name: 'Name2.1.2' },],},],},];
console.log(getPath(array, '4'));

Related

fillter arrays of objects

i have two arrays.
const department = [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
];
const models = [
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }],
},
{
id: '54',
name: 'model2',
departments: [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
],
},
];
i need to render accordions with department names and accordion details with matching models names. My question is how to filter those arrays to get models
We can map through the departments array, and add a models property that equals the models array, but filtered only to the ones that contain a matching department id.
const departments = [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
];
const models = [
{
id: "23",
name: "model1",
departments: [{ id: "1", name: "department1" }],
},
{
id: "54",
name: "model2",
departments: [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
],
},
];
const getDepartmentsWithModels = () => {
return departments.map((department) => {
return {
...department,
models: models.filter((model) => {
const modelDepartmentIds = model.departments.map(({ id }) => id);
return modelDepartmentIds.includes(department.id);
}),
};
});
};
console.log(getDepartmentsWithModels());
// [ { id: '1', name: 'department1', models: [ [Object], [Object] ] },
// { id: '2', name: 'department2', models: [ [Object] ] } ]```
I've built some code, which iterates over the departments. For each department it iterates the models and for each model it checks if the department is within the model departments.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }]
},
{
id: '54',
name: 'model2',
departments: [{ id: '1', name: 'department1' },{ id: '2', name: 'department2' }]
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.forEach(model => {
if (model.departments.find(modelDep => dep.id===modelDep.id)) {
console.log(` Model: ${model.name}`)
}
})
})
If you could change your data objects, then your code could be much smoother.
I've changed your data objects slightly by just reducing the departments in a model to be an array of department id's. This code iterates over the departments. For each department it filters the models and iterates over the filtered models to output them to the console. This is lesser code and provides much better performance.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: ['1']
},
{
id: '54',
name: 'model2',
departments: ['1', '2']
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.filter(model => model.departments.includes(dep.id)).forEach(model => {
console.log(` Model: ${model.name}`)
})
})
There are two solutions.
Using Array.reduce() --> returns an object where the key is department name and value is an array of the names of matching models:
let data1 = models.reduce((res, curr) => {
curr.departments.forEach(dep => {
if (!res[dep.name]) {
res[dep.name] = [curr.name]
} else {
if (!res[dep.name].includes(curr.name)) {
res[dep.name].push(curr.name);
}
}
})
return res;
}, {});
Using map and filter --> returns an array of kind:
[{department: [names of the models]},...]
let data2 = department.map(dep => {
let matchingModels = models.filter(model => {
return model.departments.filter(modDep => {
return modDep.name === dep.name;
}).length > 0;
}).map(mod => {
return mod.name;
});
return {
department: dep.name,
models: matchingModels
}
});

How to make a function as pure function using javascript and react?

i have data like below,
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
}
the above code works fine. but i want to make loop(children) function a pure function. I am not sure how to do it.
the problem now is i define variables outside the loop method.
how can i define everything as arguments to the function, you could move the function outside the component.
could someone help me with this. thanks.
You could take an array of the wanted type order and iterate only one level and han over the rest of wanted type. If no type are left over, return one otherwise the result of nested count.
const
getCount = (array, types) => {
let count = 0;
for (const { type, children } of array) {
if (types[0] === type) {
count += types.length === 1
? 1
: getCount(children, types.slice(1));
}
}
return count;
}
data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }],
order = ['TYPE2', 'MAIN'],
count = getCount(data, order);
console.log(count);
Pure Function is a function (a block of code ) that always returns the same result if the same arguments are passed. It does not depend on any state, or data change during a program’s execution rather it only depends on its input arguments.
Reference
In the above shared code I could see expectedCount as the shared variable which is not the PURE function.
As I could see your comments the desired type is in Children then its just the 2 levels. Then the following code would work.
function count(data, condition) {
let count = 0;
data.forEach((value, index) => {
if(value.type === condition[0]){
value.children.forEach((val, idx) => {
if(val.type === condition[1]) {
count++;
}
})
}
});
return count;
}
const condition = ['TYPE2', 'MAIN'];
console.log(count(arr_obj, condition));
Nina's answer is more to the point but you could also do it by filtering the input array.
const data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }];
const count = data
.filter(v=>v.type==='TYPE2')
.flatMap(v=>v.children)
.filter(v=>v.type==='MAIN')
.length
console.log(count);

Grouping a object value inside n level nested array

I have an array like this. How to group all child Ids into an array?
My solution below is not giving me all child elements. Where is the mistake? and suggest me any other ways
const data = {
name: '1',
id: '05f770d5',
child: [
{
name: '2',
id: '0ecfc8e1',
child: [
{
name: '3',
id: '2e1eb75c',
child: [],
},
],
},
{
name: '1c',
id: 'b9ee9864',
child: [
{
name: '8',
id: '575f4760',
child: [],
},
],
},
],
};
let array1 = [];
function sumChild(data) {
data.child.forEach((data) => {
array1.push(data.id);
sumChild(data?.child[0]);
});
return array1;
}
sumChild(data);
console.log(array1);
function sumChild(data) {
data.child?.forEach((data) => {
array1.push(data.id);
sumChild(data);
});
return array1;
}

How to add a field to each object in array and push that result array to another array using react and javascript?

i want to add a field type to each object in an array and push the result to another array and push that to another array.
below is how the array of object looks,
const arrObj1 = [
{
id: '1',
name: 'n1',
},
{
id: '2',
name: 'n2',
}
]
const arrObj2 = [
{
id: '3',
name: 'n3',
},
{
id: '4',
name: 'n4',
}
]
what i am trying to do?
i want to add a type field with value 'type1' to arrObj1 and type field with value 'type2' to arrObj2
so the output should be like below,
const arrObj1 = [
{
id: '1',
name: 'n1',
type: 'type1',
},
{
id: '2',
name: 'n2',
type: 'type1',
}
]
const arrObj2 = [
{
id: '3',
name: 'n3',
type: 'type2',
},
{
id: '4',
name: 'n4',
type: 'type2',
}
]
then i want to combine these two arrays arrObj1 and arrObj2 into one array like below
const combinedArrObj = [
{
id: '1',
name: 'n1',
type: 'type1',
},
{
id: '2',
name: 'n2',
type: 'type1',
},
{
id: '3',
name: 'n3',
type: 'type2',
},
{
id: '4',
name: 'n4',
type: 'type2',
}
]
what i have tried?
const arrObj1Res = arrObj1.map((obj: any) => {
return {
...arrObj1,
type: 'type1',
}
});
const arrObj2Res = arrObj2.map((obj: any) => {
return {
...arrObj2,
type: 'type2',
}
});
const combinedArrObj = [
...arrObj1Res,
...arrObj2Res,
];
this works. but how can i refactor to something simple rather than adding field type for each obj in array and then combine
could someone help me with this. thanks.
you can use this
let result=arrObj1.concat(arrObj2)
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
}
result.filter(element => {
if (contains(arrObj1,element) ){
element.type='type1'
return element
}else {
element.type='type2'
return element
}
});
In pure JS, just a simple for loop can do the part where you add a field named type.
for(var i = 0; i < arrObj1.length; i++){
arrObj1[i].type = "type1";
}
for(var i = 0; i < arrObj2.length; i++){
arrObj2[i].type = "type2";
}
and to combine the arrays together just use concat.
const combinedArrObj = arrObj1.concat(arrObj2);
const allArrayObject = arrObj1.concat(arrObj2);
const finalResultArray = allArrayObject.reduce((finalResultArray, eachArrayObj, index) =>{
const isTypeOne = arrObj1.length - 1 >= index
const relevantTypeString = isTypeOne ? "type1" : "type2"
eachArrayObj.type = relevantTypeString
finalResultArray.push(eachArrayObj)
return finalResultArray
}, [])

Pick unique sub-objects from list of objects and transform them with Ramda

With the following data structure...
const branches = [
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }}
]
I want to pick unique brands... that's easy enough.
const pickBrands = RM.pipe(
RM.map(RM.prop('brand')),
RM.uniqBy(RM.prop('id'))
)
But ultimately, I need to transform a whole thing like this...
const brands = [
{ id: 'A', branches: ['1', '3'] },
{ id: 'B', branches: ['2'] },
]
I am kinda confused how could I approach that considering that after the first map, I am losing information about a branch.
Final solution:
https://runkit.com/fredyc/5d2e1bf1df8aec001aff7f64
This might help too:
const group = R.pipe(
R.groupBy(R.path(['brand', 'id'])),
R.values,
R.map(
R.applySpec({
id: R.path([0, 'brand', 'id']),
branches: R.pluck('id'),
}),
),
);
const branches = [
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }}
];
console.log('group', group(branches));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
You can use R.groupBy with R.path to group by brand.id, and then use R.toPairs and R.map with R.zipObject to generate the new object from the groups.
Example (annotated by #ScottSauyet):
const { pipe, groupBy, path, map, pluck, toPairs, zipObj } = R
const fn = pipe(
groupBy(path(['brand', 'id'])), //=> {"A": [{brand: {id: "A"}, id: "1"}, {brand: {id: "A"}, id: "3"}], B: [{brand: {id: "B"}, id: "2"}]}
map(pluck('id')), //=> {A: ["1", "3"], B: ["2"]}
toPairs, //=> [["A", ["1", "3"]], ["B", ["2"]]]
map(zipObj(['id', 'brand']) ) //=> [{id: "A", brand: ["1", "3"]}, {id: "B", brand: ["2"]}]
)
const branches = [
{ id: '1', brand: { id: 'A' } },
{ id: '2', brand: { id: 'B' } },
{ id: '3', brand: { id: 'A' } }
]
const result = fn(branches)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Note: My original solution used R.applySpec({ id: head, branches: last }), but it seems that I'm the only one to find it more readable.
You can use plain old JS to achieve your outcome, use reduce with findIndex to check if the id already exists, if it does, push the id to the existing object, otherwise, push the new object:
const branches = [
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }}
]
console.log(branches.reduce((a, {id, brand}) => {
const i = a.findIndex(o => o.id === brand.id)
i + 1 ? a[i].branches.id.push(id) : a.push({id: brand.id, branches: { id: [id] }})
return a
}, []))
You could take a Map for collecting brands.
const
branches = [{ id: '1', brand: { id: 'A' } }, { id: '2', brand: { id: 'B' } }, { id: '3', brand: { id: 'A' } }],
brands = Array.from(
branches.reduce((m, { id: branche, brand: { id } }) =>
m.set(id, [...(m.get(id) || []), branche]), new Map),
([id, branches]) => ({ id, branches: { id: branches }})
);
console.log(brands);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a solution using values and reduceBy.
First let's define an empty template for brands:
const brandTmpl =
{ id: null,
branches: [] };
Then let's define a function that returns the brand id for a given branch:
const brandId = path(['brand', 'id']);
brandId({ id: '1', brand: { id: 'A' }});
//=> 'A'
Then given a brand and a branch, let's define a function that "adds" a branch to a brand:
const brand = (brd, bch) => (
{ id: brandId(bch),
branches: append(bch.id, brd.branches) });
brand(brandTmpl, { id: '1', brand: { id: 'A' }});
//=> {id: 'A', branches: ['1']}
Now let's use all of that to merge branches by brand:
const brands = reduceBy(brand, brandTmpl, brandId);
brands([
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }}]);
//=> { A: { id: "A", branches: ["1", "3"]},
//=> B: { id: "B", branches: ["2"]} }
Finally we can simply extract the values:
const branches = [
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }} ];
const brandId = path(['brand', 'id']);
const brandTmpl =
{ id: null,
branches: [] };
const brand = (brd, bch) => (
{ id: brandId(bch),
branches: append(bch.id, brd.branches) });
const brands = reduceBy(brand, brandTmpl, brandId);
const pickBrands = compose(values, brands);
console.log(
pickBrands(branches)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {path, append, reduceBy, compose, values} = R;</script>
try this:
const branches = [
{ id: '1', brand: { id: 'A' }},
{ id: '2', brand: { id: 'B' }},
{ id: '3', brand: { id: 'A' }}
]
const groupBy = (branches) => branches.reduce((acc, ele)=>( (acc[ele.brand.id] = acc[ele.brand.id] || []).push(ele), acc),{})
const reformat = ([k, v]) =>({id: k, branches: {id:v.map(({id})=>id)}})
const result = Object.entries(groupBy(branches)).map(ele => reformat(ele))
console.log(result);

Categories

Resources