How to use SET to reduce running time in javascript [duplicate] - javascript

This question already has answers here:
Javascript ES6 computational/time complexity of collections
(3 answers)
Closed 3 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I have seen in an answer that the Set.has() method is O(1) and Array.indexOf() is O(n).
var a = [1, 2, 3, 4, 5];
a.indexOf(5);
s = new Set(a);
s.has(5); //Is this O(1)?
Is Set.has() really O(1) ?

I don't think the array that has 5 elements is good case to check time complexity.
So based on #Shidersz's snippet, I made a new one that has many elements and invoked once.
Is Set.has() really O(1) ?
Yes. Time complexity of Set.has() is O(1) according to result of the test below.
const MAX = 10000000
let a = []
a.length = MAX
for (let i = 0; i < MAX; i++) {
a[i] = i
}
let s = new Set(a)
let o = a.reduce((acc, e) => {
acc[e] = e
return acc
}, {})
console.time("Test_Array.IndexOf(0)\t")
a.indexOf(0);
console.timeEnd("Test_Array.IndexOf(0)\t")
console.time("Test_Array.IndexOf(n/2)\t")
a.indexOf(MAX / 2);
console.timeEnd("Test_Array.IndexOf(n/2)\t")
console.time("Test_Array.IndexOf(n)\t")
a.indexOf(MAX);
console.timeEnd("Test_Array.IndexOf(n)\t")
console.time("Test_Set.Has(0)\t\t")
s.has(0)
console.timeEnd("Test_Set.Has(0)\t\t")
console.time("Test_Set.Has(n/2)\t")
s.has(MAX / 2)
console.timeEnd("Test_Set.Has(n/2)\t")
console.time("Test_Set.Has(n)\t\t")
s.has(MAX)
console.timeEnd("Test_Set.Has(n)\t\t")
console.time("Test_Object[0]\t\t")
o[0]
console.timeEnd("Test_Object[0]\t\t")
console.time("Test_Object[n/2]\t")
o[MAX / 2]
console.timeEnd("Test_Object[n/2]\t")
console.time("Test_Object[n]\t\t")
o[MAX]
console.timeEnd("Test_Object[n]\t\t")
.as-console {
background-color: black !important;
color: lime;
}
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}

If one read the specification of has(), there is an algorithm describing it:
Algorithm for Set.prototype.has(value):
The following steps are taken:
Let S be the this value.
If Type(S) is not Object, throw a TypeError exception.
If S does not have a [[SetData]] internal slot, throw a TypeError exception.
Let entries be the List that is the value of S’s [[SetData]] internal slot.
Repeat for each e that is an element of entries,
If e is not empty and SameValueZero(e, value) is true, return true.
Return false.
And apparently, based on that algorithm and the presence of the word REPEAT one can have some confusion about it to be O(1) (we could think it could be O(n)). However, on the specification we can read that:
Set objects must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection.
Thanks to #CertainPerformance for pointing this.
So, we can create a test to compare Array.indexOf() and Set.has() in the worst case, i.e. look for an item that isn't in the array at all (thanks to #aquinas for pointing this test):
// Initialize array.
let a = [];
for (let i = 1; i < 500; i++)
{
a.push(i);
}
// Initialize set.
let s = new Set(a);
// Initialize object.
let o = {};
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i <= 10000000; i++)
{
a.indexOf(1000);
}
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i <= 10000000; i++)
{
s.has(1000);
}
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i <= 10000000; i++)
{
o.hasOwnProperty(1000);
}
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
And now we can see that Set.has() performs better than Array.indexOf(). There is also an extra comparison to Object.hasOwnProperty() to take as reference.
Conclusion:
While O(1) complexity isn't guaranteed, the specification requires the method to run in sublinear time. And Set.has(), generally, will perform better than Array.indexOf().
Another Test:
On next example, we going to generate a random set of sample data and use it later to compare the differents methods.
// Generate a sample array of random items.
const getRandom = (min, max) =>
{
return Math.floor(Math.random() * (max - min) + min);
}
let sample = Array.from({length: 10000000}, () => getRandom(0, 1000));
// Initialize array, set and object.
let a = [];
for (let i = 1; i <= 500; i++)
{
a.push(i);
}
let s = new Set(a);
let o = {};
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i < sample.length; i++)
{
a.indexOf(sample[i]);
}
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i < sample.length; i++)
{
s.has(sample[i]);
}
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i < sample.length; i++)
{
o.hasOwnProperty(sample[i]);
}
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Finally I want to apologize for the confusion the first version of my answer could cause. Thanks to all for giving me a better understanding of my mistakes.

Related

Js Math.random not random [duplicate]

I tried to get this to work, but the outer loop stops after second iteration, and everything that's after it does not execute(just like it was the end of the script). I want to fill two dimensional array with any character(here i used 'q' as an example)
var A=[[],[]];
for(var i=0;i<12;i++){
for(var j=0;j<81;j++){
A[i][j]='q';
}
}
It didn't work, so i put alert(i+' '+j); to see if it's even executing, and, as i wrote before, it stops after second iteration of outer loop, and then ignores rest of the script.
All I want is to have this array filled with same character in the given range(12 rows, 81 columns in this specific case), so if there's no hope in this method, i'll be glad to see one that works.
This does the job in one line.
var A = Array(12).fill(null).map(()=>Array(81).fill('q'))
This is an array of references and a bad idea as harunurhan commented.
var A = Array(12).fill(Array(81).fill('q'));
The Array.from() method creates a new, shallow-copied Array instance
from an array-like or iterable object.
function createAndFillTwoDArray({
rows,
columns,
defaultValue
}){
return Array.from({ length:rows }, () => (
Array.from({ length:columns }, ()=> defaultValue)
))
}
console.log(createAndFillTwoDArray({rows:3, columns:9, defaultValue: 'q'}))
var A=[[], []];
^ This line declares a two dimensional array of size 1x2. Try this instead:
var A = [];
for (var i = 0; i < 12; i++) {
A[i] = [];
for (var j = 0; j < 81; j++) {
A[i][j] = 'q';
}
}
Since fill() is the most succinct and intuitive, and it works as intended for immutable values, my preference would be an outer from() and an inner fill():
Array.from({length: 12}, _ => new Array(81).fill('q'));
The best approach to fill up 2D array would be like the following
let array2D = [], row = 3, col = 3, fillValue = 1
for (let i = 0; i < row; i++){
let temp = []
for (let j = 0; j < col; j++){
temp[j] = fillValue
}
array2D.push(temp)
}
You need to initialise a new array for i each time the first loop runs, and you don't need to set the layout of the array before you create it (Remove the [], [] inside the declaration of A). Try this:
var A = [];
for (var i = 0; i < 12; i++) {
A[i] = [];
for (var j = 0; j < 81; j++) {
A[i][j] = 'q';
}
}
console.log(A);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}

Tests fail when concatenating nested array in JavaScript

Implemented a Radix Sort which sorts a list of numbers. Here is my code:
function getDigit(number, index, lengthOfLongestNumber) {
let numberString = number.toString();
numberString = numberString.padStart(lengthOfLongestNumber, '0');
return parseInt(numberString[index], 10) || 0;
}
function getLengthOfLongestNumber(numbers) {
// return Math.floor(Math.log10(Math.max(...numbers))) + 1;
let largestNumber = 0;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > largestNumber) {
largestNumber = numbers[i];
}
}
return largestNumber.toString().length;
}
function radixSort(numbers) {
const lengthOfLongestNumber = getLengthOfLongestNumber(numbers);
for (let i = lengthOfLongestNumber - 1; i >= 0; i--) {
const buckets = new Array(10).fill().map(() => []);
while (numbers.length) {
const number = numbers.shift();
buckets[getDigit(number, i, lengthOfLongestNumber)].push(number);
}
for (let j = 0; j < 10; j++) {
// numbers = numbers.concat(buckets[j]); ---> uncomment this line and comment out the following while loop
// numbers = [...numbers, ...buckets[j]]; ---> or uncomment this line and comment out the following while loop
while (buckets[j].length) {
numbers.push(buckets[j].shift());
}
}
}
return numbers;
}
Tests fail when I merge buckets array into numbers array using concat() in radixSort function (inside inner for loop). But it somehow passes if I use while loop instead.
You can see and run tests in CodeSandbox.
Why is this happening? What is the difference between them that causes tests fail?
For the returned array it doesn't matter which of those alternatives you use, but if the tests expect you to sort the given array, so that after the call the input array has been sorted itself, then indeed, the commented-out alternatives will not work. This is because those alternatives assign to numbers instead of mutating it.
Just check the difference between the alternatives when ignoring the returned array, but looking at the given array:
let arr = [521, 219, 100, 422, 889, 529, 491, 777, 641, 882, 229];
radixSort(arr);
console.log(arr);
The commented-out alternatives will log an empty array.
The correct version mutates numbers, and does not assign a (new) array to it.
To avoid the loop, you can do this mutation also like this:
numbers.push(...buckets[j]);
And you can even replace the for loop around that statement, with just this line:
numbers.push(...buckets.flat());

Finding unique pairs in array

Given this input [3,1,2] I want to have this output [ [ 1, 1 ], [ 1, 2 ], [ 1, 3 ], [ 2, 2 ], [ 2, 3 ], [ 3, 3 ] ]
Its the unique pairs ([1,2] == [2,1])
Currently I've made that
const arr = [3,1,2];
const pairBuilder = (left, index, collection) =>
collection.slice(index).map(right => [left, right]);
const pairs = arr.sort().flatMap(pairBuilder);
console.log(pairs)
This code is functional, but I wonder if there is not a better way (in terms of performances) to achieve this ? I've though of using lodash to improve sorting / mapping (with chain), but my question is more about algorithm improvement.
You could use a Generator with a function* and slice the array for getting only unique pairs.
function* getPairs(array, left) {
var i = 0;
while (i < array.length) {
if (left) yield [left, array[i]];
else yield* getPairs(array.slice(i), array[i]);
i++;
}
}
var array = [1, 2, 3];
console.log([...getPairs(array)]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A classic approach.
function getPairs(array) {
var i, j, result = [];
for (i = 0; i < array.length; i++) {
for (j = i; j < array.length; j++) {
result.push([array[i], array[j]]);
}
}
return result;
}
var array = [1, 2, 3];
console.log(getPairs(array));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This is a naive and slow approach.
But i beleive it is faster than accepted answer and answer in the question.
It generates less new objects overhead and requires less memory.
Naive algorithm doesn't use recursion. so does not face recursion limit (50 by default)
Also i beleive it's still readable and easy to understand
const inp = [3, 1,2];
function genPair(inp) {
const length = inp.length;
const sorted = inp.sort();
const result = [];
for (let i = 0; i < length; i++) {
for (let j = i; j < length; j++) {
result.push([sorted[i], sorted[j]]);
}
}
return result;
}
const r = genPair(inp);
console.log(r);
link to js perf
https://jsperf.com/find-dups/1
Accepted answer in IE11 60% slower and in Chrome 10% slower

Is the Set.has() method O(1) and Array.indexOf O(n)? [duplicate]

This question already has answers here:
Javascript ES6 computational/time complexity of collections
(3 answers)
Closed 3 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I have seen in an answer that the Set.has() method is O(1) and Array.indexOf() is O(n).
var a = [1, 2, 3, 4, 5];
a.indexOf(5);
s = new Set(a);
s.has(5); //Is this O(1)?
Is Set.has() really O(1) ?
I don't think the array that has 5 elements is good case to check time complexity.
So based on #Shidersz's snippet, I made a new one that has many elements and invoked once.
Is Set.has() really O(1) ?
Yes. Time complexity of Set.has() is O(1) according to result of the test below.
const MAX = 10000000
let a = []
a.length = MAX
for (let i = 0; i < MAX; i++) {
a[i] = i
}
let s = new Set(a)
let o = a.reduce((acc, e) => {
acc[e] = e
return acc
}, {})
console.time("Test_Array.IndexOf(0)\t")
a.indexOf(0);
console.timeEnd("Test_Array.IndexOf(0)\t")
console.time("Test_Array.IndexOf(n/2)\t")
a.indexOf(MAX / 2);
console.timeEnd("Test_Array.IndexOf(n/2)\t")
console.time("Test_Array.IndexOf(n)\t")
a.indexOf(MAX);
console.timeEnd("Test_Array.IndexOf(n)\t")
console.time("Test_Set.Has(0)\t\t")
s.has(0)
console.timeEnd("Test_Set.Has(0)\t\t")
console.time("Test_Set.Has(n/2)\t")
s.has(MAX / 2)
console.timeEnd("Test_Set.Has(n/2)\t")
console.time("Test_Set.Has(n)\t\t")
s.has(MAX)
console.timeEnd("Test_Set.Has(n)\t\t")
console.time("Test_Object[0]\t\t")
o[0]
console.timeEnd("Test_Object[0]\t\t")
console.time("Test_Object[n/2]\t")
o[MAX / 2]
console.timeEnd("Test_Object[n/2]\t")
console.time("Test_Object[n]\t\t")
o[MAX]
console.timeEnd("Test_Object[n]\t\t")
.as-console {
background-color: black !important;
color: lime;
}
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
If one read the specification of has(), there is an algorithm describing it:
Algorithm for Set.prototype.has(value):
The following steps are taken:
Let S be the this value.
If Type(S) is not Object, throw a TypeError exception.
If S does not have a [[SetData]] internal slot, throw a TypeError exception.
Let entries be the List that is the value of S’s [[SetData]] internal slot.
Repeat for each e that is an element of entries,
If e is not empty and SameValueZero(e, value) is true, return true.
Return false.
And apparently, based on that algorithm and the presence of the word REPEAT one can have some confusion about it to be O(1) (we could think it could be O(n)). However, on the specification we can read that:
Set objects must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection.
Thanks to #CertainPerformance for pointing this.
So, we can create a test to compare Array.indexOf() and Set.has() in the worst case, i.e. look for an item that isn't in the array at all (thanks to #aquinas for pointing this test):
// Initialize array.
let a = [];
for (let i = 1; i < 500; i++)
{
a.push(i);
}
// Initialize set.
let s = new Set(a);
// Initialize object.
let o = {};
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i <= 10000000; i++)
{
a.indexOf(1000);
}
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i <= 10000000; i++)
{
s.has(1000);
}
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i <= 10000000; i++)
{
o.hasOwnProperty(1000);
}
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
And now we can see that Set.has() performs better than Array.indexOf(). There is also an extra comparison to Object.hasOwnProperty() to take as reference.
Conclusion:
While O(1) complexity isn't guaranteed, the specification requires the method to run in sublinear time. And Set.has(), generally, will perform better than Array.indexOf().
Another Test:
On next example, we going to generate a random set of sample data and use it later to compare the differents methods.
// Generate a sample array of random items.
const getRandom = (min, max) =>
{
return Math.floor(Math.random() * (max - min) + min);
}
let sample = Array.from({length: 10000000}, () => getRandom(0, 1000));
// Initialize array, set and object.
let a = [];
for (let i = 1; i <= 500; i++)
{
a.push(i);
}
let s = new Set(a);
let o = {};
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i < sample.length; i++)
{
a.indexOf(sample[i]);
}
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i < sample.length; i++)
{
s.has(sample[i]);
}
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i < sample.length; i++)
{
o.hasOwnProperty(sample[i]);
}
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Finally I want to apologize for the confusion the first version of my answer could cause. Thanks to all for giving me a better understanding of my mistakes.

Javascript returning a sparse array from "push" operations despite the logged value having numbers

I'm working on my backtracking algorithim skills and I've run into a problem. The problem is to generate all permutations of an array of distinct integers i.e
permute([1,2,3]) => [[1,3,2], [1,2,3], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
I have the written like so:
var permute = function(nums) {
var backtrack = function(nums, chosen, solutions) {
if (chosen.length === nums.length) {
// I'm not mutating nums.length so I don't get why this is pushing on empty arrays
console.log(chosen);
solutions.push(chosen);
} else {
for (let i = 0; i < nums.length; i++) {
if (!chosen.includes(nums[i])) {
chosen.push(nums[i]);
backtrack(nums, chosen, solutions);
chosen.pop();
}
}
}
}
var chosen = [];
var solutions = [];
backtrack(nums, chosen, solutions);
return solutions;
}
When I log out the chosen array variable on line 5, it has 4 values as I expect. However, I noticed that Javascript claims it has 4 values but a length property of zero. This means that when I run my function permute([1,2,3]), I get a result of [[], [], [], [], [], []] or nums.length factorial number of sparse arrays. I suspect that my loop is the problem and I'm not fully understanding all these array references I'm passing around but I'm not sure what else to do. Logging out chosen is what I expect. I appreciate any help or further readings.
This is not specific to the Chrome console environment. If I run this inside of a node repl I see the same behavior.
You mutate the same array chosen. For pushing a result, you could add a copy of the chosen array.
solutions.push(chosen.slice());
The part
for (let i = 0; i < nums.length; i++) {
if (!chosen.includes(nums[i])) {
chosen.push(nums[i]);
backtrack(nums, chosen, solutions);
chosen.pop();
}
}
iterates all elements of nums and checks if the value is already in chosen. If not, the value is pushed into the array chosen. Then the backtracking takes place and after this, the last value of chosen gets removed.
At the end, chosen is an empty array, which is the result of the pushing of the same array/object reference.
As result, you get the right amount of items (6), but always the same empty array.
var permute = function(nums) {
var backtrack = function(nums, chosen, solutions) {
if (chosen.length === nums.length) {
// I'm not mutating nums.length so I don't get why this is pushing on empty arrays
//console.log(chosen);
solutions.push(chosen.slice());
} else {
for (let i = 0; i < nums.length; i++) {
if (!chosen.includes(nums[i])) {
chosen.push(nums[i]);
backtrack(nums, chosen, solutions);
chosen.pop();
}
}
}
}
var chosen = [];
var solutions = [];
backtrack(nums, chosen, solutions);
return solutions;
}
console.log(permute([1, 2, 3]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories