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;
}
Answer from zmag on Stack OverflowI 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.
Hi all,
Given:
var containsDuplicate = function(numsArray) {
return new Set(numsArray).size < nnumsArrayums.length;
};
By declaring Set() would the time complexity be O(n) because javascript would iterate throughout the whole numsArray before adding it to the set?
I'm assuming the space complexity is O(n) because we are adding values (the values in numsArray) to Set().
If we are not adding values, such as the example below, the space complexity would be O(1)
var containsDuplicate = function(numsArray) {
let a = new Set(); //space complexity of O(1)
let b = [] //space complexity of O(1)
return a;
};Javascript ES6 computational/time complexity of collections - Stack Overflow
javascript - What is the Time Complexity of creating set from array? - Stack Overflow
javascript - Time complexity of array.prototype.includes vs. set.prototype.has - Stack Overflow
Time and space complexity of Set()?
Right from that very paragraph your linked to:
Set objects must be implemented using [mechanisms] that, on average, provide access times that are sublinear on the number of elements in the collection.
You will find the same sentence for Maps, WeakMaps and WeakSets.
It looks the ECMA spec mandates that the implementations (e.g. Set.prototype.has) are to use a linear time (
O(n)) algorithm.
No:
The data structures used in this
Setobjects specification is only intended to describe the required observable semantics of Set objects. It is not intended to be a viable implementation model.
The observable semantics are mostly related to the predictable iteration order (which still can be implemented efficient and fast). It is indeed expected by the specification that an implementation uses a hash table or something similar with constant access, though trees (with logarithmic access complexity) are allowed as well.
For anyone who is curious, I did a very quick benchmark:
I'm running node 18.16.0.
This has been edited to include data on strings and deletion as users have pointed out there is a performance difference. However, object is still faster in everything I ran.
const benchmarkMap = size => {
console.time('benchmarkMap');
var map = new Map();
for (var i = 0; i < size; i++) map.set(i, i);
for (var i = 0; i < size; i++) var x = map.get(i);
console.timeEnd('benchmarkMap');
}
const benchmarkObj = size => {
console.time('benchmarkObj');
var obj = {};
for (var i = 0; i < size; i++) obj[i] = i;
for (var i = 0; i < size; i++) var x = obj[i];
console.timeEnd('benchmarkObj');
}
const benchmarkMapStr = size => {
console.time('benchmarkMapStr');
var map = new Map();
for (var i = 0; i < size; i++) map.set(i.toString(), i);
for (var i = 0; i < size; i++) var x = map.get(i.toString());
console.timeEnd('benchmarkMapStr');
}
const benchmarkObjStr = size => {
console.time('benchmarkObjStr');
var obj = {};
for (var i = 0; i < size; i++) obj[i.toString()] = i;
for (var i = 0; i < size; i++) var x = obj[i.toString()];
console.timeEnd('benchmarkObjStr');
}
const benchmarkMapDel = size => {
console.time('benchmarkMapDel');
var map = new Map();
for (var i = 0; i < size; i++) map.set(i, i);
for (var i = 0; i < size; i++) {
var x = map.get(i);
map.delete(i);
}
console.timeEnd('benchmarkMapDel');
}
const benchmarkObjDel = size => {
console.time('benchmarkObjDel');
var obj = {};
for (var i = 0; i < size; i++) obj[i] = i;
for (var i = 0; i < size; i++) {
var x = obj[i];
delete obj[i];
}
console.timeEnd('benchmarkObjDel');
}
var size = 1000000;
benchmarkMap(size);
benchmarkObj(size);
benchmarkMapStr(size);
benchmarkObjStr(size);
benchmarkMapDel(size);
benchmarkObjDel(size);
This will yield the following results:
benchmarkMap: 189.425ms
benchmarkObj: 41.519ms
benchmarkMapStr: 263.951ms
benchmarkObjStr: 102.959ms
benchmarkMapDel: 271.904ms
benchmarkObjDel: 102.373ms
I know this is an old question, but I was double checking the data. I too assumed Set.has would be O(1) or O(log N), but in my first test, it appeared to be O(N). The specs for these functions hint as much, but are quite hard to decipher: https://tc39.es/ecma262/#sec-array.prototype.includes https://tc39.es/ecma262/#sec-set.prototype.has Elsewhere, though, they also say that Set.has must be sublinear-- and I believe modern implementations are.
Empirically, Set.has demonstrates linear performance when I ran it in some code playgrounds... but in real environments like node and chrome, they there were no surprises. I'm not sure what the playground was running on the back end, but perhaps a Set polyfill was used. So be careful!
Here's my test cases, trimmed down to remove the randomness:
const makeArray = (size) => [...Array(size)].map(() => size);
const small = makeArray(1000000);
const medium = makeArray(10000000);
const large = makeArray(100000000);
const solution1 = arr => {
console.time('Array.includes');
arr.includes(arr.length - 1)
console.timeEnd('Array.includes');
}
const solution2 = arr => {
const set = new Set(arr)
console.time('Set.has');
set.has(arr.length-1)
console.timeEnd('Set.has');
}
console.log('** Testing small array:');
solution1(small);
solution2(small);
console.log('** Testing medium array:');
solution1(medium);
solution2(medium);
console.log('** Testing large array:');
solution1(large);
solution2(large);
In Chrome, though:
** Testing small array:
VM183:10 Array.includes: 1.371826171875 ms
VM183:17 Set.has: 0.005859375 ms
VM183:25 ** Testing medium array:
VM183:10 Array.includes: 14.32568359375 ms
VM183:17 Set.has: 0.009765625 ms
VM183:28 ** Testing large array:
VM183:10 Array.includes: 115.695068359375 ms
VM183:17 Set.has: 0.0048828125 ms
In Node 16.5:
Testing small array:
Array.includes: 1.223ms
Set.has: 0.01ms
Testing medium array:
Array.includes: 11.41ms
Set.has: 0.054ms
Testing large array:
Array.includes: 127.297ms
Set.has: 0.047ms
So, yeah, Arrays are definitionly linear, and Sets are much faster.
The comparison like that is not entirely fair because in the function where you use the Set, the Array needs to be converted to a Set first, which takes some time.
Have a look at the results below if this is ignored. I have updated the solution2 function to receive a Set and changed the while loop to a for loop - for the sake of direct comparison.
You may notice that for a small array, Set might be slower. This is trivial because the time complexity only really comes into affect for a large (significant) n.
Also note, Array.includes is indeed O(n) but because it is in a for loop which in the worst case could go up to n the solution has a time complexity of O(n^2).
const rand = (size) => [...Array(size)].map(() => Math.floor(Math.random() * size));
const small = rand(100);
const medium = rand(5000);
const large = rand(100000);
const solution1 = arr => {
console.time('Array.includes');
for (let int = 1;;int++) {
if (!arr.includes(int)) {
console.timeEnd('Array.includes');
return int;
}
}
}
const solution2 = set => {
console.time('Set.has');
for (let i = 1;;i++) {
if (!set.has(i)) {
console.timeEnd('Set.has');
return i
}
}
}
console.log('Testing small array:');
solution1(small);
solution2(new Set(small));
console.log('Testing medium array:');
solution1(medium);
solution2(new Set(medium));
console.log('Testing large array:');
solution1(large);
solution2(new Set(large));
Run code snippetEdit code snippet Hide Results Copy to answer Expand