Sum of TimeSpans in C# - c#

I have a collection of objects that include a TimeSpan variable:
MyObject
{
TimeSpan TheDuration { get; set; }
}
I want to use LINQ to sum those times.
Of course, (from r in MyCollection select r.TheDuration).Sum(); doesn't work!
I'm thinking of changing the datatype of TheDuration to an int and then summing it and converting the sum to a TimeSpan. That will be messy because each TheDuration in my collection is used in as a timespan somewhere else.
Any suggestion on this summation?

Unfortunately, there isn't a an overload of Sum that accepts an IEnumerable<TimeSpan>. Additionally, there's no current way of specifying operator-based generic constraints for type-parameters, so even though TimeSpan is "natively" summable, that fact can't be picked up easily by generic code.
One option would be to, as you say, sum up an integral-type equivalent to the timespan instead, and then turn that sum into a TimeSpan again. The ideal property for this is TimeSpan.Ticks, which round-trips accurately. But it's not necessary to change the property-type on your class at all; you can just project:
var totalSpan = new TimeSpan(myCollection.Sum(r => r.TheDuration.Ticks));
Alternatively, if you want to stick to the TimeSpan's + operator to do the summing, you can use the Aggregate operator:
var totalSpan = myCollection.Aggregate
(TimeSpan.Zero,
(sumSoFar, nextMyObject) => sumSoFar + nextMyObject.TheDuration);

This works well (code based on Ani's answer)
public static class StatisticExtensions
{
public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> selector)
{
return source.Select(selector).Aggregate(TimeSpan.Zero, (t1, t2) => t1 + t2);
}
}
Usage :
If Periods is a list of objects with a Duration property
TimeSpan total = Periods.Sum(s => s.Duration)

I believe this is the cleanest LINQ extension:
public static class LinqExtensions
{
public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> func)
{
return new TimeSpan(source.Sum(item => func(item).Ticks));
}
}
Usage is the same:
TimeSpan total = Periods.Sum(s => s.Duration)

Here's what I tried and it worked:
System.Collections.Generic.List<MyObject> collection = new List<MyObject>();
MyObject mb = new MyObject();
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
var sum = (from r in collection select r.TheDuration.Ticks).Sum();
Console.WriteLine( sum.ToString());
//here we have new timespan that is sum of all time spans
TimeSpan sumedup = new TimeSpan(sum);
public class MyObject
{
public TimeSpan TheDuration { get; set; }
}

I put this in a class to add an extension method to a collection of timespans:
public static class Extensions:
{
public static TimeSpan TotalTime(this IEnumerable<TimeSpan> TheCollection)
{
int i = 0;
int TotalSeconds = 0;
var ArrayDuration = TheCollection.ToArray();
for (i = 0; i < ArrayDuration.Length; i++)
{
TotalSeconds = (int)(ArrayDuration[i].TotalSeconds) + TotalSeconds;
}
return TimeSpan.FromSeconds(TotalSeconds);
}
}
So now, I can write TotalDuration = (my LINQ query that returns a collection of timespan).TotalTime();
Voila!

This works for both a collection, and a property within a collection;
void Main()
{
var periods = new[] {
new TimeSpan(0, 10, 0),
new TimeSpan(0, 10, 0),
new TimeSpan(0, 10, 0),
};
TimeSpan total = periods.Sum();
TimeSpan total2 = periods.Sum(p => p);
Debug.WriteLine(total);
Debug.WriteLine(total2);
// output: 00:30:00
// output: 00:30:00
}
public static class LinqExtensions
{
public static TimeSpan Sum(this IEnumerable<TimeSpan> timeSpanCollection)
{
return timeSpanCollection.Sum(s => s);
}
public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> func)
{
return new TimeSpan(source.Sum(item => func(item).Ticks));
}
}

I created extension method for IEnumerable<TimeSpan>
public static class TimeSpanExtensions
{
public static TimeSpan Sum(this IEnumerable<TimeSpan> source)
{
return source.Aggregate(TimeSpan.Zero, (subtotal, time) => subtotal.Add(time));
}
}
Thanks to that I can use:
IEnumerable<TimeSpan> times = new List<TimeSpan>();
TimeSpan totalTime = times.Sum();

You can use .Aggregate rather than .Sum, and pass it a timespan-summing function that you write, like this:
TimeSpan AddTimeSpans(TimeSpan a, TimeSpan b)
{
return a + b;
}

Once you understand that timespans can't be summed and know to use Ticks, it seems to me that this extension to just convert a long into a timespan looks more linq-ee. I believe it lets the reader have a more readable view of the operation:
var times = new[] { new TimeSpan(0, 10, 0), new TimeSpan(0, 20, 0), new TimeSpan(0, 30, 0) };
times.Sum(p => p.Ticks)
.ToTimeSpan(); // output: 01:00:00
Here is the one extension:
public static class LongExtensions
{
public static TimeSpan ToTimeSpan(this long ticks)
=> new TimeSpan(ticks);
}

Related

Cast a function as variable

Let be the following function Measure for measurement of execution time for some functnion method
public static TimeSpan Measure(Action method)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
method.Invoke();
stopwatch.Stop();
return stopwatch.Elapsed;
}
This method is launched by the following line of code
var ExecTime = ComplexityCounters.Measure(() => method(int a, int b));
As we can see it's a very huge recall of Measure, so, let's try to short it by the following function
public static TimeSpan ExecTime (Action method)
{
var ExecutionTime = Measure(() => method.Invoke());
return ExecutionTime;
}
But
var ExecTime = ExecTime(method(int a, int b));
gives the error c# cannot convert from 'void' to 'System.Action'. How to fix it ? Or may be is there a way to compose those two function in one ?
here full example
public static TimeSpan Measure(Action method)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
method.Invoke();
stopwatch.Stop();
return stopwatch.Elapsed;
}
public static TimeSpan ExecTime(Action method)
{
var ExecutionTime = Measure(() => method.Invoke());
return ExecutionTime;
}
private static void Main(string[] args)
{
var spentTime = ExecTime(() => Sum(1,2));
}
public static int Sum(int t1, int t2)
{
return t1 + t2;
}
actually you are declaring your variable ExecTime same as your method name ExecTime

C# Method to Compare DateTime Fields Specified During Execution?

My project has many objects with date fields, and I often need to select everything where one such field is within a date range.
For example:
public class Contract
{
public DateTime SignDate { get; set; }
public DateTime ReleaseDate { get; set; }
}
public class PersonalCheck
{
public DateTime SignDate { get; set; }
public DateTime ProcessDate { get; set; }
public DateTime VoidDate { get; set; }
}
If I only cared about SignDate, it would be easy. I would declare an Interface...
public interface IObjectWithSignDate
{
DateTime SignDate { get; set; }
}
...change my other objects to inherit from it, then create a method like this:
public static IQueryable<T> SignedWithin<T>(this IQueryable<T> items, DateTime start, DateTime end) where T : IObjectWithSignDate
{
return items.Where(q => q.SignDate >= start && q.SignDate <= end);
}
How can I avoid rewriting this function for ReleaseDate, ProcessDate, VoidDate, etc.? Can I make this method take in an IQueryable of any object and a variable telling it which date field to run this selector against?
Note this would have to be able to a) execute in LinqToEntities to run against a database and b) not add a lot of overhead (as I'm fearful reflection might do)
Simple but specific
You can add an extension method like this:
public static class DateTimeExtensions
{
public static bool IsBetween(this DateTime thisDateTime, DateTime start, DateTime end)
{
return thisDateTime >= start && thisDateTime <= end;
}
}
which you can unit test in isolation.
Then you can use this on whichever DateTime field you want to check. For example:
var start = new DateTime(2017, 1, 1);
var end = new DateTime(2017, 12, 31, 23, 59, 59);
IList<Contract> contracts = new List<Contract>(); // or anything enumerable
var contractsSignedBetween = contracts.Where(x => x.SignDate.IsBetween(start, end));
var contractsReleasedBetween = contracts.Where(x => x.ReleaseDate.IsBetween(start, end));
(Notice how I set the start datetime to have 00:00:00 time, and the end datetime to have 23:59:59 time [feel free to include milliseconds as well], so that times within the last day are included.)
Making that reusable
If you find yourself needing to do that a lot, you could do an extension for that
public static class EnumerableContractsExtensions
{
public static IEnumerable<Contract> SignedBetween(this IEnumerable<Contract> contracts, DateTime start, DateTime end)
{
return contracts.Where(x => x.SignDate.IsBetween(start, end));
}
}
and use it like this
var contractsSignedBetween = contracts.SignedBetween(start, end);
which could also be unit tested in isolation.
More flexible but specific
Use an expression to say which date you want...
public static class EnumerableContractsExtensions
{
public static IEnumerable<Contract> Between(this IEnumerable<Contract> contracts, Func<Contract, DateTime> selector, DateTime start, DateTime end)
{
return contracts.Where(x => selector(x).IsBetween(start, end));
}
}
and then do:
var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);
var contractsReleasedBetween = contracts.Between(x => x.ReleaseDate, start, end);
Flexible and generic
Go the whole hog and do it generically (although you can't make it an extension method since it's generic):
public static class EnumerableExtensions
{
public static IEnumerable<T> Between<T>(IEnumerable<T> items, Func<T, DateTime> selector, DateTime start, DateTime end)
{
return items.Where(x => selector(x).IsBetween(start, end));
}
}
again, this is testable in its own right, and can be used like this:
IList<Contract> contracts = new List<Contract>();
IList<PersonalCheck> personalChecks = new List<PersonalCheck>();
var contractsSignedBetween = EnumerableExtensions.Between(contracts, x => x.SignDate, start, end);
var checksSignedBetween = EnumerableExtensions.Between(personalChecks, x => x.SignDate, start, end);
Making it IQueryable
To make this work as IQueryable the approach needs to shift to an expression tree, since LINQ to Entities does not know how to translate a method into SQL.
public static IQueryable<TSource> Between<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
TKey low,
TKey high)
where TKey : IComparable<TKey>
{
Expression key = keySelector.Body;
Expression lowerBound = Expression.LessThanOrEqual(Expression.Constant(low), key);
Expression upperBound = Expression.LessThanOrEqual(key, Expression.Constant(high));
Expression and = Expression.AndAlso(lowerBound, upperBound);
Expression<Func<TSource, bool>> lambda =
Expression.Lambda<Func<TSource, bool>>(and, keySelector.Parameters);
return source.Where(lambda);
}
which would still be used like this:
var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);
And this works for things other than DateTimes as well. Hope this helps.

Generic function declaration in C#

I'm trying to create some stats about method call duration in a library.
Instead of wrapping each method call to the library with lines to time and track it, I want to create a generic action and function which does these recurring steps.
E.g. for methods that don't return a value, I have created this:
private readonly Action<string, Action> timedAction = (name, action) =>
{
var sw = Stopwatch.StartNew();
action.Invoke();
trackDuration(name, sw.ElapsedMilliseconds);
};
That can be invoked with timedAction("methodname", () => lib.methodname()).
I want to do something similar for methods that return a value, but obviously Action can't be used for that purpose, since it can't return a value.
Is there a way to do this with a generic Func, so I don't have to declare one for each combination of library method parameters?
You can use a generic function like this:
private static TValue FuncHandler<TValue>(string name, Func<TValue> func)
{
var sw = Stopwatch.StartNew();
var result = func();
trackDuration(name, sw.ElapsedMilliseconds);
return result;
}
Call it like this:
var result = FuncHandler("name", () => MyMethod(param1));
Indeed, AOP will buy you more than this sort of tediousness:
https://dotnetfiddle.net/5PLCmM
// Needs to be replicated after Func<T1, TResult>, Func<T1, T2, TResult>, etc, for all the functions arities you'll want to wrap with it
public static TResult Timed<T1, /*T2, etc*/TResult>(out long duration, Func<T1, /*T2, etc*/TResult> func, T1 arg1/*T2 arg2, etc*/)
{
//start timing
var t0 = DateTime.Now;
var result = func(arg1/*, arg2, etc*/);
//end timing
duration = (long)DateTime.Now.Subtract(t0).TotalMilliseconds;
return result;
}
public int Factorial(int n)
{
return n > 0 ? n * Factorial(n - 1) : 1;
}
public int Fibonacci(int n)
{
return n > 1 ? Fibonacci(n - 2) + Fibonacci(n - 1) : n;
}
public static void Main()
{
var program = new Program();
long duration;
var _12bang = Timed(out duration, program.Factorial, 12);
Console.WriteLine("{0}! = {1} in {2} ms", 12, _12bang, duration);
var fib31 = Timed(out duration, program.Fibonacci, 31);
Console.WriteLine("Fib {0} = {1} in {2} ms", 31, fib31, duration);
}
(yes, I know about StopWatch; was just too lazy to put it in there)
'Hope this helps.
In your case AOP will be more tedious. Here is my solution which works:
Class1.cs
using System;
namespace ClassLibrary1
{
public class Class1
{
public void WriteNoParam()
{
Console.WriteLine("void");
}
public void WriteWithParam(string name)
{
Console.WriteLine("My name is: " + name);
}
}
}
Program.cs
using System;
namespace ConsoleApplication2
{
using System.Diagnostics;
using System.Reflection;
using ClassLibrary1;
class Program
{
static void Main(string[] args)
{
var prReflection = new TestReflection<Class1>();
var elapsed = prReflection.TestFunc(new Class1(), #"C:\Users\yasir\Documents\visual studio 2013\Projects\ConsoleApplication2\ClassLibrary1\bin\Debug\ClassLibrary1.dll", "WriteNoParam", new string[0]);
Console.WriteLine("Elapsed time for non parameter method: "+elapsed);
elapsed = prReflection.TestFunc(new Class1(), #"C:\Users\yasir\Documents\visual studio 2013\Projects\ConsoleApplication2\ClassLibrary1\bin\Debug\ClassLibrary1.dll", "WriteWithParam", new[]{"Yasir"});
Console.WriteLine("Elapsed time for parameter method: " + elapsed);
Console.ReadLine();
}
}
public class TestReflection<T> where T: class
{
public Func<T, string, string, string[], long> TestFunc = (arg1, s, s2, arr) =>
{
var assembly = Assembly.LoadFile(s);
var type = assembly.GetType(typeof (T).ToString());
long executionTime;
if (type != null)
{
var methodInfo = type.GetMethod(s2);
if (methodInfo != null)
{
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
var stopWatch = new Stopwatch();
if (parameters.Length == 0)
{
// This works fine
stopWatch.Start();
methodInfo.Invoke(classInstance, null);
return stopWatch.ElapsedMilliseconds;
}
stopWatch.Start();
methodInfo.Invoke(classInstance, arr); ;
return stopWatch.ElapsedMilliseconds;
}
}
return 0;
};
}
}
I have run in debug mode to test if the console is able to output in milliseconds and it works.
If you don't run in debug, execution will be really fast and console will output 0.
I know this question already has an answer, but I think this solution can be interesting, if you don't want to have to pass the name, yourself, each time, you could do this:
(It was a lot inspired by #selami' answer.)
private MemberInfo GetMethodName<T>(Expression<T> expression)
{
Expression body = expression.Body;
// You might want to complete this
// depending on which expression you want to use
return ((MethodCallExpression)body).Method.Name;
}
// Works for both Action and Func
private object TimedMethodInvoke<T>(Expression<T> funcExpression)
{
var sw = Stopwatch.StartNew();
var result = ((Delegate)(object)funcExpression.Compile()).DynamicInvoke();
trackDuration(GetMethodName(funcExpression), sw.ElapsedMilliseconds);
return result;
}
And your final methods:
public void TimeMethod(Expression<Action> actionExpression)
{
TimedMethodInvoke(actionExpression);
}
public TValue TimeMethod<TValue>(Expression<Func<TValue>> funcExpression)
{
return (TValue)TimedMethodInvoke(funcExpression);
}
I didn't run a benchmark over this solution, but I guess you should encounter a little performance hit, but if you don't mind about that and want to avoid to type the name each time, this could help.

OutOfMemoryException with big collection and OrderBy?

i have a collection trends with about 30kk elements. When i am trying execute following code in linqpad
trends.Take(count).Dump();
it works ok.
But if i add sort:
trends.OrderByDescending(x => x.Item2).Take(count).Dump();
I get System.OutOfMemoryException
What i am doing wrong?
OrderByDescending (or OrderBy) materializes the whole sequence when you try to fetch the first element - it has to, as otherwise you can't possibly know the first element. It has to make a copy of the sequence (typically just a bunch of references, of course) in order to sort, so if the original sequence is an in-memory collection, you end up with two copies of it. Presumably you don't have enough memory for that.
You don't have to sort the whole collection just take top count elements from it. Here is a solution for this https://codereview.stackexchange.com/a/9777/11651.
The key point from this answer is It doesn't require all items to be kept in memory(for sorting)
Again from comments of the answer in the link:
The idea is: You can find the Max(or Min) item of a List in O(n) time. if you extend this idea to m item(5 in the question), you can get top(or buttom) m items faster then sorting the list(just in one pass on the list + the cost of keeping 5 sorted items)
Here is another extension method that may work better than the original LINQ (e.g. it shouldn't blow up for a small number of selected items). Like L.B.'s solution it should be O(n) and doesn't keep all items in memory:
public static class Enumerables
{
public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
{
var qCount = 0;
var queue = new SortedList<TV, List<T>>(count, comparer);
foreach (var val in value)
{
var currTv = selector(val);
if (qCount >= count && comparer.Compare(currTv, queue.Keys[0]) <= 0) continue;
if (qCount == count)
{
var list = queue.Values[0];
if (list.Count == 1)
queue.RemoveAt(0);
else
list.RemoveAt(0);
qCount--;
}
if (queue.ContainsKey(currTv))
queue[currTv].Add(val);
else
queue.Add(currTv, new List<T> {val});
qCount++;
}
return queue.SelectMany(kvp => kvp.Value);
}
public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
{
return value.TopN(selector, count, Comparer<TV>.Default);
}
public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
{
return value.TopN(selector, count, new ReverseComparer<TV>(comparer));
}
public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
{
return value.BottomN(selector, count, Comparer<TV>.Default);
}
}
// Helper class
public class ReverseComparer<T> : IComparer<T>
{
private readonly IComparer<T> _comparer;
public int Compare(T x, T y)
{
return -1*_comparer.Compare(x, y);
}
public ReverseComparer()
: this(Comparer<T>.Default)
{ }
public ReverseComparer(IComparer<T> comparer)
{
if (comparer == null) throw new ArgumentNullException("comparer");
_comparer = comparer;
}
}
And some tests:
[TestFixture]
public class EnumerablesTests
{
[Test]
public void TestTopN()
{
var input = new[] { 1, 2, 8, 3, 6 };
var output = input.TopN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(8));
Assert.IsTrue(output.Contains(6));
Assert.IsTrue(output.Contains(3));
}
[Test]
public void TestBottomN()
{
var input = new[] { 1, 2, 8, 3, 6 };
var output = input.BottomN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(1));
Assert.IsTrue(output.Contains(2));
Assert.IsTrue(output.Contains(3));
}
[Test]
public void TestTopNDupes()
{
var input = new[] { 1, 2, 8, 8, 3, 6 };
var output = input.TopN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(8));
Assert.IsTrue(output.Contains(6));
Assert.IsFalse(output.Contains(3));
}
[Test]
public void TestBottomNDupes()
{
var input = new[] { 1, 1, 2, 8, 3, 6 };
var output = input.BottomN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(1));
Assert.IsTrue(output.Contains(2));
Assert.IsFalse(output.Contains(3));
}
}

How can I get a subset of a list based on a Datetime key in C#?

I have a list of elements describing events that took place at some time, the time being represented as a Datetime property 'StartTime' on the objects. I now wish to extract a subset of these events containing those elements that are placed in an interval / between two DateTime instances A,B such that StartTime >= A && StartTime <= B. Currently this is accomplished by a simple Linq query, but since I have to run a lot of queries extracting small fractions of the list, its quite inefficient.
Had hoped that the standard SortedList class had some sort of subset functionality over the key, but that doesnt seem to be the case. Any ideas if this can be archived relatively simple with existing framework classes, or would I have to make some sort of custom binary search based on a SortedList?
If possible, I would keep the instances in an array, sorted by the StartTime, and then call BinarySearch to determine the indexes in the array the bounds end at.
Then, given that, you can easily access a subset based on date range quickly.
I've worked up a generic class which can help you with this as well:
public class BinarySearcher<T>
{
// Possibly passed to the call to BinarySort.
private class ComparisonComparer : Comparer<T>
{
Comparison<T> comparison;
internal static IComparer<T> Create(Comparison<T> comparison)
{
// If comparison is null, return the default comparer for T.
if (comparison == null)
{
// Return the default.
return Comparer<T>.Default;
}
// Return a new implementation.
return new ComparisonComparer(comparison);
}
private ComparisonComparer(Comparison<T> comparison)
{
this.comparison = comparison;
}
public override int Compare(T x, T y)
{
return comparison(x, y);
}
}
// The elements.
T[] elements;
// The IComparable implementation.
IComparer<T> comparer;
// Do not assume sorting.
public BinarySearcher(IEnumerable<T> elements) :
this(elements, false, (IComparer<T>) null) { }
// Use default comparer.
public BinarySearcher(IEnumerable<T> elements, bool sorted) :
this(elements, sorted, (IComparer<T>) null) { }
// Assume no sorting.
public BinarySearcher(IEnumerable<T> elements,
Comparison<T> comparer) :
this(elements, false,
ComparisonComparer.Create(comparer)) { }
// Convert to IComparable<T>.
public BinarySearcher(IEnumerable<T> elements, bool sorted,
Comparison<T> comparer) :
this(elements, sorted,
ComparisonComparer.Create(comparer)) { }
// No sorting.
public BinarySearcher(IEnumerable<T> elements,
IComparer<T> comparer) :
this(elements, false, comparer) { }
// Convert to array.
public BinarySearcher(IEnumerable<T> elements, bool sorted,
IComparer<T> comparer) :
this(elements.ToArray(), sorted, comparer) { }
// Assume no sorting.
public BinarySearcher(T[] elements) : this(elements, false) { }
// Pass null for default comparer.
public BinarySearcher(T[] elements, bool sorted) :
this(elements, sorted, (IComparer<T>) null) { }
// Assume not sorted.
public BinarySearcher(T[] elements, Comparison<T> comparer) :
this(elements, false, ComparisonComparer.Create(comparer)) { }
// Create IComparable<T> from Comparable<T>.
public BinarySearcher(T[] elements, bool sorted,
Comparison<T> comparer) :
this(elements, sorted, ComparisonComparer.Create(comparer)) { }
// Assume the elements are not sorted.
public BinarySearcher(T[] elements, IComparer<T> comparer) :
this(elements, false, comparer) { }
public BinarySearcher(T[] elements, bool sorted,
IComparer<T> comparer)
{
// If the comparer is null, create the default one.
if (comparer == null)
{
// Set to the default one.
comparer = Comparer<T>.Default;
}
// Set the comparer.
this.comparer = comparer;
// Set the elements. If they are sorted already, don't bother,
// otherwise, sort.
if (!sorted)
{
// Sort.
Array.Sort(elements, this.comparer);
}
// Set the elements.
this.elements = elements;
}
public IEnumerable<T> Between(T from, T to)
{
// Find the index for the beginning.
int index = Array.BinarySearch(this.elements, from, comparer);
// Was the item found?
bool found = (index >= 0);
// If the item was not found, take the bitwise
// compliment to find out where it would be.
if (!found)
{
// Take the bitwise compliment.
index = ~index;
}
// If the item was found, cycle backwards from
// the index while there are elements that are the same.
if (found)
{
// Cycle backwards.
for (; index >= 0 &&
comparer.Compare(from, elements[index]) == 0;
--index) ;
// Add one to the index, since this is on the element
// that didn't match the comparison.
index++;
}
// Go forward now.
for ( ; index < elements.Length; index++)
{
// Return while the comparison is true.
if (comparer.Compare(elements[index], to) <= 0)
{
// Return the element.
yield return elements[index];
}
else
{
// Break
yield break;
}
}
}
}
Most of what is here are helpers to initialize the class, which will need a method of comparing two items (IComparer<T> and Comparable<T> are taken here). The class also lets you take in a raw array which is already sorted, in case you want to add/remove/update elements in the proper positions yourself later on (without having to resort the array all the time).
The meat of the class is in the call to Between. It takes a from value, which will be passed to the static BinarySearch method on the Array class, passing the IComparer implementation that this instance uses (it assumes defaults if none are passed in).
If the value doesn't exist, it finds out where it would exist in the array, and then cycles backwards in case there are multiple elements with the same value in the array.
Then, it cycles forward, returning the items in the enumeration while the current element is less than or equal to the to value.
In your particular situation, assuming you had a class like this:
public class Task
{
public string Name;
public DateTime StartTime;
}
You could do the following:
// Create tasks.
Task[] tasks =
{
new Task() { Name = "Task 1", StartTime = new DateTime(2009, 02, 18) },
new Task() { Name = "Task 2", StartTime = new DateTime(2009, 02, 16) },
new Task() { Name = "Task 3", StartTime = new DateTime(2009, 02, 12) },
new Task() { Name = "Task 4", StartTime = new DateTime(2009, 02, 11) },
new Task() { Name = "Task 5", StartTime = new DateTime(2009, 02, 10) },
new Task() { Name = "Task 6", StartTime = new DateTime(2009, 02, 01) },
new Task() { Name = "Task 7", StartTime = new DateTime(2009, 02, 09) }
};
// Now create the indexer.
BinarySearcher<Task> searcher = new BinarySearcher<Task>(tasks,
(x, y) => Comparer<DateTime>.Default.Compare(x.StartTime, y.StartTime));
foreach (Task t in searcher.Between(
new Task() { StartTime = new DateTime(2009, 02, 13) },
new Task() { StartTime = new DateTime(2009, 02, 10) }))
{
// Write.
Console.WriteLine(t);
}
Have you checked out Array.Find using a custom predicate?

Categories

Resources