0

I have a variable List< Tuple< DateTime, double>> myList.
Given a datetime, hope it returns the Tuple that precedes the datetime by using Linq.
For example, if "2013-Feb-08 21:34:00" is supplied, want to see the last Tuple in the list whose datetime is before this timestamp.

How do I do this with Linq?

Edit:
myList.Where(t => t.Item1 < timestamp).Last();
solved my issue. Which is better in terms of performance compared to
myList.TakeWhile(t => t.Item1 < timestamp).Last();

Chris
  • 259
  • 1
  • 4
  • 14
  • use [.OderByDescending](http://stackoverflow.com/questions/5344805/linq-orderby-descending-query) – spajce Feb 05 '13 at 06:37
  • the element I want might not be the first one in the orderby list. – Chris Feb 05 '13 at 06:38
  • By "last", do you mean the item with the highest index in the list, or the item with the highest `DateTime` value? – Guffa Feb 05 '13 at 06:39
  • each Tuple was added in temporal order, so the highest index would be the one closest to the supplied datetime. – Chris Feb 05 '13 at 06:40
  • 2
    Why are you using tuples? I think anything with human-readable property names will be better – Sergey Berezovskiy Feb 05 '13 at 06:41
  • 4
    To add with what @lazyberezovsky said, a [SortedDictionary](http://msdn.microsoft.com/en-us/library/f7fta44c.aspx) may be a much better data structure than a List too. – Scott Chamberlain Feb 05 '13 at 06:43
  • 1
    @AlvinWong: It looks like sorting *is* required - the way I understand the question, given an arbitrary key, the OP wants to find the latest entry with an earlier key. – Jon Skeet Feb 05 '13 at 06:46
  • 1
    How important is performance here? Is this a really large list? (The existing solutions are O(N) or worse, but we can easily get O(log N) – Jon Skeet Feb 05 '13 at 06:48
  • the list is populated in temporal order so no sorting is needed. The list is at most some millions and performance is not so much a concern. but the faster the better. – Chris Feb 05 '13 at 06:49
  • 2
    @Chris *where + last* cannot be better than *takewhile + last*, because where enumerates whole sequence – Sergey Berezovskiy Feb 05 '13 at 07:23

4 Answers4

2

With MoreLinq MaxBy (available from NuGet):

myList.Where(t => t.Item1 < timestamp).MaxBy(t => t.Item1);

Or (if items are sorted):

myList.TakeWhile(t => t.Item1 < timestamp).Last();

UPDATE (with binary search) write comparer:

public class MyComparer : IComparer<Tuple<DateTime, double>>
{
    public int Compare(Tuple<DateTime, double> x, Tuple<DateTime, double> y)
    {
        return x.Item1.CompareTo(y.Item1);
    }
}

Then search

   int index = myList.BinarySearch(new Tuple<DateTime, double>(timestamp, 0), 
                                   new MyComparer());

   if (index == 0)
      // there is no items before timestamp

   if (index > 0)
      result = myList[index - 1]; // your item is previous

   if (index < 0) // no tuple with date equal to timestamp
      var nearestIndex = ~index;
      if (nearestIndex > 0)
          result = myList[nearestIndex - 1];
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • @AlvinWong you can't use binary search with `IEnumerable` sequence (which also is not sorted). It enumerates all items, but does not use internal storage to store them all ordered. It simply takes one max item. – Sergey Berezovskiy Feb 05 '13 at 06:54
  • Oh it seems that my brain get confused by comments suggesting using `SortedDictionary`. Seems that the OP should really use it and write his own binary search algorithm. – Alvin Wong Feb 05 '13 at 06:56
  • @lazyberezovsky Got compile error: MaxBy is not defined in System.Collections.Generic.IEnumerable<>>. Any idea what's wrong? – Chris Feb 05 '13 at 06:59
  • @Chris you should add `MoreLinq` package from NuGet (btw consider second option if you have ordered sequence) – Sergey Berezovskiy Feb 05 '13 at 07:01
  • @Chris both solutions with LINQ have 2*O(N). Use last solution with binary search for speed O(log N) – Sergey Berezovskiy Feb 05 '13 at 07:21
1
var result = myList.OrderByDescending(t => t.Item1)
  .SkipWhile(t => t.Item1 > timestamp)
  .First();
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
1

To get the best performance, you should not use LINQ at all. A binary search gives the performance O(log n) intead of the O(n) that LINQ can offer.

Create a comparer for your type:

public class MyListComparer : IComparer<Tuple<DateTime, double>> {

  public int Compare(Tuple<DateTime, double> x, Tuple<DateTime, double> y) {
    return x.Item1.CompareTo(y.Item1);
  }

}

Use the comparer with the BinarySearch method:

int idx = myList.BinarySearch(new Tuple<DateTime, double>(new DateTime(2013,2,8,21,34,0), 0), new MyListComparer());
if (idx < 0) {
  idx = (~idx) - 1;
}
Tuple<DateTime, double> item = myList[idx];
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • @lazyberezovsky: The difference is that O(log n) is less than O(n), thus better performance. – Guffa Feb 05 '13 at 07:27
  • I asked about difference between my and your answers :) MyListComparer vs MyComparer – Sergey Berezovskiy Feb 05 '13 at 07:32
  • 1
    @lazyberezovsky: No, you thought that you asked about that. ;) There seems to be no difference between the implementations. – Guffa Feb 05 '13 at 07:44
  • Well, I think you did it without knowing about my update, thus +1 from me (but you forgot to check if idx > 0) :) – Sergey Berezovskiy Feb 05 '13 at 07:50
  • 1
    @lazyberezovsky: I assumed that there will actually be a possible match. *Every* LINQ solution presented here will throw an exception if there isn't any match. – Guffa Feb 05 '13 at 08:00
0

myList.Where(t => t.Item1 < datetime).OrderByDescending(t => t.Item1).Last();

Dzmitry Martavoi
  • 6,867
  • 6
  • 38
  • 59