views:

444

answers:

7

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

+21  A: 
var list = new List<int>(new[] { 1, 2, 4, 7, 9 });
var result = Enumerable.Range(0, 10).Except(list);
Darin Dimitrov
+1 simple and clean
šljaker
Definitely simple and clean - but speed concerns if the ranges are large
Andras Zoltan
+3  A: 

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.

Andras Zoltan
The reason I went for this is with large number sequences this should be faster as the hashset is reduced by iterating through the input list and removing each element.The Except iterator method iterates the input collection for each element of the collection.
Andras Zoltan
`HashSet<T>.ExceptWith` method does not return a value (http://msdn.microsoft.com/en-us/library/bb299875.aspx). It actually transforms the original hash.
Darin Dimitrov
Have update code sample - just realised that as you posted that comment!
Andras Zoltan
If we really are doing ints, I just timed a range of a million ints and a list of 100000 known values, and the Linq Except (and I did remember to actually force enumeration) only took .25 seconds. Is that performant enough? Only OP knows.
Damien_The_Unbeliever
@Damien - I'm always amazed at how fast the basic linq enumerations can actually be :)It's most likely that OP isn't fussed about a few milliseconds here or there; I confess I'm a bit anal when it comes to that kind of thing! That said - I haven't actually benchmarked the two side by side.
Andras Zoltan
I have compared the 2 and the Hashset seems slower. Thanks for all your help guys
Jon
+3  A: 

Linqs 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();
}
Damien_The_Unbeliever
A: 

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");
}
Preet Sangha
what's wrong with this answer?
Preet Sangha
1. You are forcing the use of a List<> object; 2. You are creating a very large intermediate object (your array of bools); 3. It's not a clean and readable solution, look at the others.
Fábio Batista
Thank you. I don't mind the answer being down voted I just wish I'd originally been told the reasons so I could learn. Again thank you
Preet Sangha
+2  A: 
        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);
        }
Robin Day
+1  A: 

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

John Doe
A: 

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;
}
TheMachineCharmer