Improve performance for this method to flatten arrays - javascript

I am developing a large javascript application and unsurprisingly in IE11 it really struggles (Chrome = 8 secs, nodejs= 8 secs, IE11 = 35 secs).
So I did some profiling and found that this method is my time sink. I have already made all the changes I could think of - is there any other performance improvement modification I can put in place?
const flatten = function(arr, result) {
if (!Array.isArray(arr)) {
return [arr];
}
if(!result){
result = [];
}
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, result);
}
else {
result.push(value);
}
}
return result;
};
The method gets called lots of times, with smallish arrays (up to 10 string items, no more than 2 level deep).

Doing the if (!result) and Array.isArray(value) checks repeatedly should be avoided. I'd go for
function flatten(arr, result = []) {
if (Array.isArray(arr)) {
for (var i = 0; i < arr.length; i++) {
flatten(arr[i], result);
}
} else {
result.push(arr);
}
return result;
}
for simplicity and if the compiler doesn't optimise this enough by inlining and recognising loop patterns, I'd also try
function flatten(val) {
if (Array.isArray(val)) // omit this check if you know that `flatten` is called with arrays only
return flattenOnto(val, []);
else
return [val];
}
function flattenOnto(arr, result) {
for (var i = 0, len = arr.length; i < len; i++) {
var val = arr[i];
if (Array.isArray(val))
flattenOnto(val, result);
else
result.push(val);
}
return result;
}
I also used normal var instead of let because it had been known to be faster, dunno whether that has changed by now.
If, as you say, you also know that your arrays have a limited depth, you might even want to try to inline the recursive calls and spell it out to
function flatten(val) {
if (!Array.isArray(val)) return [val]; // omit this check if you can
var result = [];
for (var i = 0, ilen = arr.length; i < ilen; i++) {
var val = arr[i];
if (Array.isArray(val)) {
for (var j = 0, jlen = val.length; j < jlen; j++) {
// as deep as you need it
result.push(val[j]);
}
} else {
result.push(val);
}
}
return result;
}

The way you use recursion looks a bit odd to me: you're both returning the array and mutating a parameter depending on the depth level. You also have duplicated Array.isArray(array) calls. I think this code can be quite simplified, for example to something like the following (no parameter mutation as you can see):
const flatten = (array) => Array.isArray(array)
? array.reduce((accumulated, value) => accumulated.concat(flatten(value)), [])
: [array];
Not sure performances will be that improved though to be honest, but it looks more elegant in my opinion - jsPerf is your friend!

Related

Slicing an array with a function without using Array.slice

In my coding class I have a homework question that I'm stuck on!
I'm using Hackerrank. I think I'm over thinking a lot of things, but I need help, I'm new at this and I'm close to crying! Haha! Sorry about the backticks idk how to format codeblocks sorry! This is Javascript btw!!
The problem:
Write a function called slice, which accepts an array, and two numbers. The function should return a new array with the elements starting at the index of the first number and going until the index of the second number. If the third parameter is greater than the length of the array, it should slice until the end of the array. Do not use the built in Array.slice() function!
The function is expected to return an INTEGER_ARRAY.
The function accepts following parameters:
INTEGER_ARRAY arr
INTEGER index
INTEGER len
the function started off as
`function slice(arr, index, len) {
}`
After looking at other forums with similar problems, I tried this, but it isn't working and I think I'm overthinking it.
`function slice(arr, index, len) {
let result = [];
from = Math.max(index, 0);
to = Math.min(len);
if((!len) || (len > arr.length)) {
for(let i = from; i<arr.length; i++) {
result.push(arr[i]);}
}
else {
for(let i = from; i<to; i++) {
result.push(arr[i]);
}
}
return result;
}`
try this once and let me know if it works, tbh I didn't got why you have written (!len).
function slice(arr, index, len) {
let result = [];
from = Math.max(index, 0);
if((!len) || ((from+len) > arr.length)) {
for(let i = from; i<arr.length; i++) {
result.push(arr[i]);}
}
else {
for(let i = from; i<from + len; i++) {
result.push(arr[i]);
}
}
return result;
}

Array numbers sort without sort method and return new sorted array

I am trying to implement sort with aim of without copying old array and return new sorted array.
I have got some kind of success but having some bugs in my solution.
function findMin(arr, from, minVal) {
let minIdx = from || 0;
for (let i = from || 0; i < arr.length; i++) {
if (arr[i] < arr[minIdx] && arr[i] > minVal) {
minIdx = i;
}
}
return arr[minIdx];
}
function sort(arr) {
let sorted = [];
for (let i = 0; i < arr.length; i++) {
sorted.push(findMin(arr, 0, sorted[(sorted.length||1) - 1]||0 ));
}
return sorted;
}
//alert(sort([3, 1, 1]));
the above snippet working fine when there unique values.
I need help in fixing this. And how to make it more generic to do it in ascending and descending order.
I just want to reiterate that I don't want use native method and I don't want to copy or clone existing array. And the solution should return a new array.

Remove characters from a string which occurs more than twice

I want to remove the third occurrence of a character from a string.
Below is what I tried from my end:
function unique(list) {
var result = [];
function findOccurrences(arr, val) {
var i, j,
count = 0;
for (i = 0, j = arr.length; i < j; i++) {
(arr[i] === val) && count++;
}
return count;
}
$.each(list, function(i, e) {
if (findOccurrences(list, e) < 3) result.push(e);
});
return result.join("");
}
var srting = "DGHKHHNL";
var thelist = srting.split("")
console.log(unique(thelist));
Here are some expected results:
Input: DGHKHHNL
Expected: DGHKHNL
Input: AFKLABAYBIB
Expected: AFKLABYBI
Input: JNNNKNND
Expected: JNNKD
https://regex101.com/r/WmUPWW/1 .. I tried using this regex as well to solve the issue. But this this doesn't solves the issue as well.
Please help
Instead of counting the occurrences, you should check the occurrence count for the specific index you are evaluating. Basically, if it's the 3rd or more time that it has appeared, then you don't want it.
A slight change to your code can achieve this (you may want to choose a better function name):
function unique(list) {
var result = [];
function findOccurrenceIndex(arr, val, index) {
var i, j,
count = 0;
for (i = 0, j = arr.length; i < j; i++) {
(arr[i] === val) && count++;
if (i == index) {
return count;
}
}
return count;
}
$.each(list, function(i, e) {
if (findOccurrenceIndex(list, e, i) < 3) result.push(e);
});
return result.join("");
}
var srting = "DGHKHHNL";
var thelist = srting.split("")
console.log(unique(thelist));
Here is a working example
Note that this answer is based on your current code, I expect you could refactor the logic to reduce the code clutter.
In fact, the following reduces the code to a single loop. It works by building a dictionary of character counts as it works though the list. (It also doesn't rely on JQuery like your original attempt):
function unique(list) {
var result = [];
var counts = {};
for (var i = 0; i < list.length; i++) {
var c = list[i];
if (!counts[c])
counts[c] = 0;
counts[c]++;
if (counts[c] < 3) {
result.push(c);
}
}
return result.join("");
}
Here is a working example
An alternate approach which doesn't rely on jQuery (although you could easily swap that with a forEach):
function unique(str) {
var count = {}
return str.split("").reduce((acc, cur) => {
if (!(cur in count)) {
count[cur] = 1;
acc.push(cur);
return acc;
}
if (count[cur] == 2) return acc;
acc.push(cur);
count[cur]++;
return acc;
}, []).join("");
}
Here I used two helper array result and tempCount . tempCount is store each alphabet as key and count it ,so if it is exceed more than 3
function unique(list) {
var result = [];
var tempCount = [];
list = list.split("");
for(var i=0;i < list.length;i++) {
if(tempCount[list[i]]) {
if(tempCount[list[i]] == 2) continue;
tempCount[list[i]]++;
} else {
tempCount[list[i]] = 1;
}
result.push(list[i]);
}
return result.join("");
}
var srting = "JNNNKNND";
console.log(unique(srting));
Building off the answer by #musefan, another ES6 approach can use Array.reduce to build the counts/output based on an accumulator object:
const onlyTwo = list => list.split('').reduce((cache, letter) => {
cache[letter] ? cache[letter]++ : cache[letter] = 1;
if (cache[letter] < 3) cache.output += letter;
return cache;
}, {
output: ''
}).output;
console.log(onlyTwo('DGHKHHNL'));
console.log(onlyTwo('AFKLABAYBIB'));
console.log(onlyTwo('JNNNKNND'));
You can improve this by applying functional programming principles to separate the concerns of counting the duplicates and generating the output string. This way you can utilize the same accumulation technique with different max values.
const maxDuplicates = max => list => list.split('').reduce((cache, letter) => {
cache[letter] ? cache[letter]++ : cache[letter] = 1;
if (cache[letter] <= max) cache.output += letter;
return cache;
}, {
output: ''
}).output;
const onlyTwo = maxDuplicates(2);
console.log(onlyTwo('DGHKHHNL'));
console.log(onlyTwo('AFKLABAYBIB'));
console.log(onlyTwo('JNNNKNND'));
const onlyOne = maxDuplicates(1);
console.log(onlyOne('DGHKHHNL'));
console.log(onlyOne('AFKLABAYBIB'));
console.log(onlyOne('JNNNKNND'));

How To Remove All Odd Numbers In An Array Using Javascript?

Can someone debug this code? I cannot for the life of me find the (run-time) error:
function generate_fibonacci(n1, n2, max, out){
var n = n1+n2;
if(n<max){
out.push(n);
generate_fibonacci(n2, n, max, out);
}
}
function generate_fibonacci_sequence(max){
var out = [1];
generate_fibonacci(0, 1, max, out);
return out;
}
function remove_odd_numbers(arr){
for (var i = 0; i < arr.length; i++) {
if(!(arr[i]%2==0)){
arr.splice(i, 1);
}
}
return arr;
}
function sum(array){
var total = 0;
for (var i = 0; i < array.length; i++) {
total+=array[i];
}
return total;
}
var fib_sq = generate_fibonacci_sequence(4000000);
console.log("Before: " + fib_sq);
remove_odd_numbers(fib_sq);
console.log("After: " + fib_sq);
console.log("WTH?: " + remove_odd_numbers([1,2,3,4,5,6,7,8,9]));
Output:
Before: 1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578
After: 1,2,5,8,21,34,89,144,377,610,1597,2584,6765,10946,28657,46368,121393,196418,514229,832040,2178309,3524578
WTH?: 2,4,6,8
[Finished in 0.3s]
I'm going crazy or something. For some reason, all odd numbers are not being removed. But as you can see at the end, it works perfectly. I have no idea what is going on.
The problem in the original code is that when you remove the first 1 at index 0, the array gets shifted; now arr[i] is contains the second 1; but you just step over it.
You need to use while instead of if here, or copy to a separate list. This is an example for splicing:
function remove_odd_numbers1(arr){
for (var i = 0; i < arr.length; i++) {
// here
while (arr[i] % 2) {
arr.splice(i, 1);
}
}
return arr;
}
But it will be slow though. Better to create a new array:
function remove_odd_numbers2(arr){
var rv = [];
for (var i = 0; i < arr.length; i++) {
if (! (arr[i] % 2)) {
rv.push(arr[i]);
}
}
return rv;
}
Generally the best algorithm however is to use the same array, if the original is not needed, so that no extra memory is required (though on javascript this is of a bit dubious value):
function remove_odd_numbers3(arr){
var out = 0;
for (var i = 0; i < arr.length; i++) {
if (! (arr[i] % 2)) {
arr[out++] = arr[i];
}
}
arr.length = out;
return arr;
}
Notice however that unlike the splice algorithm, this runs in O(n) time.
Also, the Array.prototype.filter() is not bad, being a builtin. It also creates a new array and thus is comparable to the 2.
I'm not sure about this, however I doubt using splice is efficient compared to creating a new array.
function remove_odd_numbers(arr) {
var notOdd = [],
i = 0,
len = arr.length,
num;
for (; i < len; i++) {
!((num = arr[i]) % 2) && notOdd.push(num);
}
return notOdd;
}
EDIT: You should probably use the native filter function, as suggested by #Jack. I leave this answer as a reference.
Here is a really simple, fast way to do it. Using your data, it only took 48ms to complete. Hope this helps..
function noOdds(values){
return values.filter(function (num) {
return num % 2 === 0;
});
}
Because splice() modifies the array, your index will be off in the next iteration; you need to either decrease the loop variable, use a while loop like Antti proposed or iterate backwards like Crazy Train mentioned.
That said, the use of splice() is awkward to work with because it modifies the array in-place. This functionality can be easily accomplished using a filter function as well:
function remove_odd_numbers(arr)
{
return arr.filter(function(value) {
return value % 2 == 0;
});
}
This creates and returns a new array with only the even values.
Given the recency of this function, check the compatibility section how to handle browsers IE < 9. Many popular libraries, such as jQuery, underscore, etc. take care of this for you.
Update
Instead of filtering the array afterwards, it would be more memory efficient to only add the even values as you perform the recursion:
function generate_fibonacci(previous, current, max, callback)
{
var next = previous + current;
if (next < max) {
callback(next);
generate_fibonacci(current, next, max, callback);
}
}
function generate_fibonacci_sequence(max, callback)
{
callback(1);
callback(1);
generate_fibonacci(1, 1, max, callback);
}
var out = [];
generate_fibonacci_sequence(4000000, function(value) {
if (value % 2 == 0) {
out.push(value);
}
});
Instead of passing the out array, I'm passing a function to be called whenever a new sequence value is generated; the filtering is done inside that callback.
ES6 version from "Tabetha Moe" answer
function noOdds(arr) {
return arr.filter(value => value % 2 === 0);
}

checking if sum of array is greater than max number and vice versa javascript

function ArrayAdditionI(arr) {
var numbers = arr();
var arraySum = "";
for (var i = 0; i < numbers.length; i++) {
arraySum = arraySum + arr[i];
};
if (numbers.max() <= arraySum) {
arr = true
}
else if (numbers.max() > arraySum) {
arr = false;
}
return arr;
}
I need to find the numbers stored in an array called arr and check if they add up to or total the greatest number or whether they do not. If so, return true. If not, return false.
I am not sure I am calling the array correctly in the beginning.
Thanks
I actually wrote a library I use just for functions like this.
http://code.google.com/p/pseudosavant/downloads/detail?name=psMathStats.min.js
You would just do this:
var arr = [1,2,3,4,5,300];
if (arr.max() > arr.sum()){
// Max is greater than sum...
}
One warning though. This library prototypes the Array object which could mess up other scripting that uses for (var i in arr) on an Array, which you shouldn't ever do. I am actually almost done with v2 of the library with a number of new functions and it no longer prototypes the Array object.
You can just grab the .max() and .sum() methods from the code, and use them without the prototyping if you want though.
maxArray = function (arr) {
return Math.max.apply(Math, arr);
}
sumArray = function (arr) {
for (var i = 0, length = arr.length, sum = 0; i < length; sum += arr[i++]);
return sum;
}
You mean something like this?
function ArrayAdditionI(arr) {
for (var i = 0, sum=0; i < arr.length; i++) {
sum += arr[i];
}
return Math.max.apply( Math, arr ) <= sum;
}
function ArrayAdditionI(input) {
var arraySum, max;
arraySum = max = input[0];
for (var i = 1; i < input.length; i++) {
arraySum += input[i];
if(input[i] > max){
max = input[i];
}
};
return arraySum >= max;
}
If the numbers are positive, the answer is guaranteed - the sum is always greater than or equal to the max. If you need to calculate it, ddlshack's code looks good.
Looking at your code, there are a number of issues. First of all, arr() should error out. Arrays aren't functions, and trying to treat them as a function does nothing. Your array is already usable when it is passed in. Additionally, you want to initialize arraySum to 0, not "". The way you are doing it, the values in the array will be coerced into strings and concatenated together, which is not what you are looking for. Finally, arrays don't implement a max() method, but Math does, and functions/methods in javascript can be applied to an array in the manner shown by ddlshack and others.
There are some syntax errors: type missmatch, wrong assign and calls to method that doesn't exists. If I'm understanding what do you want to do, this is the correct code(if changing items order is not a problem):
function ArrayAdditionI(arr) {
var ret = false;
var arraySum = 0;
for (var i = 0; i < arr.length; i++) {
arraySum += arr[i];
}
if (arr.sort()[arr.length-1] <= arraySum) {
ret = true
}
return ret;
}

Categories

Resources