8

In CodinGame learning platform, one of the questions used as an example in a C# tutorial is this one:

The aim of this exercise is to check the presence of a number in an array.

Specifications: The items are integers arranged in ascending order. The array can contain up to 1 million items. The array is never null. Implement the method boolean Answer.Exists(int[] ints, int k) so that it returns true if k belongs to ints, otherwise the method should return false.

Important note: Try to save CPU cycles if possible.

Example:

int[] ints = {-9, 14, 37, 102};

Answer.Exists(ints, 102) returns true.
Answer.Exists(ints, 36) returns false.

My proposal was to do that:

using System;
using System.IO;

public class Answer
{
    public static bool Exists(int[] ints, int k)
    {
        foreach (var i in ints)
        {
            if (i == k)
            {
                return true;
            }

            if (i > k)
            {
                return false;
            }
        }

        return false;
    }
}

The result of the test was:

  • ✔ The solution works with a 'small' array (200 pts) - Problem solving
  • ✔ The solution works with an empty array (50 pts) - Reliability
  • ✘ The solution works in a reasonable time with one million items (700 pts) - Problem solving

I don't get the last point. It appears that the code may be more optimal than the one I suggested.

How to optimize this piece of code? Is a binary search an actual solution (given that the values in the array are already ordered), or there is something simpler that I missed?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199
  • It seems that the point of the exercise is to derive your own algorithm. Otherwise, what are you learning by using `BinarySearch` (other than how to use it)? – IAbstract Oct 20 '16 at 22:12
  • As an aside: `if (i > k) return false;` isn't much of an improvement. It lets you avoid loop iterations where `i>k`, but it also adds an extra comparison operation to each iteration. On the bright side, the CPU branch predictor will probably predict the results of that comparison with very high accuracy. – Brian Nov 03 '16 at 13:17

8 Answers8

14

Yes, I think that binary search O(log(N)) complexity v. O(N) complexity is the solution:

   public static bool Exists(int[] ints, int k) {
     return Array.BinarySearch(ints, k) >= 0;
   }

since Array.BinarySearch return non-negative value if the item (k) has been found:

https://msdn.microsoft.com/en-us/library/2cy9f6wb(v=vs.110).aspx

Return Value Type: System.Int32 The index of the specified value in the specified array, if value is found; otherwise, a negative number.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • 2
    However, wouldn't the use of `BinarySearch` negate the purpose of the exercise? – IAbstract Oct 20 '16 at 22:11
  • 2
    @IAbstract: it may look so; when designing a solution I'll put `Array.BinarySearch` without any hesitation. When doing an exercise in case `Array` class has not been forbidden, I'll do the same: the exercises should teach us writing a real-world code. And only if I'm supposed to implement an algorithm I'll do the binary search manually. – Dmitry Bychenko Oct 20 '16 at 22:20
  • [Here is the source of `Array.BinarySearch` from MSDN.](https://referencesource.microsoft.com/#mscorlib/system/array.cs,911). It has a lot of other error checking/handling stuff in there, but you can see the binary search algorithm starting on line 931 if you want a reference to implement that from scratch. – Cody Oct 21 '16 at 16:51
4

Here is a fast method for an ordered array

public static class Answer
{
    public static bool Exists( int[] ints, int k )
    {
        var lower = 0;
        var upper = ints.Length - 1;

        if ( k < ints[lower] || k > ints[upper] ) return false;
        if ( k == ints[lower] ) return true;
        if ( k == ints[upper] ) return true;

        do
        {
            var middle = lower + ( upper - lower ) / 2;

            if ( ints[middle] == k ) return true;
            if ( lower == upper ) return false;

            if ( k < ints[middle] )
                upper = Math.Max( lower, middle - 1 );
            else
                lower = Math.Min( upper, middle + 1 );
        } while ( true );
    }
}

Takes around 50 ticks on my cpu (with 90.000.000 items in the array)

Sample on dotnetfiddle

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
4
class Answer
{
    public static bool Exists(int[] ints, int k)
    {
        int index = Array.BinarySearch(ints, k);
        if (index > -1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    static void Main(string[] args)
    {
        int[] ints = { -9, 14, 37, 102 };
        Console.WriteLine(Answer.Exists(ints, 14)); // true
        Console.WriteLine(Answer.Exists(ints, 4)); // false
    }

}
2

Apparently, the task intends we use the default binary search method instead of implementing one. I was also somewhat surprised it is what it evaluates for in 3rd test. "The solution uses the standard library to perform the binary search (iterating on ints)"

Which kinda is confusing, they could have mentioned this in the code instead of giving some 15 - 20 minutes to solve. which is another reason for this confusion.

enter image description here

This is what I wrote for that question -> dividing array to half and search the half -> a more rudimentary way of implementing it...

int half = size/2;
if( k < ints[half])
{
    for(int i=0; i < half; i++)
    {
        if( k == ints[i])
        {
             return true;
        }
    }
}
else
{
    for(int i=half; i < size; i++)
    {
        if( k == ints[i])
        {
            return true;
        }                
    }
 }

Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199
  • Interesting. I've seen other cases (although I don't remember which ones) where the exercise seems to suggest to write a basic algorithm (and gives enough time to draft one), while in fact you're expected to respond with a one-liner containing a call to a method from .NET Framework. – Arseni Mourzenko May 19 '21 at 13:10
  • I find `binary search (iterating on ints)` sibylic in itself. – greybeard May 19 '21 at 15:49
1
 public static bool Exists(int[] ints, int k)
        {
            var d = 0;
            var f = ints.Length - 1;
            if (d > f) return false;
            if (k > ints[f] || k < ints[d]) return false;
            if (k == ints[f] || k == ints[d]) return true;
            return (BinarySearch(ints, k, d, f) > 0);
        }

 public static int BinarySearch(int[] V, int Key, int begin, int end)
        {
            if (begin > end)
                return -1;
            var MidellIndex = (begin + end) / 2;

            if (Key == V[MidellIndex])
                return MidellIndex;
            else
            {
                if (Key > V[MidellIndex])
                {
                    begin = MidellIndex + 1;
                    return BinarySearch(V, Key, begin, end);
                }
                else
                {
                    end = MidellIndex - 1;
                    return BinarySearch(V, Key, begin, end);
                }
            }

        }
ilyes
  • 382
  • 2
  • 10
1

I saw the all solutions, by the way I create and test the following recursive approach and get the complete points:

public static bool Exists(int[] ints, int k)
    {


        if (ints.Length > 0 && ints[0] <= k && k <= ints[ints.Length - 1])
        {
            if (ints[0] == k || ints[ints.Length - 1] == k) return true;
            return SearchRecursive(ints, k, 0, ints.Length - 1) != -1;
        }
        return false;
    }

    private static int SearchRecursive(int[] array, int value, int first, int last)
    {
        int middle = (first + last) / 2;

        if (array[middle] == value)
        {
            return middle;
        }
        else if (first >= last)
        {
            return -1;
        }
        else if (value < array[middle])
        {
            return SearchRecursive(array, value, first, middle - 1);
        }
        else
        {
            return SearchRecursive(array, value, middle + 1, last);
        }
    }
Beny Sad
  • 300
  • 3
  • 14
0

Yes, BinarySearch would be faster than most algorithms you can write manually. However, if the intent of the exercise is to learn how to write an algorithm, you are on the right track. Your algorithm, though, makes an unnecessary check with if (i > k) ... why do you need this?

Below is my general algorithm for simple requirements like this. The while loop like this is slightly more performant than a for-loop and out performs a foreach easily.

public class Answer
{
    public static bool Exists(int[] ints, int k)
    {
        var i = 0;
        var hasValue = false;

        while(i < ints.Length && !hasValue)
        {
            hasValue = ints[i] == k;
            ++i;
        }

        return hasValue;
    }
}
IAbstract
  • 19,551
  • 15
  • 98
  • 146
0

If you are trying to squeeze every ounce of speed out of it... consider that your array has 1..100 and you want to search for 78. Your algorithm needs to search and compare 78 items before you find the right one. How about instead you search the first item and its not there, so you jump to array size / 2 and find 50? Now you skipped 50 iterations. You know that 78 MUST be in the top half of the array, so you can again split it in half and jump to 75, etc. By continuously splitting the array in half, you do much fewer iterations then your brute force approach.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86