I have an array that is defined like this:
const arr = [
{
status: 'status1',
count: 2,
elements: [
{
id: '1',
},
{
id: '2',
},
],
},
{
status: 'status2',
count: 1,
elements: [
{
id: '3',
},
],
},
{
status: 'status3',
count: 1,
elements: [
{
id: '4',
},
],
},
];
The array is dynamic, but I know the status field and the id inside the elements. What I am trying to achieve is a new array, that contains all the same items, but looks like this instead:
const arr = [
{
status: 'status1',
count: 1,
elements: [
{
id: '1',
},
],
},
{
status: 'status2',
count: 2,
elements: [
{
id: '2',
},
{
id: '3',
},
],
},
{
status: 'status3',
count: 1,
elements: [
{
id: '4',
},
],
},
];
In other words, if status === status1, remove element with id === 2, set count to the length of elements array (or decrease by 1), take the element that has id === 2 and put it into elements array that has status === status2, set total count to the length of elements array .
I am not all that strong with iterating through nested elements and manipulating the array in these ways, but simpler concepts with array.filter/map etc seems okay, but I cannot wrap my head around all of this. :P
I would prefer to use vanilla JS, if possible. But I do have access to lodash if it makes things a lot easier.
Edit: When I mean vanilla JS, I refer to not using a ton of external libraries, but ES6 is what I use at this project.
My try consists of simply trying to filter arr on status, and then again on my id. Then I know what item I want to move. But it kind of stops there, and I also believe that it could be much more efficient:
const itemToMove = arr.filter(e => e.status === "status1")[0].elements.filter(e => e.id ==='2' )
I am not sure how to best proceed after this, thus asking for help.
You could find the source and target objects and splice the element.
const
move = (data, from, to, id) => {
const
find = value => ({ status }) => status === value,
source = data.find(find(from)),
target = data.find(find(to)),
index = source.elements.findIndex(o => o.id === id);
if (index === -1) return;
target.elements.push(...source.elements.splice(index, 1));
source.count--;
target.count++;
},
array = [{ status: 'status1', count: 2, elements: [{ id: '1' }, { id: '2' }] }, { status: 'status2', count: 1, elements: [{ id: '3' }] }, { status: 'status3', count: 1, elements: [{ id: '4' }] }];
move(array, 'status1', 'status2', '2');
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I have an array of objects that have specific IDs and another second array of objects that also have specified IDs. I want to delete objects from the first array that have the same ID as objects in the second array. How can I do it?
Is that what you're trying to do?
let arr1 = [{
id: 0,
name: 'x'
}, {
id: 1,
name: 'y'
}, {
id: 2,
name: 'z'
}];
const arr2 = [{
id: 0,
name: 'x'
}];
arr1 = arr1.filter(element => !arr2.find(el => el.id === element.id));
console.log(arr1);
Here is a link that seems to fit what you are trying to accomplish.
How to merge two arrays in JavaScript and de-duplicate items
You can simply achieve this requirement with the help of Array.filter() along with the Array.includes() method.
Live Demo :
const arr1 = [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}];
const arr2 = [{
id: 2
}, {
id: 3
}, {
id: 5
}];
const res = arr1.filter(obj => !arr2.map(({ id }) => id).includes(obj.id));
console.log(res);
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);
it possible to sort and rearrange an array that looks like this:
items:[{
id: '5',
name: 'wa'
},{
id: '3',
name: 'ads'
},{
id: '1',
name: 'fdf'
}]
to match the arrangement of this object:
Item_sequence: {
"5": {index: 1},
"1": { index: 0 }
}
Here is the output I’m looking for:
items:[{
id: '1',
name: 'fdf'
},{
id: '5',
name: 'wa'
},{
id: '3',
name: 'ads'
}]
You could check if the index is supplied and if not take a lage value for sorting by delta of two items.
var data = { items: [{ id: '5', name: 'wa' }, { id: '3', name: 'ads' }, { id: '1', name: 'fdf' }] },
sequence = { 5: { index: 1 }, 1: { index: 0 } };
data.items.sort(({ id: a }, { id: b }) =>
(a in sequence ? sequence[a].index : Number.MAX_VALUE) -
(b in sequence ? sequence[b].index : Number.MAX_VALUE)
);
console.log(data.items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
JavaScript specifically, First you have to apply loop to your array "items":
`
let newArr = [];
items.map(obj=>{
//obj will be the element of items array, here it is an object.
if(Item_sequence.obj[id] !== undefined) {
/*this condition will be satisfied when id from items array will be present as a
key in Item_sequence array*/
insertAt(newArr, Item_sequence.obj[id] , obj)
}
else{
newArr.push(obj);
}
})
//After checking on whole array here you assign a newArr value to items array.
items=newArr;
Hope that it will help you.
I have this:
products = [
{
'id': 1
'name: 'test'
},
{
'id': 2
'name: 'test'
},
{
'id': 3
'name: 'test'
}
... etc, etc
]
I need to restructure it to this:
products = [
row1: [
{
'id': 1
'name: 'test'
},
{
'id': 2
'name: 'test'
},
{
'id': 3
'name: 'test'
},
{
'id': 4
'name: 'test'
}
]
row2: [
{
'id': 5
'name: 'test'
},
{
'id': 6
'name: 'test'
},
{
'id': 7
'name: 'test'
},
{
'id': 8
'name: 'test'
}
]
row3: [.. etc, etc
]
the hard part is that the number of objects in each group is set using a variable (in this example the variable would be 4).
How can I achieve this using Typescript/Javascript? Its driving me mad!
Thanks
Option A: Custom Chunking/Translation Method
Given that you'd like to break the array into chunks without regard for item content, I'd suggest a simple loop to step through the array by chunk-size, slicing subsets as we go:
function chunk(array, chunkSize) {
// Create a plain object for housing our named properties: row1, row2, ...rowN
const output = {},
// Cache array.length
arrayLength = array.length;
// Loop variables
let arrayIndex = 0, chunkOrdinal = 1;
// Loop over chunks
while (arrayIndex < arrayLength) {
// Use slice() to get a chunk. Note the incrementing/assignment operations.
output[`row${chunkOrdinal++}`] = array.slice(arrayIndex, arrayIndex += chunkSize);
}
return output;
}
// Testing with a simplified demo array
console.table(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4));
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script><style>.as-console-wrapper{display:block}</style><script>console.config({timeStamps:false,maximize:true})</script>
This has a few advantages over some of the reduce() suggestions. This one:
Steps by chunk-size rather than 1; so, it performs fewer iterations.
Has no need for repeated, comparative logic or calculations for every index.
Defines each rowN property once and never has to check whether it already exists.
Uses the native Array.prototype.slice() method to select our subsets rather than pushing individual items one at a time.
Option B: Pre-chunk and Reduce
Alternatively, you could pre-chunk the array using a more generic (reusable?) method and then use Array.prototype.reduce() on the resultant, shorter, two-dimensional array. This mitigates many of the weaknesses of using reduce on its own and actually becomes faster than Option A at certain thresholds of input array length and chunk size:
function generateChunks(array, size) {
// Cache array.length
const length = array.length;
// Pre-size output array so we don't have to push/resize
const output = new Array(Math.ceil(length / size));
// Loop variables
let seekIndex = 0, outputIndex = 0;
// Loop over chunks
while (seekIndex < length) {
// Use slice() to get a chunk. Note the incrementing/assignment operations.
output[outputIndex++] = array.slice(seekIndex, seekIndex += size);
}
// Return our chunks
return output;
}
console.table(
// Pre-chunk
generateChunks([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4)
// Reduce to translate into the desired object
.reduce((output, chunk, index) => {
output[`row${index + 1}`] = chunk;
return output;
},{})
);
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script><style>.as-console-wrapper{display:block}</style><script>console.config({timeStamps:false,maximize:true})</script>
Either of these methods, A or B, should be considerably faster (~10x) than the accepted reduce answer, particularly as the input array length and/or chunk size increases.
Here's a jsbench comparing these options.
Your mileage may vary. Feel free to fork the test to try different input data or chunk sizes.
Use Array.reduce()
You can run a .reduce() method on your products array, like so:
var products = [
{ 'id': 1, name: 'test' },
{ 'id': 2, name: 'test' },
{ 'id': 3, name: 'test' },
{ 'id': 4, name: 'test' },
{ 'id': 5, name: 'test' },
{ 'id': 6, name: 'test' }
]
var numberOfObjects = 4 // <-- decides number of objects in each group
var groupedProducts = products.reduce((acc, elem, index) => {
var rowNum = Math.floor(index/numberOfObjects) + 1
acc[`row${rowNum}`] = acc[`row${rowNum}`] || []
acc[`row${rowNum}`].push(elem)
return acc
}, {})
console.log(groupedProducts)
Lodash groupBy is handy.
It takes an array, and an iterator function and groups the entries in the array accordingly.
The grouping logic can easily be done by counting the index for each iteration, and increment the group count on when the modulo (remainder) operator returns zero.
Using your example:
const { groupBy } = require("lodash");
const products = [
{ id: "1", name: "test" },
{ id: "2", name: "test" },
{ id: "3", name: "test" },
{ id: "4", name: "test" },
{ id: "5", name: "test" },
{ id: "6", name: "test" },
{ id: "7", name: "test" },
{ id: "8", name: "test" },
];
const myGroupingFunction = (val) => {
++index;
const groupLabel = "row" + groupCount;
if (index % groupSize === 0) {
++groupCount;
}
return groupLabel;
};
const groupSize = 2;
let groupCount = 1;
let index = 0;
const groupedEntries = groupBy(products, myGroupingFunction);
console.log("GroupedEntries: ", groupedEntries);
// GroupedEntries: {
// row1: [ { id: '1', name: 'test' }, { id: '2', name: 'test' } ],
// row2: [ { id: '3', name: 'test' }, { id: '4', name: 'test' } ],
// row3: [ { id: '5', name: 'test' }, { id: '6', name: 'test' } ],
// row4: [ { id: '7', name: 'test' }, { id: '8', name: 'test' } ]
//}
This will iterate through a list, group the entries in equally sized groups according to the groupSize variable, in the order they appear in the list.
If you want, you can also calculate the group number based on object values in the list. I'm incrementing an index instead.
https://lodash.com/docs/4.17.15#groupBy
This should return exactly what you are looking for:
const filteredProducts = {}
products.map((product, i) => {
const row = `row${Math.floor(i / 4) + 1}`
if (!filteredProducts[row]) filteredProducts[row] = []
return filteredProducts[row].push(product)
})
I have an array of objects:
[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
I'd like to strip out objects with duplicate Ids, leaving an array that would look like this:
[
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]
I don't care which objects are left, as long as each ID is unique. Anything in Underscore, maybe, that would do this?
Edit: This is not the same as the duplicate listed below; I'm not trying to filter duplicate OBJECTS, but objects that contain identical IDs. I've done this using Underscore - I'll post the answer shortly.
You can use reduce and some to good effect here:
var out = arr.reduce(function (p, c) {
// if the next object's id is not found in the output array
// push the object into the output array
if (!p.some(function (el) { return el.id === c.id; })) p.push(c);
return p;
}, []);
DEMO
the es6 way
function removeDuplicates(myArr, prop) {
return myArr.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos
})
}
Test it
let a =[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
console.log( removeDuplicates( a, 'id' ) )
//output [
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]
If you use underscore, you can use the _uniq method
var data = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
_.uniq(data, function(d){ return d.ID });
Produces a duplicate-free version of the array, using === to test object equality. In particular only the first occurence of each value is kept. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iteratee function.
Source: http://underscorejs.org/#uniq
Can use es6 Map collection mix with reduce
const items = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
const uniqItems = [...items.reduce((itemsMap, item) =>
itemsMap.has(item.id) ? itemsMap : itemsMap.set(item.id, item)
, new Map()).values()]
console.log(uniqItems);
Using findIndex should be the simplest solution.
array.filter((elem, index, arr) => arr.findIndex(e => e.id === elem.id) === index)
You can simply filter the array, but you'll need an index of existing IDs that you've already used...
var ids = [];
var ar = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
];
ar = ar.filter(function(o) {
if (ids.indexOf(o.id) !== -1) return false;
ids.push(o.id);
return true;
});
console.log(ar);
Here's some documentation on filter()...
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter