Original array changed without any modifications done - javascript

I am trying to remove duplicate objects from an array, and keep only the objects which have the highest nb value.
Example:
From this array:
let arr = [
{id: 1, nb: 1},
{id: 1, nb: 4},
{id: 2, nb: 1},
{id: 3, nb: 1},
{id: 1, nb: 2},
{id: 1, nb: 3},
{id: 2, nb: 7},
{id: 2, nb: 8},
];
I am supposed to get this:
arr2 = [
{ id: 1, nb: 4 },
{ id: 2, nb: 8 },
{ id: 3, nb: 1 }
]
The algorithm below is very correct in theory, however I see the original array is modified by the end (see the last console.log(arr) below):
Code:
let arr = [
{id: 1, nb: 1},
{id: 1, nb: 4},
{id: 2, nb: 1},
{id: 3, nb: 1},
{id: 1, nb: 2},
{id: 1, nb: 3},
{id: 2, nb: 7},
{id: 2, nb: 8},
];
// Original array
console.log(arr);
let tmp = {};
for(let i=0; i<arr.length; i++) {
if( !tmp[arr[i].id] ) {
tmp[arr[i].id] = arr[i];
} else {
if (tmp[arr[i].id].nb < arr[i].nb ) {
tmp[arr[i].id].nb = arr[i].nb;
}
}
}
var result = Object.values(tmp);
// This output the desired result
console.log(result);
// Why the original array changed ?
console.log(arr);
This will output:
> Array [Object { id: 1, nb: 1 }, Object { id: 1, nb: 4 }, Object { id: 2, nb: 1 }, Object { id: 3, nb: 1 }, Object { id: 1, nb: 2 }, Object { id: 1, nb: 3 }, Object { id: 2, nb: 7 }, Object { id: 2, nb: 8 }]
> Array [Object { id: 1, nb: 4 }, Object { id: 2, nb: 8 }, Object { id: 3, nb: 1 }]
> Array [Object { id: 1, nb: 4 }, Object { id: 1, nb: 4 }, Object { id: 2, nb: 8 }, Object { id: 3, nb: 1 }, Object { id: 1, nb: 2 }, Object { id: 1, nb: 3 }, Object { id: 2, nb: 7 }, Object { id: 2, nb: 8 }]
Why did the original array changed when there is no processing on it apart from looping?

The original array is updated at last as the objects in your tmp map and arr share the same object reference. So changes made in tmp will be reflected in arr. You can use Object.assign() to make them point to separate reference. Try the following:
let arr = [ {id: 1, nb: 1}, {id: 1, nb: 4}, {id: 2, nb: 1}, {id: 3, nb: 1}, {id: 1, nb: 2}, {id: 1, nb: 3}, {id: 2, nb: 7}, {id: 2, nb: 8}, ];
let tmp = {};
for(let i=0; i<arr.length; i++) {
if( !tmp[arr[i].id] ) {
tmp[arr[i].id] = Object.assign({},arr[i]);
} else {
if (tmp[arr[i].id].nb < arr[i].nb ) {
tmp[arr[i].id].nb = arr[i].nb;
}
}
}
var result = Object.values(tmp);
console.log(result)

Because objects in both the arrays are sharing the same reference.
You will need to update from
tmp[arr[i].id] = arr[i];
to
tmp[arr[i].id] = JSON.parse(JSON.stringify(arr[i]));
let arr = [
{id: 1, nb: 1},
{id: 1, nb: 4},
{id: 2, nb: 1},
{id: 3, nb: 1},
{id: 1, nb: 2},
{id: 1, nb: 3},
{id: 2, nb: 7},
{id: 2, nb: 8},
];
let tmp = {};
for(let i=0; i<arr.length; i++) {
if( !tmp[arr[i].id] ) {
tmp[arr[i].id] = JSON.parse(JSON.stringify(arr[i]));
} else {
if (tmp[arr[i].id].nb < arr[i].nb ) {
tmp[arr[i].id].nb = arr[i].nb;
}
}
}
var result = Object.values(tmp);
console.log(arr); // original array unchanged

Related

Comparing Two Arrays with Array.filter and Pushing to New Arrays Based on Common Values

I am trying to use array.filter() to compare two arrays and separate out values that the two arrays have in common, based on a certain property (id), vs. values they don't have in common. The common ids I want to push to a new array (recordsToUpdate). And I want to push the remaining elements from arr2 to a new array (recordsToInsert).
What I've tried is not working. How can I rework this to get the results I wanted? - (which in the example here should be one array of 1 common element {id: 3}, and another array of the remaining elements from arr2):
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
let recordsToUpdate = [];
let recordsToInsert = [];
recordsToUpdate = arr1.filter(e => (arr1.id === arr2.id));
recordsToInsert = ?
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
The desired result should be:
recordsToUpdate = [{id: 3}];
recordsToInsert = [{id: 4}, {id: 5}];
Try this, which uses Array.prototype.find to test for whether an object exists in arr2 with a given id:
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
const recordsToUpdate = arr1.filter(e => arr2.find(obj => obj.id === e.id) !== undefined);
const recordsToInsert = arr1.filter(e => arr2.find(obj => obj.id === e.id) === undefined);
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
Update to Robin post using some instead of find. It is just other way around.
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
const recordsToUpdate = arr1.filter(e => arr2.some(obj => obj.id === e.id));
const recordsToInsert = arr2.filter(e => !arr1.some(obj => obj.id === e.id));
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
I think this is what you are after... I added values to show the replacement. If you are doing any kind of state management, be careful as I am directly mutating the current array.
const arr1 = [
{ id: 1, v: "a" },
{ id: 2, v: "b" },
{ id: 3, v: "old" }
];
const arr2 = [
{ id: 3, v: "new" },
{ id: 4, v: "e" },
{ id: 5, v: "f" }
];
function updateRecords(currentArray, updatesArray) {
const currentIds = currentArray.map(item => item.id);
updatesArray.forEach(updateItem =>
currentIds.includes(updateItem.id)
? (currentArray[
currentIds.findIndex(id => id === updateItem.id)
] = updateItem)
: currentArray.push(updateItem)
);
return currentArray;
}
console.log(updateRecords(arr1, arr2))
This now gives the option below:
[
{
"id": 1,
"v": "a"
},
{
"id": 2,
"v": "b"
},
{
"id": 3,
"v": "new"
},
{
"id": 4,
"v": "e"
},
{
"id": 5,
"v": "f"
}
]
Putting it in a function is also something you likely want to do as you will likely use this multiple places in your code.

In Javascript, how do I tell array.filter() to duplicate element objects in the new array?

So simplified code.
var a = [
{ name: "first", num: 1 },
{ name: "first", num: 2 },
{ name: "first", num: 3 },
{ name: "first", num: 4 },
{ name: "first", num: 5 },
{ name: "first", num: 6 },
{ name: "first", num: 7 },
{ name: "first", num: 8 },
{ name: "first", num: 9 }
];
var b = a.filter(function(el) {
return el.num % 2 == 0;
});
console.log("a1", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b1", b); // [20, 40, 60, 80]
for (let i = 0; i < b.length; i++) {
b[i].num = b[i].num * 10;
}
console.log("a2", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b2", b); // [20, 40, 60, 80]
My new understanding is the array element contains a reference to an object, not the object. What are some ways to get those objects duplicated?
Filter, then build new objects from the filtered array and put the new things in a new array?
Use some method I'm not currently familiar with?
Redesign the code to stop using objects in an array?
Also, what's up with console.log() showing the variables have changed when placed before the for loop?
If you wish to duplicate the objects inside the array, you should use the map function.
var b = a.filter(val => val.num %2 === 0).map(val => Object.assign({}, val, { num: val.num * 10}));
The map function will return a new array with the value returned from the function. In this example, we are creating a new object Object.assign({}) and duplicating the existing object while changing the num field.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
If you want to clone objects you will need a clone function, I use this function
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
You can then clone the array with
let c = clone(b);
Which will be a new array where each object is a new clone.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
var b = a.filter(function(el){return el.num%2==0 });
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
let c = clone(b);
console.log(b[0] === c[0]);
Yes, elements of Array a are all pointers. so you need to use Object.assign (as many says)
and other solution with array reduce usage (see Adrian Brand comment)
var a = [ { name: 'first', num: 1 }
, { name: 'first', num: 2 }
, { name: 'first', num: 3 }
, { name: 'first', num: 4 }
, { name: 'first', num: 5 }
, { name: 'first', num: 6 }
, { name: 'first', num: 7 }
, { name: 'first', num: 8 }
, { name: 'first', num: 9 }
]
var b = a.filter(el=>!(el.num%2)).map(el=>Object.assign({},el))
// other solution with reduce
var c = a.reduce((acc,cur)=>{
if (!(cur.num%2) )acc.push(Object.assign({},cur))
return acc
}, [])
ConsoleArrayNamNum('var a -1-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -1-',b) // [2, 4, 6, 8]
ConsoleArrayNamNum('var c -1-',c) // [2, 4, 6, 8]
for(let elm of b)
{ elm.num *= 10 }
ConsoleArrayNamNum('var a -2-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -2-',b) // [20, 40, 60, 80]
function ConsoleArrayNamNum(title,arr) {
console.log(title)
for(let elm of arr)
{ console.log(`{ name: '${elm.name}', num: ${elm.num} }`) }
}
.as-console-wrapper { min-height: 100% !important; }
If you want a new array with the final values you can use reduce to do it all in one go, reduce starts with an accumulator of an empty array and each iteration if it meets the condition it adds a clone with the spread operator overriding the num time 10.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const evensTimes10 = array => array.reduce((results, item) => {
if (item.num % 2 === 0) {
results.push({ ...item, num: item.num * 10 });
}
return results;
}, []);
var b = evensTimes10(a);
console.log('a1',a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b1',b); // [20, 40, 60, 80]
A simple solution using some ES6 syntax:
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const b = a
.filter(el => {
if (el.num % 2 === 0) {
return {
...el
}
}
})
.map(newEl => newEl.num * 10);
console.log('a', a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b', b);
.filter() iterates the "a" array and returns only elements with
"num" property that reaches the condition. This is a cloned array.
return { ...el } returns a cloned object thanks to spread
operator.
.map() creates a new array and returns each "el.num" value *
10
Here some info about .map() .filter() and spread operator:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
I found this very interesting site that lists all Javascript functions with their descriptions and shows if is mutable or not, this helps a lot:
https://doesitmutate.xyz/

finding index of item in nested array

If I have an array (sub) which has its own objects each with arrays within them and I'm looking for a particular value such as id === 9, how would I find the index of the object AND the index within that object's s array?
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id : 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.findIndex(a => a.s.findIndex(z => z.id === 9)))
If you're sure there's only one matching element in all your sub arrays, here's a little trick with flatMap.
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id: 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.flatMap((a, i) => {
const j = a.s.findIndex(z => z.id === 9);
return j > -1 ? [i, j] : []
}));
This will return an array containing the index, i, in a.sub where a matching element is found followed by the index, j, in a.sub[i].s where the matching element was found.
Note flatMap is a relatively recent addition to the standard, so it may not work in older browsers. Be sure to use a polyfill or a transpiler like Babel, if this is a concern in your case.
Try this:
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id : 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
v = 9
id1 = a.sub.findIndex(e => e.s.findIndex(ee => ee.id === v)!= -1)
id2 = a.sub[id1].s.findIndex(e => e.id === v )
console.log(id1) //index of the object
console.log(id2) //index within that object's s array
Modified answer of p.s.w.g, less likely to give you an eslint error.
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id: 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.flatMap((a, i) => {
const j = a.s.findIndex(z => z['id'] === 9);
return j > -1 ? [i, j] : []
}));

Array remapping

We have an array of objects
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 4},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
Each have id and some order number.
For example, user changes order number at id 2 to 0. Array now will look like this:
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
Now we need to reindex order_assigned in order to fill gaps in order_assigned order.
i.e. array should look like this
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 4},
{ id: 5, order_assigned: 3}
];
Item with id 4 changes order assigned number from 5 to 4, cos there is no item with 4 order assigned number.
Numbering in order_assigned should go in order from 1 to the last established rank. 1, 2, 3, 4. If someone enters 1,2,5,7 to order_assigned it must be converted to 1,2,3,4.
In other words, if there is a gap in order number, all digits after it must be recalculated. No resorting of items order itself needed.
Totally stucked how to do this right way. Please, help!
I think you're asking us to order this based on order_assigned, then close the gaps between the order_assigned.
I'm first sorting the array, then looping through it. I'm then replacing the order_assigned value with the current index.
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
items.sort(function(a, b) {
return a.order_assigned - b.order_assigned;
});
var counter = 0;
for (var index in items) {
if (items[index].order_assigned === 0) {
continue;
} // leave orders that are 0 the same
items[index].order_assigned = ++counter;
}
items.sort(function(a, b) {
return a.id - b.id;
}); // sort again on the ID field
console.log(items);
function sortCloseGap(array) {
return array.slice() // copy
.sort(function(a, b) { return a.order_assigned - b.order_assigned }) // sort
.map(function(item, index) { // assign new order
item.order_assigned = index + 1;
return item;
});
}
I hope you don't mind the mutability.

JS: Put elements to array beginning on condition

I have an array something like this:
[
{id: 5, attr: 99},
{id: 7, attr: null},
{id: 2, attr: 8},
{id: 9, attr: 3},
{id: 4, attr: null}
]
What would be the most efficient to put all objects with attr === null at the beginning of the array, without changing order of other elements in the array? (e.g. I need ids to go in this order: 7, 4, 5, 2, 9 (the order of 7 and 4 don't matter, but the order of 5, 2, 9 must never change.)
Simple fiddle to test your code: http://jsfiddle.net/8GEPC/
Could used built in filter methods that require two loops or one while loop to find the elements and remove them.
var x = [
{id: 5, attr: 99},
{id: 7, attr: null},
{id: 2, attr: 8},
{id: 9, attr: 3},
{id: 4, attr: null}
];
var temp = [];
for(var i = x.length-1;i>=0;i--){
if (x[i].attr===null) { //if we have a null
temp.unshift(x.splice(i,1)[0]); //remove the element from org and add to temp
}
}
x = temp.concat(x); //add the original to the temp to maintain the order
var arr = [
{id: 5, attr: 99},
{id: 7, attr: null},
{id: 2, attr: 8},
{id: 9, attr: 3},
{id: 4, attr: null}
]
var nulls = arr.filter(function(a){return a.attr === null});
var notnulls = arr.filter(function(a){return a.attr !== null});
nulls.concat(notnulls);
// outputs [{"id":7,"attr":null},{"id":4,"attr":null},{"id":5,"attr":99},{"id":2,"attr":8},{"id":9,"attr":3}]
And here's a solution using reduce:
var sorted = arr.reduce(function(output, element) {
if (element.attr === null)
output.unshift(element);
else
output.push(element);
return output
}, []);
console.log(sorted);
Grab the objects where attr === null.
var withNull = arr.filter(function (el) { return el.attr === null });
Grab the objects where attr !== null.
var withVal = arr.filter(function (el) { return el.attr !== null });
Add the 'null array' to the front of the 'values array'.
withVal.unshift.apply(withVal, withNull);
Fiddle
function customSort(myArray) {
var result = [];
myArray.map(function(el, idx, arr) {
return myArray[idx].attr === null ? result.unshift(myArray[idx]) : result.push(myArray[idx]);
});
return result;
}
console.log(customSort([
{id: 5, attr: 99},
{id: 7, attr: null},
{id: 2, attr: 8},
{id: 9, attr: 3},
{id: 4, attr: null}
]));

Categories

Resources