Why is shift faster than index access in this JavaScript example? - javascript

// Shifting the array and accessing 0
let sum = 0;
while(matrix.length > 0) {
sum += matrix[0][0];
matrix.shift();
}
// direct access
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
sum += matrix[i][0];
}
https://jsperf.com/shift-vs-index-access
Shifting the array and accessing 0 is faster than
direct access in the given examples in the above jsPerf link.
Isn't shift() an O(n) operation?

No, it's not faster. It's just your benchmark being broken. The shift() operation empties the matrix array, and after the first iteration you are comparing your codes on an empty array.
When you are benchmarking code that mutates your data structure, you need to re-create the data structure on every test run. I've fixed your jsperf.com case and as expected shift is slower (notice that probably a large part of the execution time is spent on createMatrix, so in fact it's a lot slower).

Related

Javascript loop time complexity

When looping an array, people often use a simple method like below.
const array = [1,2,3,4,5];
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}
My question is if array[i] is O(1) operation or not.
For example, when i is 3, does javascript get the number immediately OR count from 0 to 3 again?
Yes. array[i] is O(1). However you do it N times, which makes the entire loop O(n).
Yes, it is O(1).
Because it takes a single step to access an item of an array via its index, or add/remove an item at the end of an array, the complexity for accessing, pushing, or popping a value in an array is O(1).
ref: here
Should be O(1) you can read it at here
javascript array complexity

What is the time complexity of this recursive solution for removing duplicates?

After I complete a Leetcode question, I always try to also determine the asymptotic time complexity, for practice.
I am now looking at problem 26. Remove Duplicates from Sorted Array:
Given a sorted array nums, remove the duplicates in-place such that
each element appears only once and returns 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.
Clarification:
Confused why the returned value is an integer but your answer is an
array?
Note that the input array is passed in by reference, which means a
modification to the input array will be known to the caller as well.
Internally you can think of this:
// nums is passed in by reference. (i.e., without making a copy) int
len = removeDuplicates(nums);
// any modification to nums in your function would be known by the caller.
// using the length returned by your function, it prints the first len elements.
for (int i = 0; i < len; i++) {
print(nums[i]);
}
Example 1:
Input: nums = [1,1,2]
Output: 2, nums = [1,2]
Explanation: 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.
My code:
/**
* #param {number[]} nums
* #return {number}
*/
var removeDuplicates = function(nums) {
nums.forEach((num,i) => {
if(nums[i+1] !== null && nums[i+1] == nums[i] ){
nums.splice(i, 1);
console.log(nums)
removeDuplicates(nums)
}
})
return nums.length;
};
For this problem, I got O(log n) from my research. Execution time halves each time it runs. Can someone please verify or determine if I am wrong?
Are all recursive functions inherently O(logn)? Even if there are multiple loops?
For this problem, I got O(log n) from my research. Execution time halves for each time it's run. Can someone please verify or determine if I am wrong?
The execution time does not halve for each run: imagine an extreme case where the input has 100 values and they are all the same. Then at each level of the recursion tree one of those duplicates will be found and removed. Then a deeper recursive call is made. So for every duplicate value there is a level in the recursion tree. So in this extreme case, the recursion tree will have a depth of 99.
Even if you would revise the algorithm, it would not be possible to make it O(log n), as all values in the array need to be read at least once, and that alone already gives it a time complexity of O(n).
Your implementation uses splice which needs to shift all the values that follow the deletion point, so one splice is already O(n), making your algorithm O(n²) (worst case).
Because of the recursion, it also uses O(n) extra space in the worst case (for the call stack).
Are all recursive functions inherently O(logn)?
No. Using recursion does not say anything about the overall time complexity. It could be anything. You typically get O(logn) when you can ignore O(n) (like half) of the current array when making the recursive call. This is for instance the case with a Binary Search algorithm.
Improvement
You can avoid the extra space by not using recursion, but an iterative method. Also, you are not required to actually change the length of the given array, only to return what its new length should be. So you can avoid using splice. Instead, use two indexes in the array: one that runs to the next character that is different, and another, a slower one, to which you copy that new character. When the faster index reaches the end of the input, the slower one indicates the size of the part that has the unique values.
Here is how that looks:
var removeDuplicates = function(nums) {
if (nums.length == 0) return 0;
let len = 1;
for (let j = 1; j < nums.length; j++) {
if (nums[j-1] !== nums[j]) nums[len++] = nums[j];
}
return len;
};

Why do the following two pieces of code run so differently?

Look at these two pieces of code, the second only add the third line. But time is 84 times. Anybody can explain why?
let LIMIT = 9999999;
let arr = new Array(LIMIT);
// arr.push(1);
console.time('Array insertion time');
for (let i = 1; i < LIMIT; i++) {
arr[i] = i;
}
console.timeEnd('Array insertion time');
let LIMIT = 9999999;
let arr = new Array(LIMIT);
arr.push(1);
console.time('Array insertion time');
for (let i = 1; i < LIMIT; i++) {
arr[i] = i;
}
console.timeEnd('Array insertion time');
The arr.push(1) operation creates a "sparse" array: it has a single element present at index 9999999. V8 switches the internal representation of such a sparse array to "dictionary mode", i.e. the array's backing store is an index→element dictionary, because that's significantly more memory efficient than allocating space for 10 million elements when only one of them is used.
The flip side is that accessing (reading or writing) elements of a dictionary-mode array is slower than for arrays in "fast/dense mode": every access has to compute the right dictionary index, and (in the scenario at hand) the dictionary has to be grown several times, which means copying all existing elements to a new backing store.
As the array is filled up, V8 notices that it's getting denser, and at some point transitions it back to "fast/dense mode". By then, most of the slowdown has already been observed. The remainder of the loop has some increased cost as well though, because by this time, the arr[i] = i; store has seen two types of arrays (dictionary mode and dense mode), so on every iteration it must detect which state the array is in now and handle it accordingly, which (unsurprisingly) costs more time than not having to make that decision.
Generalized conclusion: with JavaScript being as dynamic and flexible as it is, engines can behave quite differently for very similar-looking pieces of code; for example because the engine optimizes one case for memory consumption and the other for execution speed, or because one of the cases lets it use some shortcut that's not applicable for the other (for whatever reason). The good news is that in many cases, correct and understandable/intuitive/simple code also tends to run quite well (in this example, the stray arr.push looks a lot like a bug).

Efficient duplicates search algorithm

I need a script to search efficiently all the duplicates in a one-dimensional array.
I tried a naive method :
for(var i=0, ii<arr.length-1; i<ii; i++)
for(var j=i+1, jj<arr.length; j<jj; j++)
if(arr[i] == arr[j])
// remove the duplicate
Very simple but it takes a too long time if the array contains a large set of values. The tables that I use often contain hundreds of thousands of values, so that the number of iterations required for this operation is HUGE !
If someone has an idea !?
Use a LinkedHashSet or OrderedHashSet implementation, it does not allow duplicates and provides expected O(1) on insertion, lookup, and deletion. Since your OP says you want to remove the duplicates, there is no faster way to do this than O(n). In an array of 1,000,000 items max time was 16ms
Create a LinkedHashSet hs
foreach object obj in arr
-- hs.add(obj);
Complexity is expected O(n) with a good hash function.
This code could be the most efficient way you can do it ..!! Which is nothing but the direct implementation of set .
function eliminateDuplicates(arr) {
var i,
len=arr.length,
out=[],
obj={};
for (i=0;i<len;i++) {
obj[arr[i]]=0;
}
for (i in obj) {
out.push(i);
}
return out;
}

When should I use forEach?

I can either do,
var arr = [];
arr.forEach(function(i) {
i;
});
for (var i = 0, length = arr.length; i < length; ++i) {
arr[i];
}
When should I use one over the other, is there performance differences?
You use foreach whenever :
your array is associtive or has gaps, i.e. you cannot reach every element by an incremented number (1,2,5, 'x', -7)
you need to iterate in exactly the same order as they appear in the array. (e.g. 2,1,3)
you want to be sure not the get into an endless loop
The last point is the main difference: foreach works on a copy, so even if you alter the elements, the array remains intact and can be iterated without defects.
That copy makes foreach somewhat slower than for, since it has to copy data. Keep in mind that some old or rare browsers don´t supports foreach, but they do support "for". Unless your array is really big (10.000 + items), ignore the speed difference. It´s in the milliseconds.
You use for whenever
you want an easy way to aler the array you are moving on
you want specific sequences, e.g. for ($i=100; $i < 1000; $i += 5) resulting in 100, 105, 110...

Categories

Resources