Search code examples
c#linq-to-objectssorteddictionary

Using SortedDictionary TakeWhile returns empty


The TakeWhile extension method has the following comment in its tooltip: "the element's index is used in the logic of the predicate function". Note that this tooltip is not visible in the normal tooltip but only in the tooltip of the list of intellisense for the lists members of the variable sortedDictionary.

That's exactly what is was looking for in combination with a SortedDictionary.

var first = sortedDictionary.First(kv => kv.Key.ToString().StartsWith(searchkey));
var last= sortedDictionary.Last(kv => kv.Key.ToString().StartsWith(searchkey));
var range = sortedDictionary.TakeWhile(kv => kv.Key.ToString().StartsWith(searchkey));

The items first and last are found and correct, however my collection range is empty.
What is wrong here? I expected a range with all items between first and last including them.

I could still find the range using first and last but TakeWhile uses the index while First and Last apparantly don't.

EDIT: "using the index" turns out to have nothing to do with sorting but you can use the index in the query. E.g. when I replace SortedDictionary with SortedList I can do:

int ix1 = sortedList.IndexOfKey(first.Key);
int ix2 = sortedList.IndexOfKey(last.Key);
var range = sortedList.SkipWhile((x, i) => i < ix1).TakeWhile((x, i) => i <= ix2);

Also with SortedDictionary I can do:

var range = sortedList.SkipWhile(x => !x.Key.ToString().StartsWith(searchkey))
                      .TakeWhile(x => x.Key.ToString().StartsWith(searchkey));

I will have to test which method is faster and also have to test the Where query.


Solution

  • If the first element in your sequence does not match your predicate, then the TakeWhile method will exit:

    Returns elements from a sequence as long as a specified condition is true

    The First method will take the first element that matches your predicate:

    Returns the first element in a sequence that satisfies a specified condition

    Opposite of the First method, the Last method will take the last element that matches your predicate:

    Returns the last element of a sequence that satisfies a specified condition

    I'm guessing that the TakeWhile is exiting early because their are no elements matching the condition when the iteration begins.

    If you want the range of elements between the first and last element from your example (including both the first and last item), then try this:

    var range = sortedDictionary.SkipWhile( x => !x.Equals( first ) )
      .TakeWhile( x => !x.Equals( last ) )
      .Concat( new SortedDictionary<string, string> { { last.Key, last.Value } } );
    

    Or you could just not over-think this like me and use a simpler approach using the Where method as in Jeff's example.