Flattern object in nested array of arrays Javascript - javascript

I have an array of arrays, which contain objects, would like to get the value of a certain key and return it as a big array, have tried a nested map but it returns multiple array's rather than a single array.
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
)
console.log(subItemIDs);
Expected output
[1, 2, 3, 4, 5, 6]
Actual output
[ [1,2,3], [4,5,6] ]

You can use arrays.flat(). I can provide more specific code once output is mentioned in the question
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]

You could take Array#flatMap to get a flat array from nested arrays.
const
items = [{ id: 1, sub_items: [{ id: 1 }, { id: 2 }, { id: 3 }] }, { id: 2, sub_items: [{ id: 4 }, { id: 5 }, { id: 6 }] }],
subItemIDs = items.flatMap(({ sub_items }) => sub_items.map(({ id }) => id));
console.log(subItemIDs);

Achieved this with:
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = [].concat(...items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
))
console.log(subItemIDs);

Sometimes, the obvious is the easiest:
Given a data structure that looks like this
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
A trivial function like this
function extract_item_ids( items ) {
const ids = [];
for ( const item of items ) {
for ( const {id} of sub_items ) {
ids.push(id);
}
}
return ids;
}
should do the trick. If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = [];
const pending = items;
while ( pending.length > 0 ) {
const item = pending.pop();
ids.push(item.id);
pending.push(...( item.sub_items || [] ) );
}
return ids;
}
And collecting the set of discrete item IDs is no more difficult:
If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = new Set();
const pending = [...items];
while ( pending.length > 0 ) {
const item = pending.pop();
ids.add(item.id);
pending.push(...( item.sub_items || [] ) );
}
return Array.from(ids);
}

As is the case with most things JavaScript, you have several options. Some are more efficient than others, others have a certain stylistic purity, others might better speak to your fancy. Here are a few:
Array.flat
With array flat you can take your original code and have the JS Engine flatten the array down to a one-dimensional array. Simply append .flat() onto the end of your map.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
).flat()
console.log(subItemIds);
Array.reduce
Another method is to use reduce to iterate over the object and build an accumulation array using Array.reduce. In the example below, when pushing onto the array, the spread operator (...) is used to break the array into elements.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.reduce((arr,item) => (
arr.push(...item.sub_items.map((subItem) => subItem.id)), arr
),[])
console.log(subItemIds);
Other
Other answers here make use of custom functions or Array.flatMap, which should be explored as they could lead to more readable and efficient code, depending on the program's needs.

Related

Being able to remove duplicate keys from an array of objects

I have a question about how I can delete the existing elements, for example, in my case "Tallas" is repeated, could you please help me? Thank you very much to those who are willing to help me to solve this problem
const data =
[ { atributos: { Tallas: [{ id: 0, name: 'XS' }, { id: 1, name: 'S' }] }}
, { atributos: { Calzado: [{ id: 0, name: '10' }, { id: 1, name: '9.5' }] }}
, { atributos: { Tallas: [{ id: 0, name: 'XS' }] }}
]
The idea is to have this json format with the last "Tallas" since it is the last one that I added through my dynamic form.
const expected =
[{ atributos: { Calzado: [{ id: 0, name: '10' }, { id: 1, name: '9.5' }] }}
, { atributos: { Tallas: [{ id: 0, name: 'XS' }] }}
]
How do I do this is there a way to do it, I've tried with filter plus the findindex but I can't get to eliminate the repetition of the json res= new.filter((arr, index, self) => index === self.findIndex( (t) => (t.attributes === arr.attributes )))
To unique the array of objects, we can use the Javascript Set module, if the array has complex nested objects, we can stringify each object before creating new Set data. this below function will unique the array of complex objects.
function unique_array(array = []) {
const newSetData = new Set(array.map((e) => JSON.stringify(e)));
return Array.from(newSetData).map((e) => JSON.parse(e));
}
this is a function that takes an array and return the same array but delete every duplicated item
function removeDuplicates(arr) {
return arr.filter((item,
index) => arr.indexOf(item) === index);
}
I didn't understant the part written in spanish so I hope this is what you are looking for
This is a solution specific to your question. this is not a generic solution.
const data = [
{
atributos: {
Tallas: [
{ id: 0, name: "XS" },
{ id: 1, name: "S" },
],
},
},
{
atributos: {
Calzado: [
{ id: 0, name: "10" },
{ id: 1, name: "9.5" },
],
},
},
{
atributos: {
Tallas: [
{ id: 0, name: "XS" },
{ id: 1, name: "S" },
],
},
},
];
function uniqueArray(array) {
const resultObject = array.reduce((acc, eachValue) => {
let keys = Object.keys(eachValue.atributos);
keys.forEach((eachKey) => {
if (!acc[eachKey]) {
acc[eachKey] = [];
}
let list = eachValue["atributos"][eachKey].map(
(each) => each.id + "-" + each.name
);
acc[eachKey].push(...list);
});
return acc;
}, {});
const resultArray = Object.keys(resultObject).reduce((acc, each) => {
let setData = Array.from(new Set(resultObject[each]));
acc.push({
atributos: {
[each]: setData.map((e) => {
return { id: e.split("-")[0], name: e.split("-")[1] };
}),
},
});
return acc;
}, []);
return resultArray;
}
const result = uniqueArray(data)
console.log("result ", JSON.stringify(result, null, 2));

typescript/javascript remove object partial repetition in nested array of objects

I have nested array of objects that looks like this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 1 }, { id: 2 }],
[{ id: 4 }, { id: 5 }, { id: 6 }],
]
Since objects with id 1 and 2 are already together in nestedArray's first element I want to remove the second element and maintain other elements without petition as they are. The result should be like this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 4 }, { id: 5 }, { id: 6 }]
]
How do I write a filter function by id to get the expected result?
As I see in your example:
the id are unique in each subarray
duplicate sub-array elements only exist in the previous sub-array
if the first element of a sub-array exists in the previous sub-array then all the other elements must also be
const nestedArray =
[ [ { id: 1} , { id: 2} , { id: 3} ]
, [ { id: 1} , { id: 2} ]
, [ { id: 4} , { id: 5} , { id: 6} ]
]
function arrCleaning(arr)
{
for (let i=arr.length;i--;)
{
if (i>0 && arr[i-1].some(x=>x.id===arr[i][0].id) )
arr.splice(i,1)
}
}
arrCleaning( nestedArray )
// result
console.log( 'nestedArray = [' )
nestedArray.forEach(e=>console.log(' ',JSON.stringify(e).replaceAll('"',''),','))
console.log(' ]')
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row::after { display:none !important; }
Try this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 1 }, { id: 2 }]
]
var newArr = nestedArray.flat(2).filter((x, index, self) => index === self.findIndex((t) => (t.id === x.id)));
console.log(newArr);

How to compare two array with object?

I compare id with two array with object.
Here is my function:
array1 = [
{ id: 1 },
{ id: 2 },
{ id: 3 }
];
array2 = [
{ id: 1 },
{ id: 2 },
{ id: 3 }
];
const compareFunction = (array1, array2) => {
array2.map((allValue) => {
array1.map((value) => {
allValue.selected = value.id === allValue.id;
});
})
return array2;
}
I think I will get the array2 like
[{ id: 1, selected: true }, { id: 2, selected: true },{ id: 3, selected: true }]
but actually array2 become
[{ id: 1, selected: false }, { id: 2, selected: false },{ id: 3, selected: true }]
Only the last array argument selected become true.
Which step was wrong ? Thanks.
Convert the 2nd array to a Set of id values. Iterate the 1st array with a Array.map() and create a new object for each item, by spreading the current object, and adding the selected value. To get the selected value check if the Set contains that current item id.
const array1 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const array2 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const a2Set = new Set(array2.map(o => o.id))
const result = array1.map(o => ({ ...o, selected: a2Set.has(o.id) }))
console.log(result)
checkout this :
array1 = [{ id: 1 },{ id: 2 },{ id: 3 }];
array2 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const compareFunction = (array1, array2) => {
const result = [];
array2.forEach(arr2item => {
let selected = false;
for(let arr1item of array1){
selected = arr1item.id === arr2item.id;
if(selected)break;
}
result.push({id : arr2item.id , selected : selected});
});
return result;
}
console.log(compareFunction(array1 , array2));

Move selected array index to a specified location

I'm trying to reorder a selected number of array base on selected ids. For example, I have an array of:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
And a selected id of 1,2,4. The proper arrangement should be:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 }]
I've managed to get the arrangement to work for one selected id, but when multiple ids are selected it fails on different test cases. These all asume the same input as above.
Input 1: [ 1, 2, 4 ], move to index 1:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 } ]
Input 2: [ 1, 3, 4 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 3: [ 1, 3, 5 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 5 }, { id: 4 } ]
Input 4: [ 1, 2 ], move to index 0 or 1:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 5: [ 4, 5 ], move to index 3 or 4:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
/**
* Function to move array
*/
function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
/**
* Function to find the index
*/
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
return -1;
}
/**
* Move array to specified position
*/
function moveToSpecifiedInput(selectedImage, iMoveTo) {
selectedImage.reverse();
selectedImage.forEach(function(aData) {
let old_index = findWithAttr(aImageData, 'id', aData);
let new_index = iMoveTo - 1;
array_move(aImageData, old_index, new_index);
});
}
From the clarification in the comments, I think I understand your problem. This is how I’d go about solving it:
function sortArray(array, sortProp, sortValues, sortIndex) {
const elemsBySortVal = array.reduce((obj, elem, idx) => {
obj[elem[sortProp]] = idx;
return obj;
}, {});
let sortedKeys = sortValues.map(val => elemsBySortVal[val]);
let sortedItems = sortedKeys.map(key => array[key]);
let remainingItems = array.filter((_, idx) => !sortedKeys.includes(idx));
return [
...remainingItems.slice(0, sortIndex),
...sortedItems,
...remainingItems.slice(sortIndex),
];
}
console.log(sortArray(
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ],
'id',
[ 1, 2, 4 ],
1,
));
This solution works in three phases:
Phase 1:
This is the most complex part. Here, we take your input array and create a map of sort values against input indices. This is best shown by example:
Input (array):
[ { id: 1 }, { id: 3 }, { id: 5 }, { id: 2 }, { id: 4 } ]
Output (elemsBySortVal):
{
1: 0,
3: 1,
5: 2,
2: 3,
4: 4,
}
Phase 2:
Now we use that map to fetch the indices in the input array of the values passed as the sort values:
Input (sortValues):
[ 1, 2, 4 ]
Output (sortedKeys):
[ 0, 3, 4 ]
This is then mapped to the elements from the input array:
Input (sortedKeys):
[ 0, 3, 4 ]
Output (sortedItems):
[ { id: 1 }, { id: 2 }, { id: 4 } ]
And finally, the remaining items are selected from the input array by using sortedKeys to exclude the already sorted ones:
remainingItems:
[ { id: 3 }, { id: 5 } ]
Note that all operations in phase 2 maintain the order of these arrays, even when elements are removed.
Phase 3:
Now we assemble the output array in 3 parts:
Elements before the sorted section
Elements in the sorted section
Elements after the sorted section
The before and after parts are sliced from remainingItems using sortIndex as a cut point, and the sorted section is simply sortedItems from the previous phase.
Remove the elements that match the IDs from the array, and put them in a new array. Then splice that new array back into the original array.
function move_array_elements(array, ids, new_index) {
let extracted = [];
ids.forEach(id => {
let index = array.findIndex(el => el.id == id);
if (index != -1) {
extracted.push(array[index]);
array.splice(index, 1);
}
});
array.splice(new_index, 0, ...extracted);
return array;
}
const orig_array = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ];
console.log(move_array_elements(orig_array, [1, 2, 4], 1));
You can use filter and splice
Please check the comments
let arr=[{id:1}, {id:2}, {id:3}, {id:4}, {id:5}];
let selected=[1,2,4];
let move_2_index=1;
//filter out selected
const selectedonly = arr.filter(a=>selected.includes(a.id));
//filter out not selected
const notselected = arr.filter(a=>!selected.includes(a.id));
//splice to insert selected on not selectd
notselected.splice(move_2_index, 0, selectedonly);
//flattern the array
final_array=notselected.flat();
console.log(final_array);
Using null as a Placeholder
Details in demo.
Demo
let arr=[{id:1},{id:2},{id:3},{id:4},{id:5}];
Array.prototype.move = function(to, moveFrom) {
// Make a copy of the original array
let that = [...this];
// Declare an empty array
let moveTo = [];
/*
- Compare current index with the numbers in move array (offset)
- Replace each match with a null as a placeholder so that the
indexes of the array is still accurate.
- Place each match into the empty array from previous step.
*/
that.forEach(function(obj, idx, arr) {
if (moveFrom.indexOf(idx +1) !== -1) {
moveTo.push(arr.splice(idx, 1, null));
}
});
// Remove all nulls
that = that.filter(function(obj) {
return obj !== null;
});
/*
Insert the new moving array into the copied array at the index
indicated in the first parameter.
*/
that.splice(to, 0, moveTo.flat())
return that.flat();
};
console.log(JSON.stringify(arr.move(1, [1, 3, 4])));
console.log(JSON.stringify(arr.move(0, [1, 2, 5])));
console.log(JSON.stringify(arr.move(3, [1, 2, 3])));
console.log(JSON.stringify(arr.move(1, [2, 5])));
console.log(JSON.stringify(arr.move(2, [1, 4])));

Add property from one array into another array with the same key

Instead of explaining the problem in words, I've just made a quick visual representation below.
Say I have the following array:
let arr1 = [
{
id: 1,
someKey: someValue
},
{
id: 2,
someKey: someValue
},
]
and another array:
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
]
How would I create the following array?
let result = [
{
id: 1,
someKey: someValue,
numberOfItems: 10
},
{
id: 2,
someKey: someValue,
numberOfItems: 10
},
]
So as you can see, both arrays have the same id value. I want to take numberOfItems: 10 from the second array and place it into the first array under the same id.
Note: the two ids are completely different, have different properties and length. The only similarity is the id
You could first create a map with id as key and then combine the objects This would solve the problem in O(n):
let arr1 = [
{
id: 1,
someKey: 3
},
{
id: 2,
someKey: 6
},
];
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
];
let arr2Map = arr2.reduce((acc, curr) => {
acc[curr.id] = curr
return acc;
}, {});
let combined = arr1.map(d => Object.assign(d, arr2Map[d.id]));
console.log(combined);
let arr1 = [
{
id: 1,
someKey: 3
},
{
id: 2,
someKey: 6
},
];
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
];
let idToNumberOfItem = {};
arr2.forEach(({id, numberOfItems})=> idToNumberOfItem[id]=numberOfItems)
arr1.forEach((item)=> item['numberOfItems'] = idToNumberOfItem[item.id])
console.log(arr1);

Categories

Resources