Click here to Skip to main content
Click here to Skip to main content

A 'Between' extension method for LINQ in C#.

By , 5 Jan 2012
 

Here's an example of a simple LINQ extension method for getting values that fall within a certain range from an IEnumerable collection. First, we order the TSource items using the given selector. Then we use Invoke on each item in the collection to determine if the selector's result is above the lowest value we want. If it isn't, we skip it with the SkipWhile method. Once we're at a point where we know the selector's result is at least as large as the lowest value we want, we start taking items with the TakeWhile method until the same Invoke returns a value larger than the largest item we want. Then we just stop and return. It's a one-liner.

Picture: Linq Between Extension Method

/// <summary>
/// Returns the values in a sequence whose resulting value of the specified 
/// selector is between the lowest and highest values given in the parameters.
/// </summary>
/// <typeparam name="TSource">
/// The type of elements in the sequence.
/// </typeparam>
/// <param name="source">
/// The IEnumerable object on which this method works.
/// </param>
/// <param name="selector">
/// The selector predicate used to attain the value.
/// </param>
/// <param name="lowest">
/// The lowest value of the selector that will appear in the new list.
/// </param>
/// <param name="highest">
/// The hightest value of the selector that will appear in the new list.
/// </param>
/// <returns>
/// An IEnumerable sequence of TSource whose selector values fall within the range 
/// of <paramref name="lowest"/> and <paramref name="highest"/>.
/// </returns>
public static IEnumerable<TSource> Between<TSource, TResult>
(
    this IEnumerable<TSource> source, Func<TSource, TResult> selector,
    TResult lowest, TResult highest
)
    where TResult : IComparable<TResult>
{
    return source.OrderBy(selector).
        SkipWhile(s => selector.Invoke(s).CompareTo(lowest) < 0).
        TakeWhile(s => selector.Invoke(s).CompareTo(highest) <= 0 );
}
 
/// <summary>
/// This is a simple test for the Between Linq extension method. We'll add a few
/// values to a list and select only those that are between a certain range.
/// When we're done, we should know the lowest and highest values contained
/// in the resulting values set.
/// </summary>
[TestMethod]
public void BetweenTest()
{
    var list = <a href="http://www.google.com/search?q=new+msdn.microsoft.com">new</a> List<double>();
    for (var i = 1; i <= 20; i++)
        list.Add(i);
    var fiveTo15 = list.Between(s => s, 5, 15);
    Assert.IsTrue(fiveTo15.Min() == 5);
    Assert.IsTrue(fiveTo15.Max() == 15);
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

qenn
Engineer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Question[My vote of 2] Binary Searchmemberfrblondin6 Jan '12 - 23:03 
Since yous list is sorted, have you ever considered using a binary search?
 
I wrote this some time ago that might be useful:
 
public enum ClosestPosition { Any, AtOrAfter }
 
public static class BinarySearch
{
    public static int BinarySearchClosestIndex<T>(this IList<T> list, Func<T, double> distanceFunc)
    {
        return list.BinarySearchClosestIndex(distanceFunc, ClosestPosition.Any);
    }
 
    public static int BinarySearchClosestIndex<T>(this IList<T> list, Func<T, double> distanceFunc, ClosestPosition position)
    {
        if (list.Count == 0)
            return -1;
        int min = 0;
        int max = list.Count - 1;
        while (min < max)
        {
            int mid = (max + min) / 2;
            T midItem = list[mid];
            double midDistance = distanceFunc(midItem);
            if (midDistance < 0)
                min = mid + 1;
            else if (midDistance > 0)
                max = mid - 1;
            else
                return mid;
        }
 
        // Necessary?
        // Make sure that the previous & next are not closer
        if (min > 0 && Math.Abs(distanceFunc(list[min - 1])) < Math.Abs(distanceFunc(list[min])))
            min--;
        else if (min < list.Count - 1 && Math.Abs(distanceFunc(list[min + 1])) < Math.Abs(distanceFunc(list[min])))
            min++;
 
        while (position == ClosestPosition.AtOrAfter && distanceFunc(list[min]) < 0)
        {
            min++;
            if (min >= list.Count)
                return -1;
        }
        return min;
    }
}

QuestionNo need to require OrderBy...memberMatt T Heffron6 Jan '12 - 13:53 
You don't need to order the IEnumerable<TSource> source, and, in fact, may require preserving the original order.
First define an generic extension method for the single value Between operation (I prefer the name IsBetween for the single value operation):
 
    /// <param name="value">the value to check</param>
    /// <param name="lowerBound">the lower bound of the inclusive range</param>
    /// <param name="upperBound">the upper bound of the inclusive range</param>
    /// <returns>true if value is inclusively between the bounds</returns>
    public static bool IsBetween<T>(this T value, T lowerBound, T upperBound) where T : IComparable<T>
    {
      return (lowerBound.CompareTo(value)) <= 0 && (value.CompareTo(upperBound)) <= 0;
    }
 
and then use it for the IEnumerable:
 
    // Same as yours but without OrderBy
    public static IEnumerable<TSource> Between<TSource, TResult>
      (this IEnumerable<TSource> source, 
       Func<TSource, TResult> selector,
       TResult lowest, TResult highest)
    where TResult : IComparable<TResult>
    {
      return source.Where(s => selector(s).IsBetween(lowest, highest));
    }
 
    // When no selector is required
    public static IEnumerable<TSource> Between<TSource>
      (this IEnumerable<TSource> source, TSource lowest, TSource highest)
    where TSource : IComparable<TSource>
    {
      return source.Where(s => s.IsBetween(lowest, highest));
    }
 
 
If the sequence needs to be ordered, it can be ordered after Between is determined.
This would be more efficient in most cases, as the Between operation is O(n) on the sequence and OrderBy would be approximately O(n log n) on the (reduced) sequence.
 
(Also, remember that source and selector need to be verified as non-null.)
SuggestionOff Topic - UserIDmvpthatraja6 Jan '12 - 1:46 
I suggest you to change your Codeproject UserID. Yes, Member 7658657 is just a random ID.
thatraja

My Dad had a Heart Attack on this day so don't...
Pompeyboy3 here
| Nobody remains a virgin, Life screws everyone Sigh | :sigh:

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 5 Jan 2012
Article Copyright 2012 by qenn
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid