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.
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);
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.
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);
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);
Related
I'm taking in an array and need to return the index value where the numbers start to increase or decrease. The numbers in the array either increase then decrease [1, 3, 6, 4, 3](output = 2) decrease then increase [6, 4, 10, 12, 19](output = 1) or the same sequence [1, 3, 5, 7, 9](output = -1). For now I'm just focused on returning the index of an array that increase then decrease, then I think I can figure the other conditions out.
function ArrayChallenge(arr){
for(let i = 0; i < arr.length; i++){
if(arr[i]>arr[i+1]){
console.log(arr.indexOf(arr[i]>arr[i+1]))
}
}
}
console.log(ArrayChallenge([1, 2, 4, 5, 7, 3]))
To me the code says, take in the argument for the parameter(arr), then the for loop will go through each index and compare i with i+1, if i is greater than i+1 that means the sequence is decreasing. If that's the case I'd like to console log the index where that's actually happening. For my example code above the output should be 5 because that's the index where the numbers start decreasing, but I'm getting -1 which means the element cannot be found. If someone knows where I'm going wrong and can point me in the right direction that would be great, I'm pretty sure I'm using the .indexOf method incorrectly, but I don't know why.
"arr[i]>arr[i+1]" returns a bool "true". Your array does not contain any bool values so there is no matching index for it.
Therefore it returns -1 because it can not find any matching element.
EDIT:
If you want to print out the "moment" when your values are decreasing you can try something like this:
console.log('Decreasing from index: ' + arr.indexOf(arr[i]) + ' to ' + arr.indexOf(arr[i + 1]))
Use return arr[i] instead of console.log(arr.indexOf(arr[i]>arr[i+1]))
function ArrayChallenge(arr){
for(let i = 0; i < arr.length; i++){
if(arr[i]>arr[i+1]){
return arr[i]
}
}
}
console.log(ArrayChallenge([1, 2, 4, 5, 7, 3]))
output: 7
You can use ES6 Classes concept to achieve the requirement you have.
Working Demo :
// Defining class using es6
class arrayChallenge {
constructor(input_array) {
this.input_array = input_array;
}
getIncreaseThenDecrease() {
for(let i = 0; i < this.input_array.length; i++) {
if(this.input_array[i]>this.input_array[i+1]) {
return this.input_array.indexOf(this.input_array[i])
}
}
}
getdecreaseThenIncrease() {
for(let i = 0; i < this.input_array.length; i++) {
if(this.input_array[i] < this.input_array[i+1]) {
return this.input_array.indexOf(this.input_array[i])
}
}
}
}
// Making object with the help of the constructor
let arrayChallengeObject = new arrayChallenge([1, 2, 4, 5, 7, 3]);
console.log(arrayChallengeObject.getIncreaseThenDecrease());
console.log(arrayChallengeObject.getdecreaseThenIncrease());
The task is:
Given an array of digitals numbers, return a new array of length number containing the last even numbers from the original array (in the same order).
Codewars compiler shows the "Execution Timed Out (12000 ms)" error, though the code is working as intended.
Please help to optimize my code, because I can't figure it out myself
My code:
function evenNumbers(array, number) {
for (let i=0; i < array.length; i++) {
if (array[i] % 2 != 0) {
array.splice(i, 1);
i -= 1;
}
}
array.splice(0, array.length - number)
return array;
}
You're iterating over the whole array, but you only need to iterate over number elements. For example, given a number of 5, you only need to iterate until you find 5 values fulfilling the condition - you don't want to iterate over all of a 10,000-length array if you don't have to. (Codewars tests often have such huge object structures.)
It also says to return a new array, not modify the existing array.
const evenNumbers = (array, number) => {
const newArr = [];
for (let i = array.length - 1; i >= 0 && newArr.length <= number; i--) {
if (array[i] % 2 === 0) newArr.unshift(array[i]);
}
return newArr;
};
console.log(evenNumbers(
[1, 2, 3, 4, 5, 6, 7, 8],
3
));
Good day, I have been trying to solve JS problem where I have an array a = [1, 1, 1, 2, 1, 3, 4]. I have to loop through an array and remove every three elements ie (1,1,1) do some logic, then the next three (1,1,2), and so on.
I have used for loop and splice
a = [1, 1, 1, 2, 1, 3, 4]
tuple = ()
for(j=0; j < a.length; j++){
tuple = a.splice(j, 3)
}
However, loop would not go beyond first round of (1,1,1).
What would be the correct way to loop through this array and remove sets of every three elements.
thank you.
Splice return removed elements from base array in given range. You probable want to use slice which only copy them and doesn't change primary array. You can also check this solution.
let a = [1, 1, 1, 2, 1, 3, 4];
for (j = 0; j < a.length; j++) {
if ( j < a.length - 2 ) {
const touple = [ a[j], a[j+1], a[j+2] ]
console.log( touple )
}
}
splice changes the original array and that must be causing your issue.
So, you can use slice which returns a shallow copy of a portion of the original array (without modifying the original array):
const a = [1, 1, 1, 2, 1, 3, 4]
const doLogic = (arr) => {
console.log(JSON.stringify(arr)) // JSON.stringify is only for one-liner print. You can ignore it.
}
for (let i = 0; i < a.length - 2; i++) {
const picked = a.slice(i, i + 3)
doLogic(picked)
}
Or this, if you want to pick the full array when length is less than 3:
if (a.length < 3) {
doLogic(a)
} else {
for (let i = 0; i < a.length - 2; i++) {
const picked = a.slice(i, i + 3)
doLogic(picked)
}
}
I need to create a function that take as parameter an array and a target. It should return an array of arrays where the sum of these numbers equals to the target
sumPairs(array, target) {
}
For example:
sumPairs([1, 2, 3, 4, 5], 7) // output : [[2, 5], [3, 4]]
I know I have to use map(), and probably reduce(), set(), or filter() maybe (I read their documentation in MDN but still cant find out). I tried some ways but I can't get it.
If you guys could help me to find out how to dynamically create arrays and push them into a new array..
I read there some solutions (Split array into arrays of matching values) but I hate to just use created functions without knowing what they really do or how they work.
Some very basic code for achieving it, Just run all over combinations and conditionally add the items you want.
function sumPairs(array, target) {
var res = [];
for(var i = 0; i < array.length; i++){
for(var j = 0; j < array.length; j++){
if(i!=j && array[i]+array[j]==target &&
res.filter((x)=> x[0] == array[j] && x[1] == array[i]).length == 0 )
res.push([array[i], array[j]]);
}
}
return res;
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
Option 2 - see this answer for more options (like using reduce)
function sumPairs(array, target) {
return array.flatMap(
(v, i) => array.slice(i+1).filter(w => (v!=w && v+w==target)).map(w=> [w,v])
);
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
"The exercise says that it sould be arrays of pairs that sum the
target value so I think only 2 items"
If you need a pair that matches a sum and you pick any number from the list, you are left with
the following equation to solve num + x = sum where we want to find x. E.g. if you picked 7 and the target sum is 10 then you know you are looking for a 3.
Therefore, we can first construct a counting map of the numbers available in our list linear (O(n)) time and then search for matches in linear time as well rather than brute forcing with a quadratic algorithm.
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
return nums.reduce((pairs, num) => {
countByNum[num]--;
const target = sum - num;
if (countByNum[target] > 0) {
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[num]++;
}
return pairs;
}, []);
}
function countGroupByNum(nums) {
return nums.reduce((acc, n) => (acc[n] = (acc[n] || 0) + 1, acc), {});
}
Here's another implementation with more standard paradigms (e.g. no reduce):
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
const pairs = [];
for (const num of nums) {
const target = sum - num; //Calculate the target to make the sum
countByNum[num]--; //Make sure we dont pick the same num instance
if (countByNum[target] > 0) { //If we found the target
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[target]++; //Didin't find a match, return the deducted num
}
}
return pairs;
}
function countGroupByNum(nums) {
const countByNum = {};
for (const num of nums) {
countByNum[num] = (countByNum[num] || 0) + 1;
}
return countByNum;
}
You can also sort your array and find all the pairs with given sum by using two pointer method. Place the first pointer to the start of the array and the second pointer to the end.
if the sum of the values at the two places is :
More than target: Decrement your second pointer by 1
Less than target: Increment your first pointer by 1
Equal to target: This is one possible answer, push them to your answer array and increment your first pointer by 1 and decrement your second pointer by 1.
This is more performant solution with complexity O(n*log(n))
DISCLAIMER
I am well aware of the duplicate questions, however this one is asking to remove duplicates without making a new array and wants us to mutate the original array.
INSTRUCTIONS
Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length.
Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.
EXAMPLE
Given nums = [1,1,2],
Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively.
It doesn't matter what you leave beyond the returned length.
ATTEMPT
const removeDuplicates = function(nums) {
for(let i of nums){
if(nums[i] === nums[i]){
nums.splice(i, 1)
}
}
return nums.length;
};
console.log(removeDuplicates([1, 1, 2]));
console.log(removeDuplicates([1, 2]));
// [1, 1, 2] => [1, 2] (Correct)
// [1, 2] => [1] (Incorrect - should be [1, 2])
Am I mutating the array correctly with splice and what do I need to do to correct the 2nd argument?
Also, in leetcode, when I run the first argument, it says it's correct and returns the array of the leftover elements, but the instructions were asking for the length of the new array. Not sure if I'm missing something but why is it not returning the length?
https://imgur.com/5cuhFYf
Here you are:
const removeDuplicates = function(nums) {
for(let i = 0; i < nums.length;){
if(nums[i] === nums[++i]){
nums.splice(i, 1)
}
}
return nums.length;
};
console.log(removeDuplicates([1, 1, 2]));
console.log(removeDuplicates([1, 2]));
let nums = [1,1,2];
nums = [...new Set(nums)].length;
console.log(nums);
nums = [1,1,2];
nums = nums.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
console.log(nums)
For each element of the array you need to iterate through all remaining elements of that array, to check for all duplicates. Not sure if this is more performant then making a copy.
const removeDuplicates = function (nums) {
let i = 0;
while (i < nums.length) {
let j = i + 1;
while (j < nums.length) {
if (nums[i] === nums[j]) {
nums.splice(j, 1);
}
else {
j++;
}
}
i++;
}
return nums.length;
};
console.log(removeDuplicates([1, 1, 2]));
console.log(removeDuplicates([1, 2]));
console.log(removeDuplicates([1, 2, 1, 3, 4, 3, 2, 1]));
// [1, 1, 2] => [1, 2] (Correct)
// [1, 2] => [1] (Incorrect - should be [1, 2])
// [1, 2, 1, 3, 4, 3, 2, 1] => [1, 2, 3, 4]
The hint is in the line: It doesn't matter what you leave beyond the returned length.
Whoever is asking you this wants you to move through the array keeping track of 2 pointers: 1) The end of the output array and 2) the current index in the input array.
If you do this, and copy the input to the output pointer only when they're different, you will end up with the correct output, the correct length (from the output pointer) and a little bit of garbage at the end of the array.
const unique = (arr) => {
let output = 0;
for (let input = 0; input < arr.length; input++) {
if (arr[output] !== arr[input]) {
output++;
arr[output] = arr[input];
}
}
return output + 1;
}
const arr = [1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 8, 8, 8, 9, 11];
const length = unique(arr);
console.log(arr, length);
I believe this solution will pass more test cases (at least in my personal testing)
const removeDups = (nums) => {
// since mutating arrays I like to start at the end of the array so when the index is removed it doesn't impact the loop
let i = nums.length - 1;
while(i > 0){
// --i decrements then evaluates (i.e 5 === 4), i-- decriments after the evaluation (i.e 5 === 5 then decrements the last 5 to 4)
if(nums[i] === nums[--i]){
// remove the current index (i=current index, 1=number of indexes to remove including itself)
nums.splice(i,1);
}
}
console.log(nums);
return nums.length;
};
// Test Cases
console.log(removeDups([1,1,2])); // 2
console.log(removeDups([0,0,1,1,1,2,2,3,3,4])); // 5
console.log(removeDups([0,0,0,2,3,3,4,4,5,5])); // 5
Tried the solution provided by Kosh above, but it failed for bigger array [0,0,1, 1, 1, 2, 2, 3, 3, 4]. So ended up writing my own. Seems to work for all tests.
var removeDuplicates = function(nums) {
var i;
for (i = 0; i <= nums.length; i++) {
const tempNum = nums[i];
var j;
var tempIndex = [];
for (j = i+1; j <= nums.length; j++) {
if (tempNum === nums[j]) {
tempIndex.push(j)
}
}
nums.splice(tempIndex[0], tempIndex.length)
}
return (nums.length);
};