Fastest possible object remapping for nested structure - javascript

Assume we have two different structures from two different APIs. Each has a different schema.
We have this as a return from API #1
[
{
Id: "test1",
Title: "label 1",
Children: [
{
Id: "test2",
Title: "label 2",
Children: [
{
Id: "test3",
Title: "label 3"
}
]
}
]
}
]
I need to convert it to the following scheme:
[
{
value: "test1",
label: "label 1",
children: [
{
value: "test2",
label: "label 2",
children: [
{
value: "test3",
label: "label 3"
}
]
}
]
}
]
So far I have come up with this method:
const transformItem = ({ Id, Title, Children }) => ({
value: Id,
label: Title,
children: Children ? transformData(Children) : null
});
const transformData = arr => arr.map(item => transformItem(item));
// Process data
const DataForApi2 = transformData(DataFromApi1);
From the limited benchmarking I performed and from what I can tell, in V8 (which is 95+% of our userbase) this looks fast enough as I'm not mutating any data structure (ergo hot objects are intact and retain performance) and using everything under a scope so I don't waste memory. Seems to be of linear complexity and not too bad if only performed once per client loading the app (only the first time after login).

In terms of runtime you're right this is probably the fastest we can get with O(n).
You could improve your space complexity by converting your solution from recursive to iterative. It saves space on the callstack which helps in extreme cases where trees go extremely deep.

Related

Find elements with specific value in a nested array with unknown depth

I have an array with objects for which the nesting depth is unknown and can be different. An example of such an array looks like that:
let exampleArray = [
{
id: 'some-id',
label: "Item 1",
children: [
{
id: 'some-id',
label: "Child 1",
children: [
{
id: 'some-id', // How to find this for example?
label: "Child 2",
children: []
}
]
}
]
},
{
id: 'some-id',
label: "Item 2",
children: [
{
id: 'some-id',
label: "Child 1",
children: [
{
id: 'some-id',
label: "Child 2",
children: []
}
]
}
]
}
]
Each array item can have a nested array children. The basic structure is the same as the parent one.
The challenge
When I for example want to delete a nested element with a specific id, how could I do this? I think it would not be the best practice to start iterations with a static number of loops, because I don't know how big the array is nested.
If I know the ID from the children element, can I say something like: "Iterate the entire array including all nesting and find the element with the ID xy?".
Or what would be the best practice to handle such nested arrays?
A recursive function is likely what you're looking for:
function deleteById(data, id){
for(var x = 0; x < data.length; x++){
if(data[x].id === id){
data.splice(x, 1); // if it matches, remove it from the array
}
else{
deleteById(data[x].children, id);
}
}
}

Returning the ids from the object within the arrays

I have a multidimensional javascript array of objects that I am trying to use to simply collate the Unit id into a new array as shown below.
What is the best solution for returning the id within the inner value so I just get an array of the ids whatever I try seems to not work
[
{
units: [
{
id: 10000282,
name: "Group 1",
},
{
id: 10000340,
name: "Group 2",
},
{
id: 10000341,
name: "Group 3",
},
],
},
{
units: [
{
id: 10000334,
name: "Group 4",
},
],
},
]
Expected output - just return an array with simply the ids
e.g
ids = [ 10000282, 10000340, 10000341, 10000334 ]
Assuming that data is in variable data:
> data.map(o => o.units.map(u => u.id)).flat()
[ 10000282, 10000340, 10000341, 10000334 ]
This assumes you're in an environment where .flat() is a thing.
If that's not the case, the longer way around is
const ids = [];
data.forEach(o => {
o.units.forEach(u => {
ids.push(u.id);
});
});

Javascript Map a Collection

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!)

Avoid databinding on nested arrays - vue.js

I'm getting headache avoiding databinding on nested arrays.
Let's say i have 2 objects:
Items, array of all items
Item, a single item object
I use
Object.assign({}, object)
to avoid databinding, bit this only works for non-nested array fields.
Example:
data: {
items: [
{
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
},
{
name: 'Rubber container',
contents: [
{title: "Yellow Rubber"}, {title: "Green Rubber"}
]
},
],
selected_item: {
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
}
},
mounted() {
this.selected_item = Object.assign({}, this.items[0]);
}
There's no databinding on name, but there is still binding on contents.title for example. I absolutely need to assign the object absolutely without databinding.
Here's a JSFIDDLE.
In the first input binding on "title" is real, while in the second input there are no binding on "name" as expected. I can't get over it, help me please.

Select first element from inner array

I'd like to select the first item from a nested Array, without fetching the whole document.
Schema/Model
Suppose I have a Schema like so:
const parentSchema = mongoose.Schema({
name: String,
children: []
});
const grandparentSchema = mongoose.Schema({
name: String,
children: [parentSchema]
})
Which would translate to this example instance:
{
name: 'Grandparent Foo',
children: [
{
name: 'Parent Foo',
children: ['Child Foo', 'Child Bar', 'Child Baz']
}
]
}
Question
I would like to get the first child of 'Parent Foo', so to boil it down I should be getting back 'Child Foo'
Notes
As you can see, the grandchildren are plain Strings, not Documents themselves (in contrast with the Parent) so I can't select them using dot notation.
I don't want to return the whole document and filter through it in code. I'd like to get over the wire only the first grandchild since the grandchildren Array (the children array of 'Parent Foo') can potentially contain millions of entries.
I need this because I want to $pop the first grandchild and return it. To do that, I plan on fetching the item first and then $pop it off, hence why I ask this question
You cannot really, without throwing extra work at the database.
As a general explanation:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
)
Will return just the matched entry from "children" and no others should they exist.
If you explicitly need the "first" array element, then you use .aggregate():
Granparent.aggregate([
{ "$match": { "children.name": "Parent Foo" } },
{ "$addFields": {
"children": {
"$map": {
"input": {
"$filter": {
"input": "$children",
"as": "child",
"cond": { "$eq": [ "$$child.name", "Parent Foo" ] }
}
},
"as": "child",
"in": {
"name": "$$child.name",
"children": { "$arrayElemAt": [ "$$child.children", 0 ] }
}
}
}
}}
])
So there you basically use $filter to replicate the standard positional match an then use $map to reshape with $arrayElemAt or $slice to actually get the first element of the inner array.
By contrast, if you live with returning "a small amount of extra data", then you just slice off of the positional match:
Grandparent.find(
{ "children.name": "Parent Foo" },
{ "children.$": 1 }
).lean().exec((err,docs) => {
docs = docs.map( doc => {
doc.children = doc.children.map( c => c.children = c.children.slice(0,1) );
return doc;
});
// do something with docs
So we returned a little more in the cursor and just got rid of that very little bit of data with minimal effort.
Mileage may vary on this due to the actual size of real data, but if the difference is "small", then it's usually best to "trim" in the client rather than the server.

Categories

Resources