Is there a way to make this Javascript code more efficient? - javascript

It is a simple exercise that I am doing for mere practice and leisure, I have done it in various ways but I was wondering if there is an even more practical way or to reduce the lines of code making use of the many methods of JavaScript.
The exercise is about receiving an array (arr) and a number (target) and returning another array with a pair of numbers found in 'arr' whose sum is equal to 'target'.
function targetSum3(arr, target) {
let newArr = [];
let copyArray = arr;
for (let i of copyArray) {
let x = Math.abs(i - target);
copyArray.pop(copyArray[i]);
if (copyArray.includes(x) && (copyArray.indexOf(x) != copyArray.indexOf(i))) {
newArr.push(i);
newArr.push(x);
return newArr;
}
}
return newArr;
}

If you are fine with a function that just returns a pair of numbers (the first match so to speak) whose sum equals the targets value, this might be enough:
function sumPair (arr, target) {
while(arr.length) {
let sum1 = arr.shift();
let sum2 = arr.find(val => sum1 + val === target);
if (sum2) return [sum2, sum1];
}
return null;
}

const targetSum = (arr, target) => {
const first = arr.find((v,i,a) => arr.includes(target-v) && (arr.indexOf(target-v) !== i));
return first ? [first, target - first] : null;
};
const values = [1,2,3,4,5,6,7,8,9];
console.log(targetSum(values, 1)); // null
console.log(targetSum(values, 2)); // null
console.log(targetSum(values, 3)); // [1, 2]
console.log(targetSum(values, 15)); // [6, 9]
console.log(targetSum(values, 20)); // null

I changed for loop with forEach (more efficient) and there is no need for the copyArray array so I removed it. I also changed pop() with shift(), I think you want to shift the array and not pop-it (if I understand the task correctly).
function targetSum3(arr, target) {
let newArr = [];
arr.forEach(element => {
let x = Math.abs(element - target); // calc x
arr.shift(); // removes first element from arr (current element)
if (arr.includes(x) && (arr.indexOf(x) != arr.indexOf(element))) {
newArr.push(element);
newArr.push(x);
return;
}
});
return newArr;
}

use Array.filter to find the target sum for all values in an given array. See comments in the snippet.
sumsForTargetInArray();
document.addEventListener(`click`,
evt => evt.target.id === `redo` && sumsForTargetInArray());
function sumsInArray(arr, target) {
// clone the array
const clone = arr.slice();
let result = [];
while (clone.length) {
// retrieve the current value (shifting it from the clone)
const current = clone.shift();
// filter arr: all values where value + sum = target
const isTarget = arr.filter(v => current + v === target);
// add to result.
// Sorting is to prevent duplicates later
if (isTarget.length) {
result = [...result, ...isTarget.map(v => [current, v].sort())];
}
}
// weed out duplicates (e.g. 0 + 3, 3 + 0)
const unique = new Set();
result.forEach(r => unique.add(`${r[0]},${r[1]}`));
// return array of array(2)
return [...unique].map(v => v.split(`,`).map(Number));
}
function sumsForTargetInArray() {
const testArr = [...Array(20)].map((_, i) => i);
const target = Math.floor(Math.random() * 30);
document.querySelector(`pre`).textContent = `testArray: ${
JSON.stringify(testArr)}\ntarget: ${target}\nResult: ${
JSON.stringify(sumsInArray(testArr, target))}`;
}
<pre></pre>
<button id="redo">Again</button>

Related

This question is about an exercise in the book Eloquent JavaScript

The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}

in the easiest and most concise way as possible

I want to sort an array values in an ascending or descending order without using sort().
I have created a function, however I am not satisfied with it.
I believe the code below could be much shorter and more concise.
Please let me know where to modify or you may entirely change the code too. Thank you in advance.
const func = arg => {
let flip = false;
let copy = [];
for(let val of arg) copy[copy.length] = val;
for(let i=0; i<arg.length; i++) {
const previous = arg[i-1];
const current = arg[i];
if(previous > current) {
flip = true;
copy[i] = previous;
copy[i-1] = current;
}
}
if(flip) return func(copy);
return copy;
};
l(func([5,2,8,1,9,4,7,3,6]));
If your input is composed of whole numbers, as in the example, pne option is to reduce the array into an object, whose keys are the numbers, and whose values are the number of times those values have occured so far. Then, iterate over the object (whose Object.entries will iterate in ascending numeric key order, for whole number keys), and create the array to return:
const func = arr => {
const valuesObj = {};
arr.forEach((num) => {
valuesObj[num] = (valuesObj[num] || 0) + 1;
});
return Object.entries(valuesObj)
.flatMap(
([num, count]) => Array(count).fill(num)
);
};
console.log(
func([5,2,8,1,9,10,10,11,4,7,3,6])
);
This runs in O(N) time.
To account for negative integers as well while keeping O(N) runtime, create another object for negatives:
const func = arr => {
const valuesObj = {};
const negativeValuesObj = {};
arr.forEach((num) => {
if (num >= 0) valuesObj[num] = (valuesObj[num] || 0) + 1;
else negativeValuesObj[-num] = (negativeValuesObj[-num] || 0) + 1;
});
return [
...Object.entries(negativeValuesObj).reverse()
.flatMap(
([num, count]) => Array(count).fill(-num)
),
...Object.entries(valuesObj)
.flatMap(
([num, count]) => Array(count).fill(num)
)
];
};
console.log(
func([5,2,8,1,-5, -1, 9,10,10,11,4,7,3,6, -10])
);
For non-integer items, you'll have to use a different algorithm with higher computational complexity.

How to remove same value if it has in an array? [duplicate]

Let's assume that I have ;
var array = [1,2,3,4,4,5,5];
I want it to be;
var newArray = [1,2,3];
I want to remove the duplicates completely rather than keeping them as unique values. Is there a way achieve that through reduce method ?
You could use Array#filter with Array#indexOf and Array#lastIndexOf and return only the values which share the same index.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(function (v, _, a) {
return a.indexOf(v) === a.lastIndexOf(v);
});
console.log(result);
Another approach by taking a Map and set the value to false, if a key has been seen before. Then filter the array by taking the value of the map.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(
Map.prototype.get,
array.reduce((m, v) => m.set(v, !m.has(v)), new Map)
);
console.log(result);
I guess it won't have some remarkable performance, but I like the idea.
var array = [1,2,3,4,4,5,5],
res = array.reduce(function(s,a) {
if (array.filter(v => v !== a).length == array.length-1) {
s.push(a);
}
return s;
}, []);
console.log(res);
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
function nukeDuplications(arr) {
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty+1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
var array = [1,2,3,4,4,5,5];
console.log(nukeDuplications(array));
A slightly more efficient solution would be to loop over the array 1 time and count the number of occurrences in each value and store them in an object using .reduce() and then loop over the array again with .filter() to only return items that occurred 1 time.
This method will also preserve the order of the array, as it merely uses the object keys as references - it does not iterate over them.
var array = [1,2,3,4,4,5,5];
var valueCounts = array.reduce((result, item) => {
if (!result[item]) {
result[item] = 0;
}
result[item]++;
return result;
}, {});
var unique = array.filter(function (elem) {
return !valueCounts[elem] || valueCounts[elem] <= 1;
});
console.log(unique)
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
// Both versions destroy array order.
// ES6 version
function nukeDuplications(arr) {
"use strict";
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty + 1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
// ES5 version
function nukeDuplicationsEs5(arr) {
"use strict";
var hash = {};
for (var i = 0; i < arr.length; i++) {
var el = arr[i];
var qty = hash[el] || 0;
hash[el] = qty + 1;
};
var ret = [];
for (let key in hash) {
if (hash.hasOwnProperty(key)) {
if (hash[key] === 1) {
ret.push(Number(key));
}
}
}
return ret;
}
var array = [1, 2, 3, 4, 4, 5, 5];
console.log(nukeDuplications(array));
console.log(nukeDuplicationsEs5(array));
There are a lot of over-complicated, and slow running code here. Here's my solution:
let numbers = [1,2,3,4,4,4,4,5,5]
let filtered = []
numbers.map((n) => {
if(numbers.indexOf(n) === numbers.lastIndexOf(n)) // If only 1 instance of n
filtered.push(n)
})
console.log(filtered)
you can use this function:
function isUniqueInArray(array, value) {
let counter = 0;
for (let index = 0; index < array.length; index++) {
if (array[index] === value) {
counter++;
}
}
if (counter === 0) {
return null;
}
return counter === 1 ? true : false;
}
const array = [1,2,3,4,4,5,5];
let uniqueValues = [];
array.forEach(element => {
if(isUniqueInArray(array ,element)){
uniqueValues.push(element);
}
});
console.log(`the unique values is ${uniqueValues}`);
If its help you, you can install the isUniqueInArray function from my package https://www.npmjs.com/package/jotils or directly from bit https://bit.dev/joshk/jotils/is-unique-in-array.
My answer is used map and filter as below:
x = [1,2,3,4,2,3]
x.map(d => x.filter(i => i == d).length < 2 ? d : null).filter(d => d != null)
// [1, 4]
Object.values is supported since ES2017 (Needless to say - not on IE).
The accumulator is an object for which each key is a value, so duplicates are removed as they override the same key.
However, this solution can be risky with misbehaving values (null, undefined etc.), but maybe useful for real life scenarios.
let NukeDeps = (arr) => {
return Object.values(arr.reduce((curr, i) => {
curr[i] = i;
return curr;
}, {}))
}
I would like to answer my questions with an answer I came up with upon reading it again
const array = [1, 2, 3, 4, 4, 5, 5];
const filtered = array.filter(item => {
const { length } = array.filter(currentItem => currentItem === item)
if (length === 1) {
return true;
}
});
console.log(filtered)
//Try with this code
var arr = [1,2, 3,3,4,5,5,5,6,6];
arr = arr.filter( function( item, index, inputArray ) {
return inputArray.indexOf(item) == index;
});
Also look into this link https://fiddle.jshell.net/5hshjxvr/

JavaScript array: is there a method that is a cross between splice and filter?

I have an array of objects, and want to:
Remove certain objects from the array
Treat the removed objects in a second step
I don't know in advance where these objects are. To recognize them, I need to use a function that queries their properties. It makes sense to retrieve the removed objects in a second array.
I had hoped to find a native method like filter or splice that would do this. Here's what I've come up with as a solution:
if (!Array.prototype.cherrypick) {
Array.prototype.cherrypick = function(fn) {
let basket = []
let ii = this.length
let item
for ( ; ii-- ; ) {
item = this[ii]
if (fn(item)) {
basket.unshift(item)
this.splice(ii, 1)
}
}
return basket
}
}
Have I missed something? Is there a native method that does this already? Is my solution unsound in some way?
Have I missed something? Is there a native method that does this already?
No, most native utility methods try not to mutate the array and instead return a new one.
Is my solution unsound in some way?
Using splice and unshift repeatedly like you do is very inefficient. Better write
if (typeof Array.prototype.cherrypick == "function")
console.warn("something already defines Array#cherrypick!");
Array.prototype.cherrypick = function(predicate) {
let removed = [];
for (let i=0, j=0; i<this.length; i++) {
const item = this[i];
if (fn(item)) {
removed.push(item);
} else {
this[j++] = item; // keep in array, but at new position
}
}
this.length = j; // removes rest
return removed;
};
Methods such as Array.filter() returns a new array instead of changing the original array.
You can create a partition method using Array.reduce() that will return two arrays - those that passed the predicate, and those that failed:
const partition = (predicate, arr) =>
arr.reduce((r, o) => {
r[+!!predicate(o)].push(o);
return r;
}, [[], []]);
const arr = [4, 8, 3, 10, 12];
const result = partition(n => n > 5, arr);
console.log(result);
And you can use the partition logic with Array.splice() to create the cherrypick method:
if (!Array.prototype.cherrypick) {
Array.prototype.cherrypick = function(predicate) {
const [removedItems, items] = arr.reduce((r, o) => {
r[+!!predicate(o)].push(o);
return r;
}, [[], []]);
this.splice(0, arr.length, items);
return removedItems;
}
}
const arr = [4, 8, 3, 10, 12];
const removed = arr.cherrypick(n => n > 5);
console.log('arr ', arr);
console.log('removed ', removed);
Just filter twice:
const picked = array.filter(fn);
array = array.filter((el, i, a) => !fn(el, i, a));
Use reduce as follows :
array = [1,2,3,4,5,6,7];
fn = n => n % 3 == 0;
[array, picked] = array.reduce ( (r, el) => (r[+fn(el)].push (el), r), [[], []] )
Do you want something like this?
const basket = ['apple', 'banana', 'car'];
const filterMapBasket = basket
.filter(item => item !== 'car')
.map(item => {return { name: item }});
This will result the initial basket array of strings to be filtered and transformed to an array of objects.
This will alter the source array in place removing items meeting some test, and return those items...
Array.prototype.removeIf = function(fn) {
let i = this.length;
let removed = [];
while (i--) {
if (fn(this[i], i)) {
removed.push(...this.splice(i, 1));
}
}
return removed;
};
let a = [0,1,2,3,4,5];
let removed = a.removeIf(i => i%2);
console.log(a);
console.log(removed);

Completely removing duplicate items from an array

Let's assume that I have ;
var array = [1,2,3,4,4,5,5];
I want it to be;
var newArray = [1,2,3];
I want to remove the duplicates completely rather than keeping them as unique values. Is there a way achieve that through reduce method ?
You could use Array#filter with Array#indexOf and Array#lastIndexOf and return only the values which share the same index.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(function (v, _, a) {
return a.indexOf(v) === a.lastIndexOf(v);
});
console.log(result);
Another approach by taking a Map and set the value to false, if a key has been seen before. Then filter the array by taking the value of the map.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(
Map.prototype.get,
array.reduce((m, v) => m.set(v, !m.has(v)), new Map)
);
console.log(result);
I guess it won't have some remarkable performance, but I like the idea.
var array = [1,2,3,4,4,5,5],
res = array.reduce(function(s,a) {
if (array.filter(v => v !== a).length == array.length-1) {
s.push(a);
}
return s;
}, []);
console.log(res);
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
function nukeDuplications(arr) {
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty+1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
var array = [1,2,3,4,4,5,5];
console.log(nukeDuplications(array));
A slightly more efficient solution would be to loop over the array 1 time and count the number of occurrences in each value and store them in an object using .reduce() and then loop over the array again with .filter() to only return items that occurred 1 time.
This method will also preserve the order of the array, as it merely uses the object keys as references - it does not iterate over them.
var array = [1,2,3,4,4,5,5];
var valueCounts = array.reduce((result, item) => {
if (!result[item]) {
result[item] = 0;
}
result[item]++;
return result;
}, {});
var unique = array.filter(function (elem) {
return !valueCounts[elem] || valueCounts[elem] <= 1;
});
console.log(unique)
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
// Both versions destroy array order.
// ES6 version
function nukeDuplications(arr) {
"use strict";
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty + 1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
// ES5 version
function nukeDuplicationsEs5(arr) {
"use strict";
var hash = {};
for (var i = 0; i < arr.length; i++) {
var el = arr[i];
var qty = hash[el] || 0;
hash[el] = qty + 1;
};
var ret = [];
for (let key in hash) {
if (hash.hasOwnProperty(key)) {
if (hash[key] === 1) {
ret.push(Number(key));
}
}
}
return ret;
}
var array = [1, 2, 3, 4, 4, 5, 5];
console.log(nukeDuplications(array));
console.log(nukeDuplicationsEs5(array));
There are a lot of over-complicated, and slow running code here. Here's my solution:
let numbers = [1,2,3,4,4,4,4,5,5]
let filtered = []
numbers.map((n) => {
if(numbers.indexOf(n) === numbers.lastIndexOf(n)) // If only 1 instance of n
filtered.push(n)
})
console.log(filtered)
you can use this function:
function isUniqueInArray(array, value) {
let counter = 0;
for (let index = 0; index < array.length; index++) {
if (array[index] === value) {
counter++;
}
}
if (counter === 0) {
return null;
}
return counter === 1 ? true : false;
}
const array = [1,2,3,4,4,5,5];
let uniqueValues = [];
array.forEach(element => {
if(isUniqueInArray(array ,element)){
uniqueValues.push(element);
}
});
console.log(`the unique values is ${uniqueValues}`);
If its help you, you can install the isUniqueInArray function from my package https://www.npmjs.com/package/jotils or directly from bit https://bit.dev/joshk/jotils/is-unique-in-array.
My answer is used map and filter as below:
x = [1,2,3,4,2,3]
x.map(d => x.filter(i => i == d).length < 2 ? d : null).filter(d => d != null)
// [1, 4]
Object.values is supported since ES2017 (Needless to say - not on IE).
The accumulator is an object for which each key is a value, so duplicates are removed as they override the same key.
However, this solution can be risky with misbehaving values (null, undefined etc.), but maybe useful for real life scenarios.
let NukeDeps = (arr) => {
return Object.values(arr.reduce((curr, i) => {
curr[i] = i;
return curr;
}, {}))
}
I would like to answer my questions with an answer I came up with upon reading it again
const array = [1, 2, 3, 4, 4, 5, 5];
const filtered = array.filter(item => {
const { length } = array.filter(currentItem => currentItem === item)
if (length === 1) {
return true;
}
});
console.log(filtered)
//Try with this code
var arr = [1,2, 3,3,4,5,5,5,6,6];
arr = arr.filter( function( item, index, inputArray ) {
return inputArray.indexOf(item) == index;
});
Also look into this link https://fiddle.jshell.net/5hshjxvr/

Categories

Resources