0

I want to create an array of type number with length n. All values inside the array should be 0 except the one which index matches a condition.

Thats how i currently do it:

const data: number[] = [];
for (let i = 0; i < n; i++) {
  if (i === someIndex) {
    data.push(someNumber);
  } else {
    data.push(0);
  }
}

So lets say n = 4, someIndex = 2, someNumber = 4 would result in the array [0, 0, 4, 0].

Is there a way to do it in O(1) instead of O(n)?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
M.Dietz
  • 900
  • 10
  • 29
  • 1
    You need to assign *n* values, so there is no way to do that in sublinear time. – trincot Jan 15 '20 at 14:47
  • You have a loop. So no, you cannot do it in O(1). –  Jan 15 '20 at 14:47
  • If you can treat `undefined` as `0` in your array, you may do it as : `let data = new Array(n); const someIndex = 2; const someNumber = 4; data[someIndex] = someNumber;` – SomeDude Jan 15 '20 at 14:49
  • Are you comfortable with extending arrays? It's possible in that case. – VLAZ Jan 15 '20 at 14:51
  • 1
    @SomeDude sparse Arrays are a pest. And they tend to make all accesses slower. – Thomas Jan 15 '20 at 15:04
  • You can use typed array instead. `const arr = new Int8Array(4); arr[2] = 4; console.log(arr)` It is initialised with 0. But depending on the implementation this may still be O(n) due to initialisation :) – Yury Tarabanko Jan 15 '20 at 15:08

2 Answers2

3

Creating an array of size n in O(1) time is theoretically possible depending on implementation details - in principle, if an array is implemented as a hashtable then its length property can be set without allocating or initialising space for all of its elements. The ECMAScript specification for the Array(n) constructor doesn't mandate that Array(n) should do anything which necessarily takes more than O(1) time, although it also doesn't mandate that the time complexity is O(1).

In practice, Array(n)'s time complexity depends on the browser, though verifying this is a bit tricky. The performance.now() function can be used to measure the time elapsed between the start and end of a computation, but the precision of this function is artificially reduced in many browsers to protect against CPU-timing attacks like Spectre. To get around this, we can call the constructor repetitions times, and then divide the time elapsed by repetitions to get a more precise measurement per constructor call.

My timing code is below:

function timeArray(n, repetitions=100000) {
    var startTime = performance.now();
    for(var i = 0; i < repetitions; ++i) {
        var arr = Array(n);
        arr[n-1] = 'foo';
    }
    var endTime = performance.now();
    return (endTime - startTime) / repetitions;
}

for(var n = 10000; n <= 1000000; n += 10000) {
    console.log(n, timeArray(n));
}

Here's my results from Google Chrome (version 74) and Firefox (version 72); on Chrome the performance is clearly O(n) and on Firefox it's clearly O(1) with a quite consistent time of about 0.01ms on my machine.

running times

I measured using repetitions = 1000 on Chrome, and repetitions = 100000 on Firefox, to get accurate enough results within a reasonable time.

Another option proposed by @M.Dietz in the comments is to declare the array like var arr = []; and then assign at some index (e.g. arr[n-1] = 'foo';). This turns out to take O(1) time on both Chrome and Firefox, both consistently under one nanosecond:

running times 2

That suggests the version using [] is better to use than the version using Array(n), but still the specification doesn't mandate that this should take O(1) time, so there may be other browsers where this version takes O(n) time. If anybody gets different results on another browser (or another version of one of these browsers) then please do add a comment.

kaya3
  • 47,440
  • 4
  • 68
  • 97
1

You need to assign n values, and so there is that amount of work to do. The work increases linearly with increasing n.

Having said that, you can hope to make your code a bit faster by making use of .fill:

const data: number[] = Array(n).fill(0);
data[someIndex] = someNumber;

But don't be mistaken; this is still O(n): .fill may be faster, but it still requires to fill the whole array with zeroes, which means a corresponding size of memory needs to be initialised, so that operation has linear time complexity.

If however you drop the requirement that zeroes need to be assigned, then you can only store the someNumber:

const data: number[] = Array(n);
data[someIndex] = someNumber;

This way you actually do not allocate the memory for the whole array, so this code snippet runs in constant time. Any access to an index different from someIndex will give you a value of undefined. You may trap that condition and translate that to a zero on-the-fly:

let value = i in data ? data[i] : 0;

Obviously, if you are going to access all indices of the array like that, you'll have again a linear time complexity.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thanks, `data[someIndex] = someNumber;` works for me, because im fine with the arrays values being empty instead of 0. Didn´t know an array of length n can have a value for the n-1th index and keep everything beforehand empty. But it seems to work like that. – M.Dietz Jan 15 '20 at 15:05
  • I'm not convinced by this. The `Array(n)` constructor isn't specified to take O(1) time, and my tests (in Google Chrome version 74) indicate that it does take O(n) time; on my machine, about 6.5ms for n = 10000, 13ms for n = 20000, 19.5ms for n = 30000... – kaya3 Jan 15 '20 at 15:31
  • @kaya, the [EcmaScript specs](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-array-len) on `Array(n)` (with `n` an integer) only specify operations that take constant time. The only thing that really happens with the length is setting the `length` property. But I cannot exclude that implementations do some extra work in anticipation of what code may follow... but that is not imposed by the language specification. – trincot Jan 15 '20 at 15:39
  • I'm getting WILDLY different results on my machine. I suspect the microbenchmark simply isn't accurate enough. I tried `start = performance.now(); a = Array(500000000); console.log("end:", performance.now() - start)` and the result was `0.00999998883344233`. With `Array(5)` I've gotten `0.010000017937272787`, `0.0050000089686363935`, `0.014999997802078724`, `0.004999979864805937` and even something really close: `0.00999998883344233`. Bottom line - doesn't seem like it's deterministic, nor does it seem linear. It shouldn't be, either. – VLAZ Jan 15 '20 at 15:43
  • Most modern browsers hobble `performance.now()`'s accuracy to protect against CPU-timing attacks like Spectre and Meltdown. You can get around this by putting the `a = Array(big_number)` inside a loop that executes `reps` times, and then computing `(end_time - start_time) / reps`, so that the resolution of the timer doesn't matter so much. Using 1000 repetitions my tests show quite consistent linear behaviour; I'll post an answer. – kaya3 Jan 15 '20 at 15:48
  • I didn´t have to initialize the array with `const data: number[] = Array(n);`. Initializing it with `const data: number[] = []` and then executing `data[someIndex] = someNumber;` was enough. The resulting array seems to have two values, one which defines how many empty values the array has and another for the assigned value. So for example `[empty × 2, 36.5]`. So shouldn´t this take O(1), because the array has a constant length? – M.Dietz Jan 15 '20 at 15:48
  • @M.Dietz yes, it has constant time. Looking up a specific index of an array does not take `O(n)` - it's handled the same way object property access is handled. And object property access generally behaves like fetching from a hash map. – VLAZ Jan 15 '20 at 15:52
  • @M.Dietz also, the array you show *doesn't* have two properties. It's just how it's shown - any index that is not currently assigned is *empty*. If you try to fetch a value from it, then you'd get `undefined` but there is a slightly different behaviour than actually having `undefined` assigned. If you had, say, `[1,,,,5,,,,,9]` you'd get `[ 1, empty x 3, 5, empty x 4, 9]` – VLAZ Jan 15 '20 at 15:54