4

In my c# MVC4 application, I have a list of strings. Each odd element in the list is a datetime. The even element before each of them is a unique identifier.

For instance: [0] is A7M0066 [1] is 2007-01-06 06:24:00.000

I want to process the list and add the top 5 most recent pairs based on datetime in the odd elements to another list of strings. Im not sure where to begin but Im assuming it will require LINQ.

Using Keith's answer below and testing I realized that what I actually need has changed. Using his approach, I get 5 results that are most recent but 3 of them have the same id. I need the end result to all have a unique id. Basically I need to keep 1 of the 3 entries that are the same and continue processing until all 5 are unique.

HendPro12
  • 1,094
  • 3
  • 17
  • 50
  • I like how you can't type "What have you tried?" but you can type "What have you tried so far?", brilliant!!! – Yuriy Faktorovich Apr 25 '13 at 20:57
  • It defentently wont *require* LINQ, but LINQ could make it easier (I'm no expert with linq) – Cemafor Apr 25 '13 at 20:58
  • Nothing requires LINQ to Objects, it is basically sugar candy over implementing your own Enumerators. – Yuriy Faktorovich Apr 25 '13 at 21:00
  • 5
    I *highly* suggest you not use lists like this. Create a list of pair objects instead of having two indexes occupied by a single logical object. It will make your life *much* easier. – Servy Apr 25 '13 at 21:00
  • 1
    You really have a mess here. If you can, I would change this to be a Dictionary instead of List and relying on order to determine if its a date or a guid. I am assuming by unique identifier you mean guid. – Scott Adams Apr 25 '13 at 21:00
  • 1
    You're doing it wrong. Prevent coming in the situation where you need to process such list. Instead, process a list where each element in the list is a pair of a guid and a datetime. – Steven Apr 25 '13 at 21:02

5 Answers5

8

I think you are going about this all wrong. Since these "pairs" are related, lets make a class for them.

public class DateTimePairs(){ 

   public string Id {get;set;}

   public DateTime Date {get;set;}
}

Now, lets make a list of those:

var MyList = new List<DateTimePairs>();
MyList = 'Get your values for the list

Finally, returning what you want is going to be really really simple

return MyList.OrderByDescending(x=>x.Date).Take(5);
Tommy
  • 39,592
  • 10
  • 90
  • 121
8
var items =
    list.Where((x, i) => i%2 == 0)
        .Zip(list.Where((x, i) => i%2 == 1), (f, s) => new {Id = f, Date = s})
        .OrderByDescending(x => x.Date)
        .DistinctBy(x => x.Id, null)   // see note later in this answer....
        .Take(5)
        .ToList();

this will zip the even elements with the odd elements and turn them into an Object with Id and Date as fields. Then sorts by the date, and takes the 5 latest

so you can then iterate over each object and do as you wish.

ie, an example use to print out the 5 to the console...

items.ForEach(x => Console.WriteLine("{0} {1}", x.Id, x.Date));

for doing unique IDs with the greatest date..... refer LINQ: Distinct values

and use the extension method shown by Mr Skeet ... except I've improved it a bit, my version is :-

public static class DistinctLinq
    {
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector,
            IEqualityComparer<TKey> comparer)
        {
            var knownKeys = new HashSet<TKey>(comparer);
            return source.Where(element => knownKeys.Add(keySelector(element)));
        }
    }
Community
  • 1
  • 1
Keith Nicholas
  • 43,549
  • 15
  • 93
  • 156
  • @Keith Nicholas This is a great answer but after testing this isnt giving me what Ive realized I actually need. I get 5 results that are most recent but 3 of them have the same id. I need the end result to all have a unique id. Basically I need to keep 1 of the 3 entries that are the same and continue processing until all 5 are unique. – HendPro12 Apr 25 '13 at 21:53
  • @HendPro12 updated my answer with doing uniqueness, which is a bit of a pain in the ass with straight linq, so you need an extension method. – Keith Nicholas Apr 25 '13 at 22:18
  • @Keith Nicholas Worked perfectly. Thanks! – HendPro12 Apr 25 '13 at 23:20
5

You can do this:

var results =
    (from i in Enumerable.Range(0, myList.Length / 2)
     let Guid = Guid.Parse(myList[i * 2])
     let Date = DateTime.Parse(myList[i * 2 + 1])
     orderby Date descending
     return new { Guid, Date })
    .Take(5);
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
1

Expanding upon Keith's answer, consider the following method, which parses the Date itself. I add this not because you need it for simple ordering (your date format will allow that) - but because you might need it if you start doing other kinds of comparison based on Dates.

var dt = DateTime.MinValue;
var items = list.Where((x, i) => i % 2 == 0)
.Zip(list.Where((x, i) => i % 2 == 1), (f, s) => new { 
    Id = f, 
    Date = DateTime.TryParse(s, out dt) ? (DateTime?)dt : null
})
.OrderByDescending(x => x.Date);

Because of the .TryParse() ternary, if the expected odd element does not parse into a valid DateTime object, you get back null. If it does, you get back the parsed DateTime. The resultant list can now be used type-safely to compare for bad data (look for nulls) or do other kinds of DateTime comparison / sorting.

0

Similar to the Zip solution, I'd recommend creating and using the more generally useful "partition" extension method (see Split List into Sublists with LINQ). With a "Partition" extension method returning IEnumerable<List<T>>, for example, implementation is pretty self documenting as:

var items = list.Partition(2).Take(5).ToList();

Obviously, it would be better to then transform this into a stronger type, but even as is, you'd be able to extract the values (assuming 0 <= n <= items.Count) using:

 var id = items[n].FirstOrDefault();
 var date = items[n].ElementAtOrDefault(1);
Community
  • 1
  • 1
T.Tobler
  • 41
  • 1
  • 4