Ramda: group by nested object - javascript

I have this data:
const entities = [
{ id: 'view_1', group: { id: 'g_1', name: 'Group 1' } },
{ id: 'view_2', group: { id: 'g_2', name: 'Group 2' } },
{ id: 'view_3', group: { id: 'g_2', name: 'Group 2' } },
];
And I need to get this as output:
const groupedEntities = [
{ id: 'g_1', name: 'Group 1', views: [{ id: 'view_1' }] },
{ id: 'g_2', name: 'Group 2', views: [{ id: 'view_3' }, { id: 'view_3' }] },
];
I know there is indexBy in ramda but it does not preserve other data than key. What I need is to preserve all data and return an object.

Ramda's groupBy can help you here:
const transform = pipe (
groupBy (path (['group', 'id'])),
values,
map (applySpec ({
group: path ([0, 'group', 'id']),
name: path ([0, 'group', 'name']),
views: pluck ('id')
}))
)
const entities = [{id: 'view_1', group: { id: 'g_1', name: 'Group 1'}}, {id: 'view_2', group: { id: 'g_2', name: 'Group 2'}}, {id: 'view_3', group: { id: 'g_2', name: 'Group 2'}}]
console .log (transform (entities))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script>const {pipe, groupBy, path, values, map, applySpec, pluck} = R</script>
The rest of it is a bit more of a handful because of the need to pull parts from the first element in each group and parts from the whole group, but overall, it's not too bad:

Related

Can you transform a list to a tree where items in the list have children but no depth or parent?

Data looks as follows:
Each node has a unique id (
Nodes have a children key which is either null or an array of ids.
Nodes can have one parent
Nodes do not have a parent or depth reference
Input:
const items = [
{
id: 1,
name: 'Item 1',
children: [ 2, 3 ]
},
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
{
id: 4,
name: 'Item 4',
children: [ 5 ]
},
{
id: 5,
name: 'Item 5',
children: [ 6 ]
},
{
id: 6,
name: 'Item 6',
children: null
},
}
]
Expected Output:
const tree = [
{
id: 1,
name: 'Item 1',
children: [
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
]
},
{
id: 4,
name: 'Item 4',
children: [
{
id: 5,
name: 'Item 5',
children: [
{
id: 6,
name: 'Item 6',
children: null
}
]
}
]
}
]
If this is in fact possible, would love to 1) see how it is done and 2) see if there are any libraries that handle this use case.
The resulting structure is more a forest than a tree, as not all nodes are connected and you have multiple "roots".
You can first key the nodes by their id in a Map, and then iterate all children arrays to replace their contents by the corresponding items found in the Map. At the same time keep track of all the children, so that at the end you can identify which items are not children, and therefore belong in the result array:
const items = [{id: 1,name: 'Item 1',children: [ 2, 3 ]},{id: 2,name: 'Item 2',children: null},{id: 3,name: 'Item 3',children: null},{id: 4,name: 'Item 4',children: [ 5 ]},{id: 5,name: 'Item 5',children: [ 6 ]},{id: 6,name: 'Item 6',children: null},];
const map = new Map(items.map(item => [item.id, item]));
const children = new Set;
for (const item of items) {
if (!item.children) continue;
for (const id of item.children) children.add(id);
item.children = item.children?.map(id => map.get(id));
}
const forest = items.filter(({id}) => !children.has(id));
console.log(forest);

How do I create an array of objects with a nested array based on a similar key?

I have an array that looks something like this
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
As you can see, the organization name is something I want to key off of and create a data structure like this:
const output = [
// data.value will be their ID
{
organizationName: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' },
],
},
{
organizationName: 'Organization B',
data: [
{ label: 'Person 3', value: '3' },
],
},
]
What I've tried
I know I want to use reduce for something like this, but I feel like I'm off:
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
if (organizationName) {
acc.push({ organization: organizationName, data: [] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ name: externalPersonName, value: id });
}
return acc;
}, [] as any);
However the output comes out to something like this:
[
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization B', data: []},
];
data doesn't seem to get anything pushed inside the array in this reduce function, and the organization name get duplicated... what am I doing wrong?
Easiest way is to use an Map/Set/or object to keep track of orgs you create. This way you are not searching in the array to see if the organization was found already. After you are done, you can create the array you want from the object.
const externalPeople = {
data : [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
],
};
const providerOptions = Object.values(externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
name: externalPersonName,
id
} = currentValue;
// Is the org new? Yes, create an entry for it
if (!acc[organizationName]) {
acc[organizationName] = { organization: organizationName, data: [] };
}
// push the person to the organization
acc[organizationName].data.push({ name: externalPersonName, value: id });
return acc;
}, {}));
console.log(providerOptions)
Here is another solution
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const result = example.reduce((res, entry) => {
const recordIndex = res.findIndex(rec => rec.organizationName === entry.organization.name);
if(recordIndex >= 0) {
res[recordIndex].data.push({ label: entry.name, value: entry.id});
} else {
const record = {
organizationName: entry.organization.name,
data: [{ label: entry.name, value: entry.id }]
};
res.push(record);
}
return res;
}, []);
console.log(result);
You are not checking if the value is already present in your accumulation acc
You can check it with a simple find in the if statement since it's an array
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
//Check if organization is not present already
if (!acc.find(a => a.organization === organizationName)) {
//Add also the data of the element your are processing
acc.push({ organization: organizationName, data: [{label: currentValue.name, value: currentValue.id}] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ label: externalPersonName, value: id });
}
return acc;
}, [] as any);
I also added the data of the first element of the group you create when adding the organization.
The result should be as your expected output:
[
{
organization: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' }
]
},
{
organization: 'Organization B',
data: [
{ label: 'Person 3', value: '3' }
]
}
]
Hope it helps!
Compare this solution (using Lodash) with other solutions. Which one emphasises your intentions at most? This is why we use Lodash in our company - to maintain code as declarative as we can, because code readability, with minimum cognitive overload, is most important goal during coding.
const persons = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const personsByOrganizations = _.groupBy(persons, 'organization.name')
const output = _.map(personsByOrganizations, (persons, organizationName) => ({
organizationName,
data: _.map(persons, ({ name, id }) => ({
label: name,
value: id
}))
}))
Something like that with using a Set?
result = [...new Set(example.map(d => d.organization.name))].map(label => {
return {
organizationName: label,
data: example.filter(d => d.organization.name === label).map(d => {
return {label: d.name, value: d.id}
})
}
})
`

How to find just first child element in array? Javascript [duplicate]

This question already has answers here:
How to map more than one property from an array of objects [duplicate]
(7 answers)
Closed last year.
I need to find first child element in array and just return it.
Here is problem because here is few children elements and i need loop thought each and return every first children element.
Example of array:
let allItems =
[{ id: 1 ,
name: 'Test 1' ,
children: [
id: 12,
title: 'Child element'
]
},
{
id: 2 ,
name: 'Test 2'
},
{
id: 3 ,
name: 'Test 3',
children: [
id: 12,
title: 'Child element',
children: [
id: 123,
title: 'GRAND Child element',
]
]
}]
What's the problem here? Since there can be many children elements, do I need to find a parent for each of those elements?
After looping i need array to be:
[{ id: 1 ,
name: 'Test 1'
},
{
id: 2 ,
name: 'Test 2'
},
{
id: 3 ,
name: 'Test 3'
}]
Wihout children elements.
What I am try:
allItems.map(item => item).filter(filteredItem => !filteredItem.children);
But this is no return me good results
Based on your expected output, here is my solution.
Also note, that you had missing curly braces with your children.
See you modified snippet below:
let allItems = [{
id: 1,
name: 'Test 1',
children: [{
id: 12,
title: 'Child element'
}]
},
{
id: 2,
name: 'Test 2'
},
{
id: 3,
name: 'Test 3',
children: [{
id: 12,
title: 'Child element',
children: [{
id: 123,
title: 'GRAND Child element',
}]
}]
}
]
console.log(allItems.map(item => {
return {
id: item.id,
name: item.name
}
}))
Using map and destructuring is a nice way to achieve what you're looking for
let allItems = [{
id: 1,
name: 'Test 1',
children: [{
id: 12,
title: 'Child element'
}]
},
{
id: 2,
name: 'Test 2'
},
{
id: 3,
name: 'Test 3',
children: [{
id: 12,
title: 'Child element',
children: [{
id: 123,
title: 'GRAND Child element',
}]
}]
}
];
const res = allItems.map(x => {
const {id, name} = x;
return {id, name};
});
console.log(res);
Use these propertys for call first child:
.firstchild==>this property calls first Node
.firstElementChild==>calls first element

what is wrong in the array map iteration

The Below code, not returning a flat array, is highly confusing, Need a flat array of deeply nested array of objects
have attached jsfiddle link https://jsfiddle.net/k6swuvox/
const arr = [{
id: 1,
name: 'XYZ 1'
}, {
id: 2,
name: 'XYZ 2',
children: [{
id: 5,
name: 'XYZ 5'
}, {
id: 6,
name: 'XYZ 6',
age: 29,
children: [{
id: 7,
name: 'XYZ 7'
}, {
id: 8,
name: 'XYZ 8'
}]
}]
}, {
id: 3,
name: 'XYZ 3'
}, {
id: 4,
name: 'XYZ 4'
}]
const flats = data => data.map(e => {
if (e.children) {
return [...flats(e.children), e]
} else {
console.log("E", e);
return e
}
})
console.log(flats(arr));
With your current code, you will sometimes return an array from the callback:
return [...flats(e.children),e]
and you'll sometimes return a plain object:
else {console.log("E",e);return e }
so the result will be a mix of arrays and plain objects, instead of an array of only objects.
Use flatMap instead, which will implement the flattening you're looking for for you. You'll also need to remove the .children property from items with children before returning them.
const arr=[{id:1,name:"XYZ 1"},{id:2,name:"XYZ 2",children:[{id:5,name:"XYZ 5"},{id:6,name:"XYZ 6",age:29,children:[{id:7,name:"XYZ 7"},{id:8,name:"XYZ 8"}]}]},{id:3,name:"XYZ 3"},{id:4,name:"XYZ 4"}];
const flats = data => data.flatMap(e=>{
const { children, ...objWithoutChildren } = e;
return children
? [...flats(children), objWithoutChildren]
: e;
});
console.log(flats(arr));
Here is an iterative solution using object-scan
// const objectScan = require('object-scan');
const arr = [{ id: 1, name: 'XYZ 1' }, { id: 2, name: 'XYZ 2', children: [{ id: 5, name: 'XYZ 5' }, { id: 6, name: 'XYZ 6', age: 29, children: [{ id: 7, name: 'XYZ 7' }, { id: 8, name: 'XYZ 8' }] }] }, { id: 3, name: 'XYZ 3' }, { id: 4, name: 'XYZ 4' }];
const flatten = objectScan(['**(^children$).id'], {
useArraySelector: false,
rtn: 'parent',
reverse: false,
afterFn: (state) => {
state.result = state.result.map(({ id, name }) => ({ id, name }));
}
});
console.log(flatten(arr));
// => [ { id: 1, name: 'XYZ 1' }, { id: 2, name: 'XYZ 2' }, { id: 5, name: 'XYZ 5' }, { id: 6, name: 'XYZ 6' }, { id: 7, name: 'XYZ 7' }, { id: 8, name: 'XYZ 8' }, { id: 3, name: 'XYZ 3' }, { id: 4, name: 'XYZ 4' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan
Need a flat array of deeply nested array of objects
In that case, I'd prefer recursive to get the flexible and high deep level array.
const arr = [{id:1,name:'XYZ 1'},{id:2,name:'XYZ 2',children:[{id:5,name:'XYZ 5'},{id:6,name:'XYZ 6',age:29,children:[{id:7,name:'XYZ 7'},{id:8,name:'XYZ 8'}]}]},{id:3,name:'XYZ 3'},{id:4,name:'XYZ 4'}];
const recursive = (array) => array.reduce((acc, {children = [], ...others}) => {
acc.push(others);
if(children.length > 0) // Base recurisve here.
acc = acc.concat(recursive(children));
return acc;
}, []);
console.log(recursive(arr));
==> As a result, the content structure will make sure like this

Get property of first objects in two arrays

I have two array of objects:
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' },
];
I need to create an array of strings that contains:
1. The Name of the first Book in books (if there are any)
2. The Names of the first 2 Cars in cars (if there are any)
I can do:
if (books.length > 0)
var bookA = books[0].name;
or:
if (cars.length > 1) {
var carA = cars[0].name;
var carB = cars[1].name;
}
Then build the string array but I believe there might be a better way to do this.
Can use filter() and map()
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' }
];
var res = [books[0], cars[0], cars[1]]
.filter(e => e)// remove undefined's
.map(({name:n}) => n)
console.log(res)
If you are using ES6. You can use [...array1,...array2] to merge them. So I slice the first item in book and use map to get a new array with only string name, and map it to result array.
For the cars array I slice the first two cars and do the same
var resultArray =[];
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' }
];
resultArray = [...resultArray, ...books.slice(0,1).map(v => v.name)]
resultArray = [...resultArray, ...cars.slice(0,2).map(v => v.name)]
console.log(resultArray)
One of a million ways to do it. This one would allow you to easily create a data structure (arrayDefinition) that configures what property to get from which array and at which index, which you could e.g. retrieve from a RESTful webservice.
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' },
];
const arrayDefinition = [
{
source: 'books',
index: 0,
prop: 'name'
},
{
source: 'cars',
index: 0,
prop: 'name'
},
{
source: 'cars',
index: 1,
prop: 'name'
}
];
let resultArr = []
arrayDefinition.forEach(function(def) {
if (Array.isArray(window[def.source]) && window[def.source].length >= def.index) {
resultArr.push(window[def.source][def.index][def.prop])
}
})
console.log(resultArr)

Categories