Check for missing number in sequence

C#AlgorithmLinq

C# Problem Overview


I have an List<int> which contains 1,2,4,7,9 for example.

I have a range from 0 to 10.

Is there a way to determine what numbers are missing in that sequence?

I thought LINQ might provide an option but I can't see one

In the real world my List could contain 100,000 items so performance is key

C# Solutions


Solution 1 - C#

var list = new List<int>(new[] { 1, 2, 4, 7, 9 });
var result = Enumerable.Range(0, 10).Except(list);

Solution 2 - C#

Turn the range you want to check into a HashSet:

public IEnumerable<int> FindMissing(IEnumerable<int> values)
{
  HashSet<int> myRange = new HashSet<int>(Enumerable.Range(0,10));
  myRange.ExceptWith(values);
  return myRange;
}

Will return the values that aren't in values.

Solution 3 - C#

Using Unity i have tested two solutions on set of million integers. Looks like using Dictionary and two "for" loops gives better result than Enumerable.Except

FindMissing1 Total time: 0.1420 (Enumerable.Except)
FindMissing2 Total time: 0.0621 (Dictionary and two for loops)

public static class ArrayExtension
{
    public static T[] FindMissing1<T>(T[] range, T[] values)
    {
        List<T> result = Enumerable.Except<T>(range, values).ToList<T>();
        return result.ToArray<T>();
    }

    public static T[] FindMissing2<T>(T[] range, T[] values)
    {
        List<T> result = new List<T>();
        Dictionary<T, T> hash = new Dictionary<T, T>(values.Length);
        for (int i = 0; i < values.Length; i++)
            hash.Add(values[i], values[i]);

        for (int i = 0; i < range.Length; i++)
        {
            if (!hash.ContainsKey(range[i]))
                result.Add(range[i]);
        }

        return result.ToArray<T>();
    }
}

public class ArrayManipulationTest : MonoBehaviour
{
    void Start()
    {
        int rangeLength = 1000000;
        int[] range = Enumerable.Range(0, rangeLength).ToArray();
        int[] values = new int[rangeLength / 5];
        int[] missing;
        float start;
        float duration;

        for (int i = 0; i < rangeLength / 5; i ++)
            values[i] = i * 5;

        start = Time.realtimeSinceStartup;
        missing = ArrayExtension.FindMissing1<int>(range, values);
        duration = Time.realtimeSinceStartup - start;
        Debug.Log($"FindMissing1 Total time: {duration:0.0000}");

        start = Time.realtimeSinceStartup;
        missing = ArrayExtension.FindMissing2<int>(range, values);
        duration = Time.realtimeSinceStartup - start;
        Debug.Log($"FindMissing2 Total time: {duration:0.0000}");
    }
}

Solution 4 - C#

LINQ's Except method would be the most readable. Whether it performs adequately for you or not would be a matter for testing.

E.g.

range.Except(listOfValues);

Edit

Here's the program I used for my mini-benchmark, for others to plug away with:

static void Main()
{
	var a = Enumerable.Range(0, 1000000);
	var b = new List<int>();

	for (int i = 0; i < 1000000; i += 10)
	{
		b.Add(i);
	}

	Stopwatch sw = new Stopwatch();
	sw.Start();
	var c = a.Except(b).ToList();
	sw.Stop();
	Console.WriteLine("Milliseconds {0}", sw.ElapsedMilliseconds );
	sw.Reset();

	Console.ReadLine();
}

Solution 5 - C#

        List<int> selectedNumbers = new List<int>(){8, 5, 3, 12, 2};

        int firstNumber = selectedNumbers.OrderBy(i => i).First();
        int lastNumber = selectedNumbers.OrderBy(i => i).Last();
        
        List<int> allNumbers = Enumerable.Range(firstNumber, lastNumber - firstNumber + 1).ToList();

        List<int> missingNumbers = allNumbers.Except(selectedNumbers).ToList();

        foreach (int i in missingNumbers)
        {
            Response.Write(i);
        }

Solution 6 - C#

An alternative method which works in general for any two IEnunumerable<T> where T : IComparable. If the IEnumerables are both sorted, this works in O(1) memory (i.e. there is no creating another ICollection and subtracting, etc.) and in O(n) time.

The use of IEnumerable<IComparable> and GetEnumerator makes this a little less readable, but far more general.

Implementation

/// <summary>
/// <para>For two sorted IEnumerable&lt;T&gt; (superset and subset),</para>
/// <para>returns the values in superset which are not in subset.</para>
/// </summary>
public static IEnumerable<T> CompareSortedEnumerables<T>(IEnumerable<T> superset, IEnumerable<T> subset)
    where T : IComparable
{
    IEnumerator<T> supersetEnumerator = superset.GetEnumerator();
    IEnumerator<T> subsetEnumerator = subset.GetEnumerator();
    bool itemsRemainingInSubset = subsetEnumerator.MoveNext();

    // handle the case when the first item in subset is less than the first item in superset
    T firstInSuperset = superset.First();
    while ( itemsRemainingInSubset && supersetEnumerator.Current.CompareTo(subsetEnumerator.Current) >= 0 )
        itemsRemainingInSubset = subsetEnumerator.MoveNext();

    while ( supersetEnumerator.MoveNext() )
    {
        int comparison = supersetEnumerator.Current.CompareTo(subsetEnumerator.Current);
        if ( !itemsRemainingInSubset || comparison < 0 )
        {
            yield return supersetEnumerator.Current;
        }
        else if ( comparison >= 0 )
        {
            while ( itemsRemainingInSubset && supersetEnumerator.Current.CompareTo(subsetEnumerator.Current) >= 0 )
                itemsRemainingInSubset = subsetEnumerator.MoveNext();
        }
    }
}

Usage

var values = Enumerable.Range(0, 11);
var list = new List<int> { 1, 2, 4, 7, 9 };
var notIncluded = CompareSortedEnumerables(values, list);

Solution 7 - C#

If the range is predictable I suggest the following solution:

public static void Main()
{
	//set up the expected range
	var expectedRange = Enumerable.Range(0, 10);
	
	//set up the current list
	var currentList = new List<int> {1, 2, 4, 7, 9};
	
	//get the missing items
	var missingItems = expectedRange.Except(currentList);		
		
	//print the missing items
	foreach (int missingItem in missingItems)
	{
		Console.WriteLine(missingItem);
	}
	
	Console.ReadLine();
}

Regards, y00daa

Solution 8 - C#

This does not use LINQ but it works in linear time.

I assume that input list is sorted.

This takes O(list.Count).

private static IEnumerable<int> get_miss(List<int> list,int length)
{
    var miss = new List<int>();
    int i =0;
    for ( i = 0; i < list.Count - 1; i++)
    {
        foreach (var item in 
                     Enumerable.Range(list[i] + 1, list[i + 1] - list[i] - 1))
        {
            yield return item;
        }
        
    }
    foreach (var item in Enumerable.Range(list[i]+1,length-list[i]))
    {
        yield return item;
    }
}

This should take O(n) where n is length of full range.

 static void Main()
    {
        List<int> identifiers = new List<int>() { 1, 2, 4, 7, 9 };

        Stopwatch sw = new Stopwatch();

        sw.Start();
        List<int> miss = GetMiss(identifiers,150000);
        sw.Stop();
        Console.WriteLine("{0}",sw.ElapsedMilliseconds);

    }
private static List<int> GetMiss(List<int> identifiers,int length)
{
    List<int> miss = new List<int>();

    int j = 0;

    for (int i = 0; i < length; i++)
    {
        if (i < identifiers[j])
            miss.Add(i);

        else if (i == identifiers[j])
            j++;

        if (j == identifiers.Count)
        {
            miss.AddRange(Enumerable.Range(i + 1, length - i));
            break;
        }
    }
    
    return miss;
}

Solution 9 - C#

Ok, really, create a new list which parallels the initial list and run the method Except over it...

I have created a fully linq answer using the Aggregate method instead to find the missings:

var list = new List<int>(new[] { 1, 2, 4, 7, 9 }); // Assumes list is ordered at this point

list.Insert(0, 0);   // No error checking, just put in the lowest and highest possibles.
list.Add(10);        // For real world processing, put in check and if not represented then add it/them.

var missing = new List<int>();    // Hold any missing values found.

list.Aggregate ((seed, aggr) =>   // Seed is the previous #, aggr is the current number.
{
	var diff = (aggr - seed) -1;  // A difference between them indicates missing.
	
	if (diff > 0)                 // Missing found...put in the missing range.
		missing.AddRange(Enumerable.Range((aggr - diff), diff));
	
	return aggr;	
});

The missing list has this after the above code has been executed:

3, 5, 6, 8

Solution 10 - C#

this method here returns the number of missing elements ,sort the set , add all elements from range 0 to range max , then remove the original elements , then you will have the missing set

int makeArrayConsecutive(int[] statues) 
{

Array.Sort(statues);    
HashSet<int> set = new HashSet<int>();
    
for(int i = statues[0]; i< statues[statues.Length -1]; i++)
 {
  set.Add(i);
 }

for (int i = 0; i < statues.Length; i++)
{
 set.Remove(statues[i]);
}

var x = set.Count;    
return x;
// return set ; // use this if you need the actual elements + change the method return type        
}

Solution 11 - C#

for a List L a general solution (works in all programming languages) would be simply

L.Count()*(L.Count()+1)/2 - L.Sum();

which returns the expected sum of series minus the actual series.

for a List of size n the missing number is:

n(n+1)/2 - (sum of list numbers)

Solution 12 - C#

Create an array of num items

const int numItems = 1000;
bool found[numItems] = new bool[numItems];


List<int> list;

PopulateList(list);

list.ForEach( i => found[i] = true );

// now iterate found for the numbers found
for(int count = 0; i < numItems; ++numItems){
  Console.WriteList("Item {0} is {1}", count, found[count] ? "there" : "not there");
}

Solution 13 - C#

This method does not use LINQ and works in general for any two IEnunumerable<T> where T :IComparable

public static IEnumerable<T> FindMissing<T>(IEnumerable<T> superset, IEnumerable<T> subset) where T : IComparable
{
    bool include = true;
    foreach (var i in superset)
    {
        foreach (var j in subset)
        {
            include = i.CompareTo(j) == 0;
            if (include)
                break;
        }
        if (!include)
            yield return i;
    }
}

Solution 14 - C#

 int sum = 0,missingNumber;
        int[] arr = { 1,2,3,4,5,6,7,8,9};
        for (int i = 0; i < arr.Length; i++)
        {
            sum += arr[i];
        }
        Console.WriteLine("The sum from 1 to 10 is 55");
        Console.WriteLine("Sum is :" +sum);
        missingNumber =  55 - sum;
        Console.WriteLine("Missing Number is :-"+missingNumber);
        Console.ReadLine();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonView Question on Stackoverflow
Solution 1 - C#Darin DimitrovView Answer on Stackoverflow
Solution 2 - C#Andras ZoltanView Answer on Stackoverflow
Solution 3 - C#Dejan IgnjatovicView Answer on Stackoverflow
Solution 4 - C#Damien_The_UnbelieverView Answer on Stackoverflow
Solution 5 - C#Robin DayView Answer on Stackoverflow
Solution 6 - C#Wai Ha LeeView Answer on Stackoverflow
Solution 7 - C#John DoeView Answer on Stackoverflow
Solution 8 - C#Pratik DeoghareView Answer on Stackoverflow
Solution 9 - C#ΩmegaManView Answer on Stackoverflow
Solution 10 - C#WesamView Answer on Stackoverflow
Solution 11 - C#LazerDanceView Answer on Stackoverflow
Solution 12 - C#Preet SanghaView Answer on Stackoverflow
Solution 13 - C#Ali BayatView Answer on Stackoverflow
Solution 14 - C#Abdullah AyoubView Answer on Stackoverflow