1

I'm looking for an efficient JavaScript utility method that in O(n) will remove a set of items from an array in place. You can assume equality with the === operator will work correctly.

Here is an example signature (written in TypeScript for type clarity)

function deleteItemsFromArray<T>(array: T[], itemsToDelete: T[]) { ... }

My thought is to do this in two passes. The first pass gets the indexes that need to be removed. The second pass then compacts the array by copying backwards from the current index being removed through the next index being removed.

Does anyone have code like this already handy or a more efficient way to do this?

P.S. Please don't point out the filter function as that creates a copy of the array, it does not work in place.

Emma
  • 27,428
  • 11
  • 44
  • 69
MgSam
  • 12,139
  • 19
  • 64
  • 95
  • Filter, then delete the contents of the original and expand the copy into it? I dunno if that would be more efficient though. – John Montgomery Jun 05 '20 at 21:46
  • If I understand you correct you have O(2*n) that is equal to O(n), no? – vadimk7 Jun 05 '20 at 21:48
  • I don't want to copy the data at all. This needs to be completely in place. Memory is an issue. – MgSam Jun 05 '20 at 21:48
  • 1
    @v.kostenko Yes, O(2n) == O(n). I haven't coded the algorithm yet. Wanted to see if the wheel exists somewhere before I go re-invent it. – MgSam Jun 05 '20 at 21:49
  • Why not pass a set for `itemsToDelete`? Then it's trivial. Otherwise you just have to transform it to a set inside `new Set(itemsToDelete)` – VLAZ Jun 05 '20 at 21:49
  • Is the set of items small? If so, you can just make nested loops and remove the values from the array. – D. Pardal Jun 05 '20 at 21:55
  • is `itemsToDelete` an array of indexes that you want to remove? – Ehsan Nazeri Jun 05 '20 at 22:01

4 Answers4

10

Iterate over the array, copying elements that aren't in itemsToDelete to the next destination index in the array. When you delete an element, you don't increment this index.

Finally, reset the length of the array at the end to get rid of the remaining elements at the end.

function deleteItemsFromArray(array, itemsToDelete) {
  let destIndex = 0;
  array.forEach(e => {
    if (!itemsToDelete.includes(e)) {
      array[destIndex++] = e;
    }
  });
  array.length = destIndex;
}

const array = [1, 2, 3, 4, 5, 6, 7];
deleteItemsFromArray(array, [3, 5]);
console.log(array);
Barmar
  • 741,623
  • 53
  • 500
  • 612
0

It's very, very simple - transform itemsToDelete to a Set. This ensures O(1) lookups. Then walk through the array backwards and remove items with Array#splice. In total, that gives you a linear O(n+m) time complexity:

function deleteItemsFromArray<T>(array: T[], itemsToDelete: T[]) { 
  const setToDelete = new Set(itemsToDelete);

  for (let i = array.length - 1; i >= 0; i--) {
    const item = array[i];

    const shouldBeDeleted = setToDelete.has(item);

    if (shouldBeDeleted) {
      array.splice(i, 1);
    }
  }
}

You can save the conversion step if you just make the function accept a set to begin with and change the signature to:

function deleteItemsFromArray<T>(array: T[], itemsToDelete: Set<T>)

function deleteItemsFromArray(array, itemsToDelete) { 
  for (let i = array.length - 1; i >= 0; i--) {
    const item = array[i];

    const shouldBeDeleted = itemsToDelete.has(item);

    if (shouldBeDeleted) {
      array.splice(i, 1);
    }
  }
}

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
deleteItemsFromArray(
  arr, new Set([1, 3, 5, 7, 8, 9])
)

console.log(arr);
VLAZ
  • 26,331
  • 9
  • 49
  • 67
0
function deleteItems(array,items){
   let itemsToDelete=0;
   let indexToSwap = array.length-1;
   for(let i = array.length-1,currItem ; i>=0 ; i--){
       if(items.includes(array[i]){
           [array[i] , array[indexToSwap]] = [array[indexToSwap] , array[i]];
           --indexToSwap;
           ++itemsToDelete;
       }
   }

   array.splice(array.length-itemsToDelete,itemsToDelete);
}

This should work, I haven't tested it.
The idea is to swap the elements to delete to the end of the array. You can remove them at the end how I do in my code or you could use too the pop() function every time.

Nick
  • 1,439
  • 2
  • 15
  • 28
0

Using copyWithin method will be helpful here.

method 1 Traverse from last index to first index, when ever we find the item to remove just copy/move elements to left. Though we will be doing times the copy/move, But still have some unnecessary moving elements.

method 2 Traverse from first index to last index, when ever we find the item to remove, identify the group of elements to copy/move. This will avoid the unnecessary moving elements as in method 1.

function deleteItemsFromArray(array, delete_list) {
  for (let i = array.length - 1; i > -1; i--) {
    if (delete_list.includes(array[i])) {
      array.copyWithin(i, i + 1).pop();
    }
  }
}

// Alternate way, with minimal copy/move group of elements.
function deleteItemsFromArrayAlternate(array, delete_list) {
  let index = -1;
  let count = 0;
  for (let i = 0; i <= array.length; i++) {
    if (delete_list.includes(array[i]) || !(i in array)) {
      if (index > -1) {
        array.copyWithin(index - count + 1, index + 1, i);
      }
      count += 1;
      index = i;
    }
  }
  array.length = array.length - delete_list.length;
}

const array = [9, 12, 3, 4, 5, 6, 7];
deleteItemsFromArray(array, [9, 6, 7]);

console.log(array);
Siva K V
  • 10,561
  • 2
  • 16
  • 29