0

I am working on the Code Wars challenge Missing number in Unordered Arithmetic Progression:

If you have not ever heard the term Arithmetic Progression, refer to a previous code challenge:

An Arithmetic Progression is defined as one in which there is a constant difference between the consecutive terms of a given series of numbers. [...] There is however one hitch: exactly one term from the original series is missing from the set of numbers which have been given to you. The rest of the given series is the same as the original AP. Find the missing term.

And here is an unordered version. Try if you can survive lists of MASSIVE numbers (which means time limit should be considered). :D

Note: Don't be afraid that the minimum or the maximum element in the list is missing, e.g. [4, 6, 3, 5, 2] is missing 1 or 7, but this case is excluded from the kata (i.e. the code challenge).

Example:

find([3, 9, 1, 11, 13, 5]) # => 7

My solution code works just fine in 99% of test cases. Nevertheless, a random test with big enough input always exceeds the permissible execution time.

function find(seq) {
    function compareNumbers(a, b) {
        return a - b;
    }
    let arr = [...seq].sort(compareNumbers);
    let difference = arr[1]-arr[0];
    let arrLen = arr.length;
    let i=0;
    while(i<arrLen){
        if(arr[i+1]-arr[i]!==difference) return arr[i] + difference;
        i++;
    }
}

I suppose there are some improvements to be made in the sort algorithm or the while loop. I've tried to replace the sort algorithm with a QuickSort one, but that didn't help.

trincot
  • 317,000
  • 35
  • 244
  • 286
Kanol77
  • 33
  • 7
  • What exactly is an "*unordered arithmetic progression*"? Can you give more details about the input? – Bergi Jun 27 '22 at 20:03
  • 1
    ahhh the mind games of the timeout quizzes with long data. :) Hint: Can you think of an alternate way to do this *without* sorting? What happens if you put the entire sequence into a *set*? – AirSquid Jun 27 '22 at 20:03
  • had you have a look to this [question](https://stackoverflow.com/questions/50942303/how-to-write-the-code-with-less-time-complexity-for-finding-the-missing-element)? – Nina Scholz Jun 27 '22 at 20:05
  • The input is an unsorted array. Like this [5, -1, 0, 3, 4, -3, 2, -2] for example. And all of the numbers in a progression like this have a fixed difference between them after they're sorted. – Kanol77 Jun 27 '22 at 20:07
  • @Kanol77 and which numbers from that progression can be "missing"? – Bergi Jun 27 '22 at 20:08
  • In the example that I used above - [5, -1, 0, 3, 4, -3, 2, -2] - the missing one is 1 because when you sort the array the difference between every one is 1 and between 0 and 2 there is the "missing" 1, so the difference isn't 1 between these numbers. – Kanol77 Jun 27 '22 at 20:11
  • @Kanol77 What about the sequence `[1,3,4,5]`? (Hint: your "working but slow" code fails on that). What about the sequence `[1,3]`? What about `[]`? – Bergi Jun 27 '22 at 20:12
  • @Bergi Yep, I know about that issue and I for sure will fix it, but it so happens that the code doesn't fail because of that but because of time complexity. – Kanol77 Jun 27 '22 at 20:15
  • 1
    *"99% of the test cases"*: please provide the test cases. – trincot Jun 27 '22 at 20:15
  • @AirSquid Good thinking, I'll test that! – Kanol77 Jun 27 '22 at 20:16
  • @Kanol77 Then please tell us what exactly the exercise is. All we currently have is your incorrect, slow code. – Bergi Jun 27 '22 at 20:16
  • 2
    I'm guessing there is some other *nugget of knowledge* in the problem description that would enable you to deduce the step-size (if it isn't given). That + fact that you can traverse a set in **linear** time are likely the keys to the long data. – AirSquid Jun 27 '22 at 20:18
  • @Bergi The exact exercise is: https://www.codewars.com/kata/568fca718404ad457c000033/train/javascript – Kanol77 Jun 27 '22 at 20:21
  • 2
    OK, that description has extra info you need (which is missing from your question). So the algo should be: get the minimum, the maximum and the length. From that you know the step. Make the set and check. For the future: when you ask a question about a code challenge **quote the challenge literally**. – trincot Jun 27 '22 at 20:24
  • 1
    Do you know what the **sum** of the complete sequence should be? Because you can take the expected sum and subtract the actual sum to get the missing number. – Wyck Jun 27 '22 at 20:46
  • @Wyck Yes, since min, max and length (due to "*exactly one term from the original series is missing from the set of numbers*") are given. Nice suggestion, a sum is more efficient to keep than a `Set`! – Bergi Jun 27 '22 at 20:51
  • If the range of the input numbers is such that the sum would not exceed the maximum safe integer, the sum method is indeed superior. I did not find any constraints in the code challenge description. – trincot Jun 27 '22 at 20:54
  • @trincot Use a bigint then :-) – Bergi Jun 27 '22 at 20:56
  • Yes, but then we might have a trade off... conversion of all input values to BigInt comes with a cost too. – trincot Jun 27 '22 at 20:58
  • @Wyck That is a great suggestion, but the test cases are generated randomly, and I can't access the sum of the sequence till I see the console while attempting. – Kanol77 Jun 27 '22 at 20:58
  • 1
    ? Kanol77, the idea is that you calculate the sum while iterating over the sequence and compare this with the sum you would *need* (which can be derived mathematically from min & step) – trincot Jun 27 '22 at 21:00

1 Answers1

1

As the code challenge guarantees that the minimum and maximum are present in the input, you can derive the step from the following info:

  • minimum
  • maximum
  • length

Given the minimum and the step, you can easily go through the sequence of expected values and verify they are in the set of input values.

Here is an implementation:

function find(seq) {
    let low = seq[0];
    let high = low;
    for (let i of seq) {
         if (i < low) low = i;
         else if (i > high) high = i;
    }
    let step = (high - low) / seq.length;
    let set = new Set(seq);
    for (let i = low + step; i < high; i += step) {
        if (!set.has(i)) return i;
    }
}

NB: I didn't use Math.min(...seq) here, as for very large arrays that will run into stack size limitations.

Alternative solution

As discussed in comments, we could calculate what the expected sum would be if there were no missing number in the sequence (based on the minimum and maximum in the input, and its length) and subtract the actual sum from that: the difference would be the missing number.

As this sum could be large and even overrun the maximum safe integer for the number data type in JavaScript, we should then shift the input numbers to a "safe" area (around 0) so that the sum will be close to zero. This way we avoid the inaccuracies that would be introduced if the sum would move out of the safe-integer range.

Here is how that looks:

function find(seq) {
    let low = seq[0];
    let high = low;
    for (let i of seq) {
         if (i < low) low = i;
         else if (i > high) high = i;
    }
    let step = (high - low) / seq.length;
    let mid = low + Math.floor((high - low) / 2);
    let expectedSum = (low - mid + high - mid) * (seq.length + 1) / 2;  
    let sum = 0;
    for (let i of seq) {
        sum += i - mid;
    }
    return expectedSum - sum + mid;
}
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Kanol77, I rejected your edit suggestion: it doesn't improve the code. If `i` is not true, then it follows it is 0 (in this context)... there is no need to have a special return for that. If you have doubts about the code, please comment below. – trincot Jun 28 '22 at 06:51