There is an array of data that needs to be converted to a tree:
const array = [{
id: 5,
name: 'vueJS',
parentId: [3]
}, {
id: 6,
name: 'reactJS',
parentId: [3]
}, {
id: 3,
name: 'js',
parentId: [1]
}, {
id: 1,
name: 'development',
parentId: null
}, {
id: 4,
name: 'oracle',
parentId: [1,2]
}, {
id: 2,
name: 'data-analysis',
parentId: null
}];
Now it works using this function:
function arrayToTree(array, parent) {
var unflattenArray = [];
array.forEach(function(item) {
if(item.parentId === parent) {
var children = arrayToTree(array, item.id);
if(children.length) {
item.children = children
}
unflattenArray.push(item)
}
});
return unflattenArray;
}
console.log(arrayToTree(array, null));
I have two problems with this feature:
The value of "parentId" should be an array of id, for example -
"parentId": [2, 3]
How to transfer to function only one argument - "array"?
https://codepen.io/pershay/pen/PgVJOO?editors=0010
I find this question confusing. It sounds like what you are really saying is the array represents the “definition of node types in the tree” and not the actual instances of those nodes that will be in the tree.
So your problem is you need to copy the “definitions” from the array to new “instance” nodes in your tree. This would let “Oracle” show twice, as you’d create a new “oracle instance” node for each parent in its parent array. It wouldn’t technically need to be a deep copy depending on your use, so you could proof of concept with Object.assign, but each instance would point to the same parents array and that may or may not cause problems for that or future reference values you add to the definition.
Finally, depending on the size of the tree and what you are really trying to do, you might want to convert to a tree represented by nodes/edges instead of parent/children. For really large datasets recursion can sometimes cause you problems.
Sorry I’m on my phone so some things are hard to see on the codepen.
Related
This question already has an answer here:
Lodash uniqBy update the latest value
(1 answer)
Closed 9 months ago.
I am working on one project where I need to remove duplicate values from JSON array object with some specification in react JS. I have tried to remove using _.uniqBy but in the output it took very first value from duplicate value which is I don't want.
Suppose You have an array JSON like:
[ { id: 1, name: 'bob' }, { id: 2, name: 'bill' }, { id: 1, name: 'alice' } ]
using _.uniqBy I got [ { id: 1, name: 'bob' }, { id: 2, name: 'bill' }] this output.
but I want [ { id: 2, name: 'bill' }, { id: 1, name: 'alice' } ] this output.
As you can see I want output whose name is alice not bob along with id:1.
Can anyone help me?
Thank you.
My first thought is to use a reduce, and shove the items in a map, then get the values:
Object.values(items.reduce((map, item) => ({ ...map, [item.id]: item }), {}))
This is probably not very efficient though if you're dealing with large arrays of have performance concerns.
It's a quick and dirty one-liner. If you want something more efficient I'd take a look at the lodash source code and tweak it to your needs or write something similar:
https://github.com/lodash/lodash/blob/2f79053d7bc7c9c9561a30dda202b3dcd2b72b90/.internal/baseUniq.js
I am trying to achieve something a bit complex.
I am working on a grouping feature where I have to get the items containing the key checked: true then I need to put them together within the key/array named questionGroup in the first item in the array with checked: true and then deleting the other items that were grouped.
See the structure of the data:
const dummyQuestions = [
{
id: 1,
checked: false,
question: {...},
questionGroup: []
},
{
id: 2,
checked: true,
question: {...},
questionGroup: []
},
{
id: 3,
checked: false,
question: {...},
questionGroup: []
},
{
id: 4,
checked: true,
question: {...},
questionGroup: []
},
{
id: 5,
checked: true,
question: {...},
questionGroup: []
}
];
So lets say you clicked on a certain button which fires a grouping function, as you noticed index 1, 3 and 4 have checked: true, so this must happen:
const dummyQuestions = [
{
id: 1,
checked: false,
question: {...},
questionGroup: []
},
{
id: 2,
checked: true,
question: {...},
questionGroup: [
{
id: 2,
checked: true,
question: {...},
questionGroup: []
},
{
id: 4,
checked: true,
question: {...},
questionGroup: []
},
{
id: 5,
checked: true,
question: {...},
questionGroup: []
}
]
},
{
id: 3,
checked: false,
question: {...},
questionGroup: []
}
];
That's how the data must be after grouping it.
Do you get the idea?
I created a reproducible demo => https://codesandbox.io/s/ts-react-grouping-odxs1?file=/src/App.tsx
EDIT
This is the relevant code:
const handleGroupQuestions = (): void => {
const questionAddedCopy: VariableConfig[] = [...questionAdded];
// Only the questions with checked === true will be grouped.
const filteredCopy: VariableConfig[] = questionAddedCopy.filter(
(q: VariableConfig) => q.checked === true
);
const grouped: VariableConfig[] = [
...questionAdded.filter((q: VariableConfig) => filteredCopy.includes(q))
];
for (let i = 0; i < grouped.length; i++) {
// Here I compare the ids and I put them together into the key questionGroup
if (filteredCopy[i].id === grouped[i].id) {
// I had to set any instead of VariableConfig type because otherwise TS throws an error
addQuestion((q: any) => {
const questionsCopy = [...q];
const mergedQuestions: VariableConfig[] = [
...questionsCopy,
{ ...grouped[i], questionGroup: grouped }
];
// With this `unique` function what I am trying to achieve (which I am not) is
// to have a unique ID per every question/item in the array.
// I need to add the questions to questionGroup to the highest item containing check = true
// I need to delete from the main array, the items going inside questionGroup.
const unique = [
...mergedQuestions.filter((c) => c.id !== questionsCopy[i].id)
];
return unique;
});
break;
}
}
};
And to explain more in deep and to answer your questions.
We see that the first item in the array with checked: true is the item with the id 2, so that will be the place where I have to stored the other items with checked: true because that is the highest item in the array, then I need to delete from the array the items that were grouped into questionGroup.
The id 2 in this case is repeated and that is a requirement, because one id is for the parent and the other one within questionGroup is its own child, get it? Like if it is storing itself into questionGroup.
This is just a grouping functionality where I am saving some steps. Imagine every item contains a checkbox, so the items that you want to group, you have to check them first, then click the GROUP THEM button, the checked items disappear and then get together into the highest checked index, keeping its id and storing itself as well.
Take a look at the arrays I posted. There you can see what is the exact output I need.
Here is one technique:
// helper function
const addChecked = (q, qs) =>
({...q, questionGroup: qs .filter (p => p .checked)})
// main function
const nestChecked = (qs, found = false) =>
qs .flatMap (q => q .checked ? found ? [] : (found = true, [addChecked (q, qs)]) : [q])
// sample data
const dummyQuestions = [{id: 1, checked: false, question: 'foo', questionGroup: []}, {id: 2, checked: true, question: 'bar', questionGroup: []}, {id: 3, checked: false, question: 'baz', questionGroup: []}, {id: 4, checked: true, question: 'qux', questionGroup: []}, {id: 5, checked: true, question: 'corge', questionGroup: []}]
// demo
console .log (JSON .stringify (nestChecked (dummyQuestions), null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}
We have a simple helper function that groups all the checked elements of the input into (a copy of) a specific question.
Our main function iterates over the questions with .flatMap, which allows us to do a filter and map in a single traversal. We maintain a flag, found which tells us if we've already handled the checked elements. For each question, if it's not checked, we simply include it, by returning it wrapped in an array, as [q] from the flatMap callback. If it is checked, we evaluate the found flag. If that is set to true, then we return and empty array. If it's not, we return the result of calling our helper function above, again wrapped in an array. (These array wrappers are not specifically needed for this problem; but I like to return a consistent type in the flatMap callback.)
We could easily inline that helper function, as it's only called once. In fact that's how I originally wrote it. But it feels cleaner to me separated.
It's not clear to me if there is some iterative process which will then further nest these. If so, you might have to do something more sophisticated in the helper function dealing wit already-populated questionGroup fields. But that would probably be for a different question.
Finally, we also might want to avoid that default parameter. There are sometimes good reasons to want to do so. I leave this change as an exercise for the reader. :-)
I need to display a acyclic directed graph looking somewhat like this:
I created a nested hierarchical Data Structure similar to this:
[
{
node: 'abs'
children: [
{
node: 'jhg',
children: [{...}]
{
node: 'AAA',
children: [{...}]
},
{
node: 'fer'
children: [
{
node: 'AAA',
children: [{...}]
{
node: 'xcv',
children: [{...}]
},
{
]
I am not sure if this is the best way to actually display the data since the nodes with multiple parents and their children would appear multiple times, but I had no other idea how to handle it yet.
I simply want to render those nodes to an imaginary grid. Therefore i need to parse my data structure and set their grid values. The problem is i dont know how to parse the data structure with hierarchy logic.
What I am doing right now is obviously causing problems for nodes with multiple parents:
for (const root of allRoots) {
currentLevel = 0;
if (root.node === 'VB8') {
getChildrenTree(root);
}
}
function getChildrenTree(node) {
currentLevel++;
node._gridX = currentLevel;
if (node.children.length > 0) {
for(const nextChild of children ) {
getChildrenTree(nextChild);
}
}
The Problem with this code is that it will only run through one path and then stop when there arent any children.
I just need an algorithm which runs through the tree and sets each nodes hierarchy level.
I hope that this is not too confusing.
If you want to reference the same node from two separate parents, you shouldn't define it more than once. I suggest listing all nodes in a flat array with a single 'invisible' root node and referencing children by id or array index:
[
{id: 0, name: "root", children: [1, 2]},
{id: 1, name: "abs", children: [3, 4]},
{id: 2, name: "fer", children: [5, 6]},
{id: 3, name: "jhg", children: [...]},
{id: 4, name: "AAA", children: [...]},
...
]
then you can recursively set their tree depths like so:
function setDepth(node, depth) {
if (node._gridX && node._gridX >= depth) {
// node has been visited already through a path of greater or equal length
// so tree depths wouldn't change
return
}
node._gridX = depth
node.children
.map(idx => nodeArray[idx]) // get the actual objects from indices
.forEach(child => setDepth(child, depth+1))
}
setDepth(nodeArray[0], 0) // start at root
... careful though, as this algorithm will get stuck in a loop if your nodes have any cycles
The Issue:
I'm attempting to build a simple search tool. It returns a search query by matching an id to another item with the same id. Without going into the complexities, the issue I'm having is that when my data was organized previously, the map function from javascript returned all the results perfectly. However, now that my data is structured a bit differently (a collection, I think?) ....the ids don't appear to be lining up which causes the wrong search results to show.
The function in question:
const options = this.props.itemIds.map((id) => (
<Option key={this.props.itemSearchList[id].id}>
{this.props.itemSearchList[id].name}
</Option>
));
When the data was structured like this it worked as expected:
Example of previous structure:
const items = [
{
id: 0,
name: "name 0",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 2,
name: "Name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
Now that the data is a ?collection...the map function doesn't work as anticipated and it returns improper results or none at all: I've been able to use the lodash Map function on this structure successfully in the past.
Here's a screenshot of the new data:
I believe a representative way to write out the example would be:
const newItems = [
0: {
id: 0,
name: "name here",
},
1: {
id: 1,
name: "name here",
},
]
Any recommendations for making this work or need more info? Perhaps I'm misunderstanding the issue entirely, but I believe it has to do with data structure and the map function from JS. I can see results returning, but the id's are not lining up appropriately anymore.
Here's a visual representation of the misalignment. The orange is the search input and it pulling the right result. The green is the misalignment of what it's actually showing because of the data structure and mapping (I assume).
The issue is you were using index and lining that up with id as a sort of pseudo-key which is...beyond fragile. What you should be doing is keying by id (meaing itemsshould be an object) and then having a seperate array that stores the order you want. So items would be an object keyed by id:
const items = {
1: {
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
2: {
id: 2,
name: "name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
9: {
id: 9,
name: "Name 9",
tags: ['#sports', '#outdoor', '#clothing'],
},
};
And then itemIds (which it appears you already have) is an array with the correct order:
const itemIds = [1,9,2];
And then they can be accessed in the right order by looping over that array, and getting the element by said key:
itemIds.map((id) => {
const item = items[id];
// do something with the item
}
Take a look at how Redux recommends normalizing state shape.
https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
What you call "collections" and "maps" are actually arrays. Now one of the arrays has the objects exactly at the position in the array that matches the id:
items[5].id === 5
Now through sorting /mutating / whatever you change the order so that the element at a certain position doesnt have that as an id:
newItems[5].id // 7 :(
That means that you cannot access the item that easy anymore, you now either have to sort the array again to bring it into order, or you search for an object with the id:
newItems.find(item => item.id === 5) // { id: 5, ... }
Or you switch over to some unsorted collections like a real Map:
const itemsMap = new Map(newItems.map(item => ([item.id, item])));
So you can get a certain item with its id as:
itemsMap.get(5) // { id: 5, ... }
... but the whole thing doesnt have to do with Array.prototype.map at all.
Here was my simple solution:
const options = [];
this.props.itemList.forEach((item) => {
if (this.props.searchResults.includes(item.id)) {
options.push(<Option key={item.id}>{item.name}</Option>);
}
});
Let me know what you think (to the group that tried to help!)
So I have some code which has a requirement of calling xprod with (input, input), similar to as follows:
const input = [
{ id: 1, data: 'a' },
{ id: 2, data: 'b' },
];
const product = xprod(input, input);
/*
[
[ { id: 1, data: 'a' }, { id: 1, data: 'a' } ],
[ { id: 1, data: 'a' }, { id: 2, data: 'b' } ],
[ { id: 2, data: 'b' }, { id: 1, data: 'a' } ],
[ { id: 2, data: 'b' }, { id: 2, data: 'b' } ],
]
*/
I'd like to filter tuples in the list above by comparing the first element of the tuples to the second element in the same tuple. In this case, to remove the tuples which contain objects which have equal ids (so the 0th and 3rd elems should be filtered out -- I know in this simplified example I could use strict equality to filter, too, but that's often not the case in the code I'm actually writing).
I know I can accomplish this pretty simply with lambdas, but since I find myself ending up with this sort of data (lists of tuples) fairly often when working with ramda, I often get stuck on trying to compare one item in a tuple to another item in the same tuple in a points free manner. And maybe that's an argument to just keep it simple and use the lambda, but I'm curious if there's a different way to do it.
Here's a link to a ramda repl containing an implementation.
One option is to simply wrap a function that expects the two arguments of the tuple with R.apply. In your example that could be a partially applied R.eqProps.
R.filter(R.apply(R.eqProps('id')), product)