JavaScript - Filter array with mutation - javascript

I want to filter a array by keeping the same array without creating a new one.
with Array.filter() :
getFiltersConfig() {
return this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
}
what is the best way to get the same result by filtering by value without returning a new array ?

For completeness, I thought it might make sense to show a mutated array variant.
Below is a snippet with a simple function mutationFilter, this will filter the array directly, notice in this function the loop goes in reverse, this is a technique for deleting items with a mutated array.
Also a couple of tests to show how Array.filter creates a new array, and mutationFilter does not.
Although in most cases creating a new array with Array.filter is normally what you want. One advantage of using a mutated array, is that you can pass the array by reference, without you would need to wrap the array inside another object. Another advantage of course is memory, if your array was huge, inline filtering would take less memory.
let arr = ['a','b','a'];
let ref = arr; //keep reference of original arr
function mutationFilter(arr, cb) {
for (let l = arr.length - 1; l >= 0; l -= 1) {
if (!cb(arr[l])) arr.splice(l, 1);
}
}
const cond = x => x !== 'a';
const filtered = arr.filter(cond);
mutationFilter(arr, cond);
console.log(`ref === array -> ${ref === arr}`);
console.log(arr);
console.log(`ref === filtered -> ${ref === filtered}`);
console.log(filtered);

I want to filter a array by keeping the same array without creating a new one.
what is the best way to get the same result by filtering by value without returning a new array ?
I have an answer for the second criterion, but violates the first. I suspect that you may want to "not create a new one" specifically because you only want to preserve the reference to the array, not because you don't want to create a new array, necessarily (e.g. for memory concerns).
What you could do is create a temp array of what you want
var temp = this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
Then set the length of the original array to 0 and push.apply() the values "in-place"
this.config.length = 0; //clears the array
this.config.push.apply(this.config, temp); //adds what you want to the array of the same reference

You could define you custom method like so:
if(!Array.prototype.filterThis){
Array.prototype.filterThis = function (callBack){
if(typeof callBack !== 'function')
throw new TypeError('Argument must of type <function>');
let t = [...this];
this.length = 0;
for(let e of t) if(callBack(e)) this.push(e);
return this;
}
}
let a = [1,2,3,4,5,5,1,5];
a.filterThis(x=>x!=5);
console.log(a);
Warning: Be very cautious in altering built in prototypes. I would even say unless your making a polyfill don't touch. The errors it can cause can be very subtle and very hard to debug.

Not sure why would you want to do mutation but if you really want to do it, maybe assign it back to itself?
let arr = ['a','b','a'];
arr = arr.filter(x => x !== 'a');
console.log(arr)

Related

React.JS Storing mapped API repsonse into a new array based on matching values

Using React, I have data from an API response. I mapped the data and am storing the visitID, which would be the main identifier, into a variable. What I would like to do is look for any matching visitID values and store them into a new array, the issue I'm having is each instance is being stored in it's own array, for example:
['7065682'] at Index 0
['7065682'] at Index 1
['7065682'] at Index 2
['7047674'] at Index 3
['7047674'] at Index 4
I would like to look through each iteration and check all, put matching values into it's own array so I can write some logic based off each unique value. I just don't understand how to look through the next iteration. Here's my code, and the function 'duplicateVisitID' that I've been trying, but it doesn't give me the results I'm looking for.
{
Object.keys(this.state.EncounterData).length !== 0 ?
Object.values(this.state.EncounterData).map((encounter, i) => {
const visitID = [encounter.resource.identifier[1].value];
console.log(visitID, i);
const duplicateVisitID = function (visitID) {
if (visitID[i] === visitID[i])
return [visitID.concat(visitID[i])]
}
I am not sure what do you want to do, but if I understood right you want new array with only strings that are unique, not repeating. If yes see the code below. This is not the best performing one because of iteration inside iteration, but it will work for you. You can optimize it later by applying some algorithms.
The newArr is equal to ['7065682', '7047674']
const EncounteredData = [['7065682'], ['7065682'], ['7065682'], ['7047674'], ['7047674']];
const newArr = [];
for(let i of EncounteredData) {
for(let j of EncounteredData) {
if((i[0] !== j[0]) && !newArr.includes(i[0])) newArr.push(i[0]);
}
}
console.log(newArr);
If I understand correctly, you want an array of unique values? If so then you can use the map function and add any unique values to an array if the value is not already in it:
const uniqueVals = [];
EncounteredData.map((visitID) => {
if (!uniqueVals.includes(visitID[0])) {
uniqueVals.push(visitID[0]);
}
});

How to duplicate elements in a js array without creating "dependent elements"?

I am trying to modify a single element from an array whose elements were previously duplicated n times. To perform the array duplication I just relied on a custom function duplicateElements(array, times)from this post (see #Bamieh answer). As shown in the exemple below, the problem is I can't modify a single element from the array without modifying other elements:
function duplicateElements(array, times) {
return array.reduce((res, current) => {
return res.concat(Array(times).fill(current));
}, []);
}
var myvar = duplicateElements([{ a: 1 }, { a: 2 }], 2);
myvar[0].a = 3;
console.log(myvar);
// (4) [{…}, {…}, {…}, {…}]
// 0: {a: 3}
// 1: {a: 3}
// 2: {a: 2}
// 3: {a: 2}
// length: 4
As you can see myvar[1].a was also modified although this wasn't intended. How can I avoid this issue?
The problem is that you're passing the reference to the original object in Array(times).fill(current) .
In this case the two copies of the first {a:2} are the same copy of the original (They reference to the same space in memory) so if you change one, the two of them will change as they reference the same object in memory.
You have to make a deepcloning function or maybe spread the object inside a new one. You can change your original function to work with objects and primitives like this:
function duplicateElements(elementsArray, times) {
//Make a new placeholder array
var newArray = [];
//Loop the array of elements you want to duplicate
for (let index = 0; index < elementsArray.length; index++) {
//Current element of the array of element
var currentElement = elementsArray[index];
//Current type of the element to check if it is an object or not
var currentType = typeof currentElement
//Loop over the times you want to copy the element
for (let index = 0; index < times; index++) {
//If the new element is not an object
if (currentType !== "object" && currentType){
//append the element
newArray.push(currentElement)
//if it is an Object
} else if (currentType === "object" && currentType){
//append an spreaded new Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
newArray.push({...currentElement})
}
}
}
return newArray;
}
This is not the optimal way to do this, but I think that maybe you're new to javascript and is better to learn the old way of looping before using more Array functionalities (as the answer from Jonas Wilms, that is also a good answer).
I would recommend javascript.info and eloquent javascript to learn more about the language
The main reason for this as specified in the Array.fill documentation is that when dealing with objects it will copy by reference:
When fill gets passed an object, it will copy the reference and fill
the array with references to that object.
With lodash (and _.cloneDeep) that is one line like this:
let dubFn = (arr, t=1) =>
_.concat(arr, _.flatMap(_.times(t, 0), x => _.cloneDeep(arr)))
let r1 = dubFn([{a:1},{b:3}]) // no parameter would mean just 1 dub
let r2 = dubFn([{a:1},{b:3},5,[1]], 2) // 2 dublicates
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Note that this now works with arrays/objects and primitives.
The idea is to use _.concat to return a new concatenated version of the input array with a combination of few functions which on the end return an array of cloned objects. We use _.times to return an array of in this case t elements and then for each of those elements we replace with a deep clone of the array. _.flatMap is needed to flatten the end result since we end up having array of arrays after the _.times call.
With ES6 you can do something like this:
let dubElements = (arr, t) =>
[...arr, ...new Array(t).fill().flatMap(x => arr.map(y => ({...y})))]
let r1 = dubElements([{a:1},{b:3}])
let r2 = dubElements([{a:1},{b:3}],2)
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
Where we concat arrays via the spread operator and we use new Array(t) to create the new duplicates array and make sure we fill it with undefined in this case after which we flatMap the results (which we map through the clone via the spread operator again.
Note that this works for your use case specifically. If you want to make it more generic you have to expand more in the last map function etc.
If you want to preserve the order of the elements you can do something like this:
let dubElements = (arr, t=1) => {
let _result = []
arr.forEach(x => {
for(let i=0; i<t+1; i++) {
_result.push({...x})
}
})
return _result
}
let result = dubElements([{a:1},{b:3}],2)
result[0].a = 3
console.log(result)
Replace
Array(times).fill(current)
which will add one reference to current multiple times to the array with:
Array.from({ length: times }, () => ({...current }))
which will shallow clone current. Note that the code will then only work with objects though, not with primitives.
I'd do:
const duplicateElements = (array, length) =>
array.flatMap(current => Array.from({ length }, () => ({ ...current }));

Modify an array without mutation

I am trying to solve a problem which states to remove(delete) the smallest number in an array without the order of the elements to the left of the smallest element getting changed . My code is -:
function removeSmallest(numbers){
var x = Math.min.apply(null,numbers);
var y = numbers.indexOf(x);
numbers.splice(y,1);
return numbers;
}
It is strictly given in the instructions not to mutate the original array/list. But I am getting an error stating that you have mutated original array/list .
How do I remove the error?
Listen Do not use SPLICE here. There is great known mistake rookies and expert do when they use splice and slice interchangeably without keeping the effects in mind.
SPLICE will mutate original array while SLICE will shallow copy the original array and return the portion of array upon given conditions.
Here Slice will create a new array
const slicedArray = numbers.slice()
const result = slicedArray.splice(y,1);
and You get the result without mutating original array.
first create a copy of the array using slice, then splice that
function removeSmallest(numbers){
var x = Math.min.apply(null,numbers);
var y = numbers.indexOf(x);
return numbers.slice().splice(y,1);
}
You can create a shallow copy of the array to avoid mutation.
function removeSmallest(numbers){
const newNumbers = [...numbers];
var x = Math.min.apply(null,newNumbers);
var y = newNumbers.indexOf(x);
newNumbers.splice(y,1);
return newNumbers;
}
array.slice() and [... array] will make a shallow copy of your array object.
"shallow" the word says itself.
in my opinion, for copying your array object the solution is:
var array_copy = copy(array);
// copy function
function copy(object) {
var output, value, key;
output = Array.isArray(object) ? [] : {};
for (key in object) {
value = object[key];
output[key] = (typeof value === "object") ? copy(value) : value;
}
return output;
}
Update
Alternative solution is:-
var arr_copy = JSON.parse(JSON.stringify(arr));
I'm not sure what the exact context of the problem is, but the goal might be to learn to write pure transformations of data, rather than to learn how to copy arrays. If this is the case, using splice after making a throwaway copy of the array might not cut it.
An approach that mutates neither the original array nor a copy of it might look like this: determine the index of the minimum element of an array, then return the concatenation of the two sublists to the right and left of that point:
const minIndex = arr =>
arr.reduce(
(p, c, i) => (p === undefined ? i : c < arr[p] ? i : p),
undefined
);
const removeMin = arr => {
const i = minIndex(arr);
return minIndex === undefined
? arr
: [...arr.slice(0, i), ...arr.slice(i + 1)];
};
console.log(removeMin([1, 5, 6, 0, 11]));
Let's focus on how to avoid mutating. (I hope when you say "remove an error" you don't mean "suppress the error message" or something like that)
There are many different methods on Array.prototype and most don't mutate the array but return a new Array as a result. say .map, .slice, .filter, .reduce
Telling the truth just a few mutate (like .splice)
So depending on what your additional requirements are you may find, say .filter useful
let newArray = oldArray.filter(el => el !== minimalElementValue);
or .map
let newArray = oldArray.map(el => el === minimalElementValue? undefined: el);
For sure, they are not equal but both don't mutate the original variable

Looping through an array with conditions

I'm having a tough time figuring out how to loop through an array and if certain items do exist within the array, i'd like to perform a .slice(0, 16) to kind of filter an already existing array (lets call that existing array "routes").
For example, a previous process will yield the following array:
points = ['=00ECY20WA200_RECV_P1SEL',
'=00ECY20WA200_RECV_P2SEL',
'=00RECV_C1A_EINCSCMPP1',
'=00RECV_C1A_EINCSCMPP2',
'=00BYPS_C1A_EINCSCMP',
'=00ECY20WA200_BYPS_SPSL1',
'=00ECC92AG184YB01',
'=00ECC92AG185YB01',
'=00ECC92AG186YB01',
'=00ECC92AG187YB01',
]
So if any of the above items exist in the "points" Array, which in this case they all do (but in some cases it could just be 1 of the 10 items existing there), I'm trying to perform routes.slice(0, 16) to the other already existing array.
I've tried lots of different ways (for loops with if statements) and at this point I'm not sure if its my syntax or what, but I'm back at square 0 and I don't even have a competent piece of code to show for. Any direction would be greatly appreciated.
You could use a hash table for checking and filtering.
var points = ['=00ECY20WA200_RECV_P1SEL', '=00ECY20WA200_RECV_P2SEL', '=00RECV_C1A_EINCSCMPP1', '=00RECV_C1A_EINCSCMPP2', '=00BYPS_C1A_EINCSCMP', '=00ECY20WA200_BYPS_SPSL1', '=00ECC92AG184YB01', '=00ECC92AG185YB01', '=00ECC92AG186YB01', '=00ECC92AG187YB01'],
hash = Object.create(null),
filtered = points.filter(function (a) {
if (!hash[a.slice(0, 16)]) {
hash[a.slice(0, 16)] = true;
return true;
}
});
console.log(filtered);
ES6 with Set
var points = ['=00ECY20WA200_RECV_P1SEL', '=00ECY20WA200_RECV_P2SEL', '=00RECV_C1A_EINCSCMPP1', '=00RECV_C1A_EINCSCMPP2', '=00BYPS_C1A_EINCSCMP', '=00ECY20WA200_BYPS_SPSL1', '=00ECC92AG184YB01', '=00ECC92AG185YB01', '=00ECC92AG186YB01', '=00ECC92AG187YB01'],
pSet = new Set,
filtered = points.filter(a => !pSet.has(a.slice(0, 16)) && pSet.add(a.slice(0, 16)));
console.log(filtered);
EDIT: So it seems like you want to remove an element from an array called routes for each element in the points array. This is how you could do this:
function removeBrokenRoutes(brokenPoints, routes){
for(let pt of brokenPoints){
let index = routes.indexOf(pt);
if(index !== -1) routes.splice(index,1);
}
return routes;
}
Keep in mind that the larger the arrays, the more time this is going to take to complete.
You could use the filter and indexOf methods in combination:
var arr = [/* all the data you're checking against */];
var points = [/* the data you're checking for */];
var filteredArr = arr.filter(function(x) {
// will return -1 if the point is not found
return points.indexOf(x) !== -1;
});
filteredArr will contain all the points that appear in both arrays. The filter function works by taking a function with one argument x, which represents each item in the array. if the function returns true, the item will be added to the new array (filteredArr), and if false the function will move on to the next item. indexOf will check if the item is found in the other array. Also it is important to note that you will need a more complex solution (such as a hashtable) if the data set is very, very large as this is not necessarily the most performant method. But it's a good place to start as it is easy to understand.

Get first element in array with index not starting from 0

I'm using a javascript library which returns arrays not starting from zero like starting from 26 or 1500, what i want to do is a method to get the first element in that array regardless of the index number starting with 0 or any other number.
Are they any method to do this in javascript ?
I suggest to use Array#some. You get the first nonsparse element and the index. The iteration stops immediately if you return true in the callback:
var a = [, , 22, 33],
value,
index;
a.some(function (v, i) {
value = v;
index = i;
return true;
});
console.log(index, value);
The information below is generally useful, but for the problem the OP listed, Nina's answer is by far a better solution.
Those are called sparse arrays and they're one of the few situations where you may want to use for-in on an array.
Remember that arrays are objects in JavaScript, and array entries are properties keyed by names (array indexes) that meet certain criteria. So we can use the features that let us discover the properties on an object to find the indexes on your sparse array.
for-in example:
for (var n in theArray) {
if (theArray.hasOwnProperty(n) && isArrayIndex(n)) {
// Use theArray[n]
}
}
This answer shows how you can determine that n is an array index as opposed to being some other property. A very technical definition would be
function isArrayIndex(n) {
return /^0$|^[1-9]\d*$/.test(n) &&
n <= 4294967294;
}
...but a definition that's good enough for most of us would be
function isArrayIndex(n) {
return !isNaN(parseInt(n, 10));
}
Similarly, you can use Object.keys; since it only looks at own enumerable properties, you don't need the hasOwnProperty check:
Object.keys(theArray).forEach(function(n) {
if (isArrayIndex(n)) {
// ...
}
});
Note that officially, neither of those is in any particular order, not even in ES2015 ("ES6"). So in theory, you could see the indexes out of numeric order. In the real world, I've never seen an even vaguely-modern JavaScript engine that returned array indexes out of order. They're not required to, but every one I've tried does.
So officially, you would need to get a full list and then find the minimum value in it:
var min = Object.keys(theArray).reduce(function(min, n) {
var i = parseInt(n, 10);
return isNaN(i) || (min !== undefined && min > i) ? min : i;
}, undefined);
That'll given you undefined if the array is empty, or the min index if it isn't. But if you want to make the assumption you'll get the keys in numeric order:
// Makes an assumption that may not be true
var min = +Object.keys(theArray).filter(isArrayIndex)[0];
If you're using a JavaScript engine that's entirely up-to-date, you can rely on the order returned by Object.getOwnPropertyNames, which is required to list the array indexes in order.
var min = +Object.getOwnPropertyNames(theArray).filter(isArrayIndex)[0];
It may be useful to use a filter function on the array to get back a normalised array.
var fullArray = array.filter(function(n){
return n != undefined;
});
fullArray[0]
The answers here may help you decide Remove empty elements from an array in Javascript
I guess one alternative to Array.prototype.some() is the Array.prototype.findIndex() method. These are much faster than filter alone and will keep your array and indices untouched.
var arr = new Array(1000),
fi = -1;
arr[777] = 1453; // now we have a nice sparse array
fi = arr.findIndex(f => f !== void 0); // void 0 is the perfect undefined
console.log(fi);
console.log(arr[fi]);
With this piece of code you can find first assigned value index and then get the value from your array:
var a = [, , 22, 33];
var value = a.find((v, i) => i in a);
console.log(value);
/* ---------------------------------------------- */
var i = 0
while (!(i in a) && i < a.length) i++; // If i === a.length then the array is emtpy
console.info(i, a[i]);
First implementation uses Array.prototype.find which makes less variable usage so this is cleaner but to find the index you should call indexOf over the array.
But the second one is a little bit old fashioned but give the chance of having index without extra efforts.
BTW Nina's seems better. (can make it shorter?)
const arr = [0,1,2]
// using destructuring to get the first element
let [first] = arr
// plus: using destructuring to get the last element
let [first] = [...arr].reverse()

Categories

Resources