Remove last added value from array without mutating it - javascript

dontMutateMeArray=[1,2,3,3,3,4,5];
toBeRemoved=3;
newArray=dontMutateMeArray.something(toBeRemoved); // [1,2,3,3,4,5]
iDontWantArray=dontMutateMeArray.filter(value=>value===toBeRemoved); // [1,2,4,5]
I indeed need it for array of objects too. And I specifically need to remove the last added object (ie. the one with higher index in the array). Something like:
dontMutateMeArray=[{id:1},{id:2},{id:3,sth:1},{id:3,sth:42},{id:3,sth:5},{id:4},{id:5}];
toBeRemoved=3;
newArray=dontMutateMeArray.something(toBeRemoved); // [{id:1},{id:2},{id:3,sth:1},{id:3,sth:42},{id:4},{id:5}]
iDontWantArray=dontMutateMeArray.filter(obj=>obj.id===toBeRemoved); // [{id:1},{id:2},{id:4},{id:5}]
iDontWantArray2=dontMutateMeArray.blahBlah(toBeRemoved); // [{id:1},{id:2},{id:3,sth:1},{id:3,sth:5},{id:4},{id:5}]

You could iterate from right and check with a closure.
var dontMutateMeArray = [{ id: 1 }, { id: 2 }, { id: 3, sth: 1 }, { id: 3, sth: 42 }, { id: 3, sth: 5 }, { id: 4 }, { id: 5 }],
toBeRemoved = 3,
newArray = dontMutateMeArray.reduceRight((found => (r, a) => (!found && a.id === toBeRemoved ? found = true : r.unshift(a), r))(false), []);
console.log(newArray);

Related

Loop through two arrays and check if key value in items of the first array are present in the second [JavaScript] [duplicate]

This question already has answers here:
How to get the difference between two arrays in JavaScript?
(84 answers)
Closed last month.
This post was edited and submitted for review last month and failed to reopen the post:
Original close reason(s) were not resolved
I am trying to loop a array of objects and compare it with another array and check of the key value is present in the array which is to be compared. Here I am not going to check for the array length too.
Example
Array1 is the array which needs to be compared to array 2
[{name:'Linus',id:1},{name:'Anthony',id:2},{name:'Carl',id:3}]
Array 2
[{name:'Linus',id:1},{name:'Anthony',id:2},{name:'Beth',id:3},{name:'Kyle',id:4}]
I am trying to validate if all the id values in array 1 are present in array 2 and if not present then I expect a boolean value and get the best solution in terms of performance.
array1.every(item1 => array2.some(item2 => item1.id === item2.id))
Pretty easy using the .findIndex() in a loop and filter depending upon your needs.
const array1 = [{
name: 'Linus',
id: 1
}, {
name: 'Anthony',
id: 2
}, {
name: 'Carl',
id: 3
}];
const array2 = [{
name: 'Linus',
id: 1
}, {
name: 'Anthony',
id: 2
}, {
name: 'Beth',
id: 3
}, {
name: 'Kyle',
id: 4
}];
array1.forEach((a1) => {
const idx = array2.findIndex((x) => {
return x.id === a1.id
});
console.log(idx);
});
// idx set to -1 if it is not found
array2.forEach((a2) => {
const idx = array1.findIndex((x) => {
return x.id === a2.id
});
console.log(idx);
});
let matches = array2.filter((a) => {
return array1.findIndex((x) => {
return x.id === a.id
}) > -1;
});
console.log(matches);
let nomatches = array2.filter((a) => {
return array1.findIndex((x) => {
return x.id === a.id
}) == -1;
});
console.log(nomatches);
const arr1 = [{
name: 'Linus',
id: 1
}, {
name: 'Anthony',
id: 2
}, {
name: 'Carl',
id: 3
}];
const arr2 = [{
name: 'Linus',
id: 1
}, {
name: 'Anthony',
id: 2
}, {
name: 'Beth',
id: 3
}, {
name: 'Kyle',
id: 4
}]
let compareTwoArrayOfObjects = (
first_array_of_objects,
second_array_of_objects
) => {
return (
first_array_of_objects.length === second_array_of_objects.length &&
first_array_of_objects.every((element_1) =>
second_array_of_objects.some(
(element_2) =>
element_1.id === element_2.id
)
)
);
};
console.log(compareTwoArrayOfObjects(arr1, arr2));
You can do it with this method to compare each object's element and check their id value

create element at index position if index does not exist in array

I have an array objects that hold an id and a name
const stages = [{
id: 1,
name: ''
}, {
id: 2,
name: ''
}, {
id: 3,
name: ''
}, {
id: 4,
name: ''
}, {
id: 5,
name: ''
}, {
id: 6,
name: ''
}, {
id: 7,
name: ''
}, {
id: 8,
name: ''
}];
Further I have an array that holds numbers.
const indexPositions = [0, 1, 2, 2, 2, 3, 2, 0];
I want to create a third array that holds arrays. Each number in distances represents the index of the current array within the array.
If the current array does not exist yet I want to create it first. Obviously I have to create new arrays until I get to this index position.
Example:
My array is empty at start. The first index position is 0 so I have to create a new array for this. The next index position is 3 so I have to create more arrays until I have 4 arrays.
All I want to do is to push the stage to its correct level index position. The result of this example would be
const levels = [
[stage1, stage8],
[stage2],
[stage3, stage4, stage5, stage7],
[stage6]
];
Currently my code looks this
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length;
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
I get this error Uncaught TypeError: Cannot read property 'push' of undefined and this happens because the indexPosition is out of bounds. If the positionDifference is 0 no array gets created but in the beginning the array is empty.
I tried setting levels.length to -1 if it is 0 but I still get the error if the difference is 1, I create one array at position 0 and want to access position 1.
How can I create an empty array if it does not exist?
While I do not fully understand what you want to do, checking existence of an array element is simple, one way of doing that is coercing it to boolean:
const thing=[];
function addElem(where,what){
if(!thing[where]) // <- here
thing[where]=[];
thing[where].push(what);
}
addElem(2,1);
addElem(2,2);
addElem(2,3);
addElem(5,1);
console.log(thing);
(The indices are deliberately non-continuous, because that does not matter: JavaScript arrays are sparse)
You could use a single loop and add an array for the index if not exists. Then push the wanted value.
var stages = [{ id: 1, name: '' }, { id: 2, name: '' }, { id: 3, name: '' }, { id: 4, name: '' }, { id: 5, name: '' }, { id: 6, name: '' }, { id: 7, name: '' }, { id: 8, name: '' }],
indexPositions = [0, 1, 2, 2, 2, 3, 2, 0],
result = stages.reduce((r, o, i) => {
var index = indexPositions[i];
r[index] = r[index] || []; // take default value for falsy value
r[index].push('stage' + o.id); // instead of string take object
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You actually were very close! You have a very small issue in your code.
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length + 1; //YOU DID NOT ADD 1 HERE
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
When you were calculating the positionDifference, you did not add 1 causing the problem when indexPosition equaled 0 and the for loop did not run and no new arrays were pushed. Just adding one fixed the problem :-)

Compare 2 arrays and assign matching value [duplicate]

This question already has answers here:
Merge property from an array of objects into another based on property value lodash
(5 answers)
Closed 4 years ago.
I have 2 array of objects
The first one called data:
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
]
and the second called subs:
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
]
In which I want to compare that if they have the same ID, the subs array will pass its name value to it and if it does not match that it puts a '-' in the data array, try this way:
data.forEach((d)=>{
subs.forEach((s)=>{
if(d.id === s.id){
d.subname = s.name;
}
else {
d.subname = '-';
}
});
});
But always assign the values with '-' as if it does not match any. What part am I doing wrong? Is there any other simpler way to do this? I would greatly appreciate your help.
The size of the subs array may vary.
It looks like you are not exiting the inner loop when a successful match is found.
In the first example where you are looking for a match for Piero, in your first iteration 1===1 and d.subname is correctly set to 'Temprano'. However, you then continue to compare the values- 1 !== 4 so Temprano is overwritten with '-', and 1 !== 7 so it is overwritten again.
An alternate approach:
data.forEach(d => {
const match = subs.find(s => s.id === d.id);
d.subname = match ? match.name : '-';});
I'd also recommend adding a case where you're not expecting to find a match, so you can see that it works in both cases!
https://codepen.io/anon/pen/MGGBLP?editors=0010
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
];
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
];
// by caching one of the arrays in an object, it reduces the run time to linear.
const obj = subs.reduce((acc, item) => {
acc[item.id] = item;
return acc;
})
data.forEach(d => {
if (d.id in obj) {
d.subname = obj[d.id].name;
} else {
d.subname = '-';
}
});
console.log(data);
You just need two lines for this:
var findIds = id => subs.find(findId => findId.id === id);
data.forEach(findId => Object.assign(findId, findIds(findId.id)));
Your data array object should now include the name property from it's respective id sharing object in subs array.
jsFiddle: https://jsfiddle.net/AndrewL64/9k1d3oj2/1/

How to remove any objects that appear more than once in an array of objects?

If I have an array like:
[
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
]
And I want to return an array that contains any objects that appear only once. So for this example, the desired output would be:
[
{
id: 1,
title: 'foo'
},
{
id: 4,
title: 'bantz'
}
]
I have tried a few different approaches that I have found to solve this using reduce() and indexOf(), like this solution, but they do not work with objects for some reason.
Any assistance would be greatly appreciated.
You could use a Map to avoid having to look through the array again and again, which would lead to inefficient O(n²) time-complexity. This is O(n):
function getUniquesOnly(data) {
return Array.from(
data.reduce( (acc, o) => acc.set(o.id, acc.has(o.id) ? 0 : o), new Map),
(([k,v]) => v)
).filter( x => x );
}
var data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
console.log(getUniquesOnly(data));
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a, index) =>
result.concat(arr.some(b => a !== b && isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
In this case, we loop through each element to reduce(), and before we add it, we see if another version of it exists in the array before adding it. We have to make sure that we are also not being equal without ourselves (otherwise we'd get an empty array).
isEqual() is a separate function to make it easy to customize what "equal" means.
As written, each element in data is unique, they're all separate objects. data[0] === data[4] is false, even though they have the same data. You must compare the data inside to determine if they're duplicates or not. As Paulpro mentioned earlier, {} === {} is also false, because they're two different objects, even though their values are the same.
console.log({} === {});
console.log({ a: 1 } === { a: 1 });
In the example version of isEqual(), I considered them equal if they had the same id.
Answer to previous version of the question
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a) =>
result.concat(result.some(b => isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
I split isEqual() to it's own function so you could easily define what "equal" means. As someone pointed out, technically all of those are unique, even if the data is different. In my example, I defined equal ids to mean equal.
I then use reduce to go through each and build an object. Before I add it to the array (via concat()), I loop through all of them with some() and go until either I find one that is equal (which I wouldn't include) or none are equal and I add it.
A straightforward implementation would look something like this:
Create an empty set (in this case an array) to contain unique values by whatever metric you define (I.E. deep comparison or comparing by a unique value like the "id")
Loop over the list of values
Whenever you find a value that is not contained within the set of unique values, add it
That is essentially how the solution you posted works, except that all of the values in your array are -- in JavaScript's eyes -- unique. Because of this you need to define your own way to compare values.
The .reduce method can be used like so:
function areEqual(a, b) { /* define how you want the objects compared here */ }
function contains(a, lst) {
return lst.reduce(function (acc, x) {
return acc || areEqual(a, x);
}, false);
}
function getUnique(lst) {
return lst.reduce(function (acc, x) {
if(!contains(x, acc))
{
acc.push(x);
}
return acc;
}, []);
}
You may want to look at how JavaScript object comparison works. For deep comparison specifically (which it sounds like you want) I would look at existing answers.

How to combine _.map and _.filter in a more efficient way?

I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources