Remove and store an array item without creating garbage - javascript

I'm looking for a performant way to remove and store elements from an array. I am trying to make an object pool to reduce garbage collections calls.
Just as .pop() and .unshift() remove elements from an array and return the value of that element, I'd like to be able to remove an element at a specific index, while storing it's value in a variable, and while not creating unnecessary arrays/objects.
.splice() removes the element at a specific index just fine, and stores that value in an array. I can access that, but the function itself creates a new array, which will eventually trigger the garbage collector.
.slice() has the same issue, a new array is created.
Is there a way to pull out and store a specific indexed element without the creation of a new array?

This always removes one item at index, if you need to remove more than 1 consecutive items at a time, it would be
more efficient to implement it to take a howMany argument and remove them in a batch instead of calling
removeAt repeatedly.
function removeAt(array, index) {
// Assumes array and index are always valid values
// place validation code here if needed
var len = array.length;
// for example if index is not valid here, it will deoptimize the function
var ret = array[index];
for (var i = index + 1; i < len; ++i) {
array[i - 1] = array[i];
}
array.length = len - 1;
return ret;
}
Usage:
var a = [1,2,3,4,5]
var removed = removeAt(a, 2);
console.log(a);
// [1, 2, 4, 5]
console.log(removed);
// 3

Related

removing a duplicate number in a numbers array

I need a function to trim a specific number to only 1 repetition in a numbers array.
i can only use .pop, .push .length commands.
If I have array i.e [ 5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6] I need to trim the duplicates of the the digit 6 to show only one time, so the result would be -
[5,4,6,8,4,6,3,3,6,5,4,8,6].
using only one array.
I have tried to go thru the array with a for loop, and if i find a 6 that comes after another 6 i tried to move all the other elements one step back each time i find a duplicated 6 , array[i] = array [i+1]
I tried looping in a for inside a loop, no luck.
You can loop the array and re-assign values in place, by taking care about whether the current value and the next value are effectively the same and the next one is the targeted one.
Other than that, you need to also handle whether you're at the end of array.
Further explanations can directly be found in the snippet below.
As a final side note, the expected output should rather be:
[5,4,6,8,4,6,3,3,6,5,4,8,6]
instead of
[5,4,6,8,4,6,3,3,6,5,3,8,6]
since slightly before the last 8, in your input, you have a 4, not a 3.
function trimSpecificNumber(arr, target) {
// Define a tracker that will update the array in-place.
var _track = 0;
// Loop over all the elements in the array.
for (var i = 0; i < arr.length; i++) {
// acquire the current and the next value.
var _curr = arr[i], _next = arr[i+1];
// If there is no next value, assign the last element of the array.
if (!_next) arr[_track++] = _curr;
else {
// Otherwise, if the current element is not the same of the next one AND the next one actually isn't the searched needle, assign the current index to the current value (in place).
if (_next === _curr && _next === target) {
// Silence is golden, skip this one.
}
else {
arr[_track++] = _curr;
}
}
}
// Finally, assign the new length of the array, so that next elements will be truncated.
arr.length = _track;
}
var needle = [5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6];
trimSpecificNumber(needle, 6);
console.log(needle);
In your case iterate the array with a for loop, and if the current item is not
the item to dedupe, or is not identical to the previous, add the item to the current counter, and increment the counter. When the loop ends, change the length of the array to the length of the counter.
function removeSpecificDuplicate(arr, item) {
let counter = 0;
for(let i = 0; i < arr.length; i++) {
if(arr[i] !== item || arr[i] !== arr[i-1]) arr[counter++] = arr[i];
}
arr.length = counter;
}
const arr = [5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6]
removeSpecificDuplicate(arr, 6);
console.log(arr)

JavaScript - Filter array with mutation

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)

add item to array in loop by this array js

I have the following method:
var items = [1,2,3];
$.map(items, function (item) {
if (item === 1) {
items.push(4);
}
console.log(item);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
and I expect in console 1,2,3,4, but see 1,2,3. I mean I want to see one extra loop item.
Can I resolve it somehow? And if yes, how can I resolve it?
Iterator methods, like .map() or .forEach(), will prevent visiting elements added during iteration by using only the original length.
To avoid that, you'll want to use a standard loop, such as for..of (with the default array iterator checking length as it progresses):
var items = [1, 2, 3];
for (var item of items) {
if (item === 1) {
items.push(4);
}
console.log(item);
}
Though, other types of loops can be used to do the same.
Of course, beyond this current example, be careful that the loop doesn't become infinite from there always being new elements to iterate to next.
Yes certainly you can resolve it, but in your specific case, items is passed by value to your map function so that you won't accidentally alter the original variable. The purpose of map is not for what you are using, but for mapping by specific key for an object or associative array. You should fall back to for loop or some other method for getting your desired output.
var items = [1, 2, 3];
for (var i = 0; i < items.length; i++) {
const item = items[i];
if (item === 1) {
items.push(4);
}
console.log(item);
};
use forIn instead of forOf, because sometime forOf give an error (maybe forOf not supported older version of js )..
var items = [1,2,3];
var k;
for (k in items){
if(items[k] === 1){
items.push(4);
}
}
alert(items);

Removing an Index altogehter from a javascript array

If i declare this
var data = [];
data [300] = 1;
data [600] = 1;
data [783] = 1;
I have an array of length 784 but with only 3 defined items within it.
Since splice(300,1) would delete the item and the index but would also shift every consecutive position, how can i delete the object in the index 300 from the array without altering the order of the array so when i use
for(var x in data)
it can correctly iterate only 2 times, on the indexes 600 and 783?
i tried using data[300] = undefined but the index 300 was still iterated over.
You could use delete:
delete data[300];
This sets the value of the index to be undefined, but doesn't modify the element index itself.
See more about the delete operator here.
dsg's answer will certainly work if you're going to use an array. But, if your data is going to be as sparse as it is, I wonder if a plain Javascript object might be a better choice for such sparse data where you never want the index/key to change. Arrays are optimized for consecutive sequences of data starting with an index of 0 and for iterating over a sequence of values. And, they keep track of a .length property of the highest index used minus one.
But, since you aren't really doing any of that and given the way you are storing data, you aren't able to use any of the useful features of an array. So, you could do this instead with a plain Javascript object:
var data = {};
data [300] = 1;
data [600] = 1;
data [783] = 1;
delete data[300];
This would create an object with three properties and then would delete one of those properties.
You can then iterate over the properties on an object like this:
for (var prop in data) {
console.log(data[prop]);
}
A couple things to remember: 1) The property names are always strings so your numbers would show us as "600" in the prop variable in the above iteration. 2) Iterating with the for/in technique does not guarantee any order of the properties iterated. They could come in any order since properties on an object have no innate order.
You can delete that element from the array:
delete data[300];
The full example:
var data = [];
data [300] = 1;
data [600] = 1;
data [783] = 1;
delete data[300];
var result = "";
for (var x in data) {
result += "<div>" + x + "</div>";
}
document.getElementById("output").innerHTML = result;
<div id="output" />

Loop to remove an element in array with multiple occurrences

I want to remove an element in an array with multiple occurrences with a function.
var array=["hello","hello","world",1,"world"];
function removeItem(item){
for(i in array){
if(array[i]==item) array.splice(i,1);
}
}
removeItem("world");
//Return hello,hello,1
removeItem("hello");
//Return hello,world,1,world
This loop doesn't remove the element when it repeats twice in sequence, only removes one of them.
Why?
You have a built in function called filter that filters an array based on a predicate (a condition).
It doesn't alter the original array but returns a new filtered one.
var array=["hello","hello","world",1,"world"];
var filtered = array.filter(function(element) {
return element !== "hello";
}); // filtered contains no occurrences of hello
You can extract it to a function:
function without(array, what){
return array.filter(function(element){
return element !== what;
});
}
However, the original filter seems expressive enough.
Here is a link to its documentation
Your original function has a few issues:
It iterates the array using a for... in loop which has no guarantee on the iteration order. Also, don't use it to iterate through arrays - prefer a normal for... loop or a .forEach
You're iterating an array with an off-by-one error so you're skipping on the next item since you're both removing the element and progressing the array.
That is because the for-loop goes to the next item after the occurrence is deleted, thereby skipping the item directly after that one.
For example, lets assume item1 needs to be deleted in this array (note that <- is the index of the loop):
item1 (<-), item2, item3
after deleting:
item2 (<-), item3
and after index is updated (as the loop was finished)
item2, item3 (<-)
So you can see item2 is skipped and thus not checked!
Therefore you'd need to compensate for this by manually reducing the index by 1, as shown here:
function removeItem(item){
for(var i = 0; i < array.length; i++){
if(array[i]==item) {
array.splice(i,1);
i--; // Prevent skipping an item
}
}
}
Instead of using this for-loop, you can use more 'modern' methods to filter out unwanted items as shown in the other answer by Benjamin.
None of these answers are very optimal. The accepted answer with the filter will result in a new instance of an array. The answer with the second most votes, the for loop that takes a step back on every splice, is unnecessarily complex.
If you want to do the for loop loop approach, just count backward down to 0.
for (var i = array.length - 0; i >= 0; i--) {
if (array[i] === item) {
array.splice(i, 1);
}
}
However, I've used a surprisingly fast method with a while loop and indexOf:
var itemIndex = 0;
while ((itemIndex = valuesArray.indexOf(findItem, itemIndex)) > -1) {
valuesArray.splice(itemIndex, 1);
}
What makes this method not repetitive is that after the any removal, the next search will start at the index of the next element after the removed item. That's because you can pass a starting index into indexOf as the second parameter.
In a jsPerf test case comparing the two above methods and the accepted filter method, the indexOf routinely finished first on Firefox and Chrome, and was second on IE. The filter method was always slower by a wide margin.
Conclusion: Either reverse for loop are a while with indexOf are currently the best methods I can find to remove multiple instances of the same element from an array. Using filter creates a new array and is slower so I would avoid that.
You can use loadash or underscore js in this case
if arr is an array you can remove duplicates by:
var arr = [2,3,4,4,5,5];
arr = _.uniq(arr);
Try to run your code "manually" -
The "hello" are following each other. you remove the first, your array shrinks in one item, and now the index you have follow the next item.
removing "hello""
Start Loop. i=0, array=["hello","hello","world",1,"world"] i is pointing to "hello"
remove first item, i=0 array=["hello","world",1,"world"]
next loop, i=1, array=["hello","world",1,"world"]. second "hello" will not be removed.
Lets look at "world" =
i=2, is pointing to "world" (remove). on next loop the array is:
["hello","hello",1,"world"] and i=3. here went the second "world".
what do you wish to happen? do you want to remove all instances of the item? or only the first one? for first case, the remove should be in
while (array[i] == item) array.splice(i,1);
for second case - return as soon as you had removed item.
Create a set given an array, the original array is unmodified
Demo on Fiddle
var array=["hello","hello","world",1,"world"];
function removeDups(items) {
var i,
setObj = {},
setArray = [];
for (i = 0; i < items.length; i += 1) {
if (!setObj.hasOwnProperty(items[i])) {
setArray.push(items[i]);
setObj[items[i]] = true;
}
}
return setArray;
}
console.log(removeDups(array)); // ["hello", "world", 1]
I must say that my approach does not make use of splice feature and you need another array for this solution as well.
First of all, I guess your way of looping an array is not the right. You are using for in loops which are for objects, not arrays. You'd better use $.each in case you are using jQuery or Array.prototype.forEach if you are using vanila Javascript.
Second, why not creating a new empty array, looping through it and adding only the unique elements to the new array, like this:
FIRST APPROACH (jQuery):
var newArray = [];
$.each(array, function(i, element) {
if ($.inArray(element, newArray) === -1) {
newArray.push(region);
}
});
SECOND APPROACH (Vanila Javascript):
var newArray = [];
array.forEach(function(i, element) {
if (newArray.indexOf(element) === -1) {
newArray.push(region);
}
});
I needed a slight variation of this, the ability to remove 'n' occurrences of an item from an array, so I modified #Veger's answer as:
function removeArrayItemNTimes(arr,toRemove,times){
times = times || 10;
for(var i = 0; i < arr.length; i++){
if(arr[i]==toRemove) {
arr.splice(i,1);
i--; // Prevent skipping an item
times--;
if (times<=0) break;
}
}
return arr;
}
An alternate approach would be to sort the array and then playing around with the indexes of the values.
function(arr) {
var sortedArray = arr.sort();
//In case of numbers, you can use arr.sort(function(a,b) {return a - b;})
for (var i = 0; sortedArray.length; i++) {
if (sortedArray.indexOf(sortedArray[i]) === sortedArray.lastIndexOf(sortedArray[i]))
continue;
else
sortedArray.splice(sortedArray.indexOf(sortedArray[i]), (sortedArray.lastIndexOf(sortedArray[i]) - sortedArray.indexOf(sortedArray[i])));
}
}
You can use the following piece of code to remove multiple occurrences of value val in array arr.
while(arr.indexOf(val)!=-1){
arr.splice(arr.indexOf(val), 1);
}
I thinks this code much simpler to understand and no need to pass manually each element that what we want to remove
ES6 syntax makes our life so simpler, try it out
const removeOccurences = (array)=>{
const newArray= array.filter((e, i ,ar) => !(array.filter((e, i ,ar)=> i !== ar.indexOf(e)).includes(e)))
console.log(newArray) // output [1]
}
removeOccurences(["hello","hello","world",1,"world"])

Categories

Resources