I have a list of trips that I need to sort by the time at which they start. Certain trips do not have a time, and these are stored in the database as 12:00am. I am currently sorting the list as follows:
trips = trips.OrderBy(t => t.PickupTime);
The problem is that I want this to sort such that the 12:00am trips are put at the bottom of the list. Changing the database is not an option. Is there an elegant, clean way to tell the OrderBy to treat a time of 12:00am as being the highest value (probably use DateTime.MaxValue)?
I am aware that I could just use trips.Sort(delegate(..));, however I'd like to avoid this if possible. Also, I am not able to filter out these trips and then add them back on to the end. It wasn't received happily in code review.
You can use OrderBy with ThenBy:
trips = trips.OrderBy(t => t.TimeOfDay == (new DateTime(2014, 1, 1, 0, 0, 0)).TimeOfDay))
.ThenBy(t => t.PickupTime);
It will sort by is time 00:00:00 first, and because false comes before true in that kind of sorts, you'll get non-midnight values first. ThenBy will sort non-midnight values using standard DateTime comparison.
You could just use the conditional operator in your order by
var midnight = new DateTime(0).TimeOfDay;
trips = trips.OrderBy(t=> t.PickupTime.TimeOfDay == midnight ?
DateTime.MaxValue :
t.PickupTime);
I would suggest writing your own comparer that inherits IComparer and use that in the OrderBy in place of the default comparer. If you give me what type PickupTime is I can give you a more specific example, but here is a string comparer I have that ensures empty strings are last:
public class EmptyStringsAreLast : IComparer<string>
{
public int Compare(string x, string y)
{
if (String.IsNullOrEmpty(y) && !String.IsNullOrEmpty(x))
{
return -1;
}
else if (!String.IsNullOrEmpty(y) && String.IsNullOrEmpty(x))
{
return 1;
}
else
{
return String.Compare(x, y);
}
}
}
You would use this like:
//assuming Person has a Name property that is a string
IEnumerable<Person> bunchOfPeople = getPeople();
bunchOfPeople.OrderBy(p => p.Name, new EmptyStringsAreLast());
Related
Can anyone explain what the difference is between:
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.OrderBy(sort2 => sort2.InvoiceOwner.FirstName)
.OrderBy(sort3 => sort3.InvoiceID);
and
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.ThenBy(sort2 => sort2.InvoiceOwner.FirstName)
.ThenBy(sort3 => sort3.InvoiceID);
Which is the correct approach if I wish to order by 3 items of data?
You should definitely use ThenBy rather than multiple OrderBy calls.
I would suggest this:
tmp = invoices.InvoiceCollection
.OrderBy(o => o.InvoiceOwner.LastName)
.ThenBy(o => o.InvoiceOwner.FirstName)
.ThenBy(o => o.InvoiceID);
Note how you can use the same name each time. This is also equivalent to:
tmp = from o in invoices.InvoiceCollection
orderby o.InvoiceOwner.LastName,
o.InvoiceOwner.FirstName,
o.InvoiceID
select o;
If you call OrderBy multiple times, it will effectively reorder the sequence completely three times... so the final call will effectively be the dominant one. You can (in LINQ to Objects) write
foo.OrderBy(x).OrderBy(y).OrderBy(z)
which would be equivalent to
foo.OrderBy(z).ThenBy(y).ThenBy(x)
as the sort order is stable, but you absolutely shouldn't:
It's hard to read
It doesn't perform well (because it reorders the whole sequence)
It may well not work in other providers (e.g. LINQ to SQL)
It's basically not how OrderBy was designed to be used.
The point of OrderBy is to provide the "most important" ordering projection; then use ThenBy (repeatedly) to specify secondary, tertiary etc ordering projections.
Effectively, think of it this way: OrderBy(...).ThenBy(...).ThenBy(...) allows you to build a single composite comparison for any two objects, and then sort the sequence once using that composite comparison. That's almost certainly what you want.
I found this distinction annoying in trying to build queries in a generic manner, so I made a little helper to produce OrderBy/ThenBy in the proper order, for as many sorts as you like.
public class EFSortHelper
{
public static EFSortHelper<TModel> Create<TModel>(IQueryable<T> query)
{
return new EFSortHelper<TModel>(query);
}
}
public class EFSortHelper<TModel> : EFSortHelper
{
protected IQueryable<TModel> unsorted;
protected IOrderedQueryable<TModel> sorted;
public EFSortHelper(IQueryable<TModel> unsorted)
{
this.unsorted = unsorted;
}
public void SortBy<TCol>(Expression<Func<TModel, TCol>> sort, bool isDesc = false)
{
if (sorted == null)
{
sorted = isDesc ? unsorted.OrderByDescending(sort) : unsorted.OrderBy(sort);
unsorted = null;
}
else
{
sorted = isDesc ? sorted.ThenByDescending(sort) : sorted.ThenBy(sort)
}
}
public IOrderedQueryable<TModel> Sorted
{
get
{
return sorted;
}
}
}
There are a lot of ways you might use this depending on your use case, but if you were for example passed a list of sort columns and directions as strings and bools, you could loop over them and use them in a switch like:
var query = db.People.AsNoTracking();
var sortHelper = EFSortHelper.Create(query);
foreach(var sort in sorts)
{
switch(sort.ColumnName)
{
case "Id":
sortHelper.SortBy(p => p.Id, sort.IsDesc);
break;
case "Name":
sortHelper.SortBy(p => p.Name, sort.IsDesc);
break;
// etc
}
}
var sortedQuery = sortHelper.Sorted;
The result in sortedQuery is sorted in the desired order, instead of resorting over and over as the other answer here cautions.
if you want to sort more than one field then go for ThenBy:
like this
list.OrderBy(personLast => person.LastName)
.ThenBy(personFirst => person.FirstName)
Yes, you should never use multiple OrderBy if you are playing with multiple keys.
ThenBy is safer bet since it will perform after OrderBy.
Can anyone explain what the difference is between:
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.OrderBy(sort2 => sort2.InvoiceOwner.FirstName)
.OrderBy(sort3 => sort3.InvoiceID);
and
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.ThenBy(sort2 => sort2.InvoiceOwner.FirstName)
.ThenBy(sort3 => sort3.InvoiceID);
Which is the correct approach if I wish to order by 3 items of data?
You should definitely use ThenBy rather than multiple OrderBy calls.
I would suggest this:
tmp = invoices.InvoiceCollection
.OrderBy(o => o.InvoiceOwner.LastName)
.ThenBy(o => o.InvoiceOwner.FirstName)
.ThenBy(o => o.InvoiceID);
Note how you can use the same name each time. This is also equivalent to:
tmp = from o in invoices.InvoiceCollection
orderby o.InvoiceOwner.LastName,
o.InvoiceOwner.FirstName,
o.InvoiceID
select o;
If you call OrderBy multiple times, it will effectively reorder the sequence completely three times... so the final call will effectively be the dominant one. You can (in LINQ to Objects) write
foo.OrderBy(x).OrderBy(y).OrderBy(z)
which would be equivalent to
foo.OrderBy(z).ThenBy(y).ThenBy(x)
as the sort order is stable, but you absolutely shouldn't:
It's hard to read
It doesn't perform well (because it reorders the whole sequence)
It may well not work in other providers (e.g. LINQ to SQL)
It's basically not how OrderBy was designed to be used.
The point of OrderBy is to provide the "most important" ordering projection; then use ThenBy (repeatedly) to specify secondary, tertiary etc ordering projections.
Effectively, think of it this way: OrderBy(...).ThenBy(...).ThenBy(...) allows you to build a single composite comparison for any two objects, and then sort the sequence once using that composite comparison. That's almost certainly what you want.
I found this distinction annoying in trying to build queries in a generic manner, so I made a little helper to produce OrderBy/ThenBy in the proper order, for as many sorts as you like.
public class EFSortHelper
{
public static EFSortHelper<TModel> Create<TModel>(IQueryable<T> query)
{
return new EFSortHelper<TModel>(query);
}
}
public class EFSortHelper<TModel> : EFSortHelper
{
protected IQueryable<TModel> unsorted;
protected IOrderedQueryable<TModel> sorted;
public EFSortHelper(IQueryable<TModel> unsorted)
{
this.unsorted = unsorted;
}
public void SortBy<TCol>(Expression<Func<TModel, TCol>> sort, bool isDesc = false)
{
if (sorted == null)
{
sorted = isDesc ? unsorted.OrderByDescending(sort) : unsorted.OrderBy(sort);
unsorted = null;
}
else
{
sorted = isDesc ? sorted.ThenByDescending(sort) : sorted.ThenBy(sort)
}
}
public IOrderedQueryable<TModel> Sorted
{
get
{
return sorted;
}
}
}
There are a lot of ways you might use this depending on your use case, but if you were for example passed a list of sort columns and directions as strings and bools, you could loop over them and use them in a switch like:
var query = db.People.AsNoTracking();
var sortHelper = EFSortHelper.Create(query);
foreach(var sort in sorts)
{
switch(sort.ColumnName)
{
case "Id":
sortHelper.SortBy(p => p.Id, sort.IsDesc);
break;
case "Name":
sortHelper.SortBy(p => p.Name, sort.IsDesc);
break;
// etc
}
}
var sortedQuery = sortHelper.Sorted;
The result in sortedQuery is sorted in the desired order, instead of resorting over and over as the other answer here cautions.
if you want to sort more than one field then go for ThenBy:
like this
list.OrderBy(personLast => person.LastName)
.ThenBy(personFirst => person.FirstName)
Yes, you should never use multiple OrderBy if you are playing with multiple keys.
ThenBy is safer bet since it will perform after OrderBy.
Say I have the following:
public Class BooClass
{
public int field1;
public double field2;
public DateTime field3;
}
public List<BooClass> booList;
So for example how do I get the element with the earliest time in field3 using booList.Find()
Edit Apologies, I meant to make all the fields public for simplicity of the example. I know can do it in linq, I wondered if there is a simple single line condition for the Find method.
F# has handy minBy and maxBy operators, which I like to implement as C# extension methods, since the Linq library omits them. It's a bit of work, but only a bit, and it allows you to avoid complex expressions such as
var earliest = booList.First(b => b.Field3 == booList.Min(e => e.Field3));
Instead, you can type this:
var earliest = booList.MinBy(b => b.Field3);
A simple implementation:
static T MinBy<T, C>(this IEnumerable<T> sequence, Func<T, C> keySelector)
{
bool first = true;
T result = default(T);
C minKey = default(C);
IComparer<C> comparer = Comparer<C>.Default; //or you can pass this in as a parameter
foreach (var item in sequence)
{
if (first)
{
result = item;
minKey = keySelector.Invoke(item);
first = false;
continue;
}
C key = keySelector.Invoke(item);
if (comparer.Compare(key, minKey) < 0)
{
result = item;
minKey = key;
}
}
return result;
}
This is also somewhat more efficient than the complex expression at the top, since MinBy iterates the sequence exactly once, while the expression iterates more than once and less than or equal to twice. And, of course, sorting and then taking the first item requires sorting, which is O(n log n), while this is just O(n).
As noted by Saeed Amiri, this approach doesn't work if you are relying on Linq to SQL or any other IQueryable<> provider. (More precisely, it works inefficiently because it pulls the objects from the database and works on them locally.) For a solution that doesn't do this, see Saeed's answer.
You could also make an extension method based on that approach, but as I am on my phone at the moment I'll leave the implementation as the proverbial "exercise for the reader."
You'll need to expose field3 through through a public property (we'll call it Field3), but you could use this:
var earliest = booList.First(b => b.Field3 == booList.Min(e => e.Field3));
Take a look at Enumerable.First and Enumerable.Min
NOTE: That this has a time complexity of O(n^2) (quadratic time) because it is traversing the list via Min each iteration. A large enough collection will see serious performance issues compared to Saeed Amiri's answer, which runs in O(n) (linear time).
Use OrderBy Then get the first element
var result = booList.OrderBy(p => p.field3).FirstOrDefault();
The O(n) approach is as follows. First find min date (for field3), then find first object with this min date:
var minDate = booList.Min(x=>x.field3);
var item = booList.First(x=>x.field3 == minDate);
Just make your property public.
As far as I can tell, there is no way to retrieve the BooClass object with the minimal date by just using List<T>.Find. Of course you can do this:
void Main()
{
List<BooClass> booList = new List<BooClass> {
new BooClass { field3 = DateTime.MaxValue},
new BooClass { field3 = DateTime.Now },
new BooClass { field3 = DateTime.MinValue }};
var pred = GetPredicate(booList);
var result = booList.Find(pred);
}
public Predicate<BooClass> GetPredicate(List<BooClass> boos)
{
var minDate = boos.Min(boo => boo.field3);
return bc => bc.field3 == minDate;
}
(which - just like Saeed's solution - also has O(n) time complexity), but I guess that would be considered cheating...
If you don't want to define a MinBy method, you can use aggregate like so:
booList.Aggregate((currMin, test) => currMin < test ? currMin : test);
To support empty lists, seed the aggregate with null, like so:
booList.Aggregate(null, (currMin, test) => null == currMin || currMin > test ? test : currMin);
This solution is O(n)
I have a set of results coming back from a Linq to SQL query. Each result has a Name and a SeriesId. The SeriesId can be any value from 1 to N,
So the results might initially come out of the database like this (i.e. any order):
FundA1
FundA6
FundA4
FundC6
FundC3
FundC4
FundB2
FundB7
FundB8
FundB6
I need to get these ordered first by Name, and then by SeriesId but I need to show SeriesId == 6 first, then the rest in any order.
So for example, I need
**FundA6**
FundA1
FundA4
**FundB6**
FundB2
FundB7
FundB8
**FundC6**
FundC3
FundC4
I know it's possible for me to order by Name and then SeriesId by doing this:
return queryable.OrderBy(f => f.Name).ThenBy(s => s.SeriesId));
but this will order the SeriesId by the lowest value first. Is there a way for me to override this default functionality by specifying that it should order by SeriesId starting at 6 rather than 1?
Try this:
return queryable.OrderBy(f => f.Name)
.ThenBy(f => f.SeriesId == 6 ? 0 : 1)
.ThenBy(s => s.SeriesId));
That relies on "false" ordering earlier than "true" - I think it will work... it would in LINQ to Objects, at least.
return queryable
.OrderBy(f => f.Name)
.ThenBy(f => f.SerialId == 6)
.ThenBy(f => f.SeriesId);
Create your own comparer, and give it as a second parameter to OrderBy, ot ThenBy.
The way user OrderBy, you rely on default comparer, that compares strings normally. But you can create your own, that will always return "6" first.
PS. Yes this won`t work directly on LINQ2SQL. But, since, you are loading all the values anyway. You can first load them, and then sort in memory.
Here`s an example:
class Sample
{
string[] strings = new[]{ "123","123456", "12345"};
public void SampleMethod()
{
var res = strings.AsEnumerable().OrderBy(s => s.Length, new MyComparer());
}
class MyComparer : IComparer<int>
{
public int Compare(int x, int y)
{
if (x == 6) return -1;
return x - y;
}
}
}
.AsEnumerable() is needed in order for LINQ2SQL to load the data into memory.
my code
I need to grab the bottom element (most recent) entry into my database and see if it was over 1 hour ago, the type of ObservationTime is DateTime. I keep getting an error saying that "Sequence contains more than one element" for my var mWeathers. I think it has to do with the way I'm orderby descending but i cant figure it out. Thanks
[OperationContract]
public bool LeastOneHour()
{
DataClassesDBDataContext db = new DataClassesDBDataContext();
var mWeathers = (from weathertable in db.WeatherTables
orderby weathertable.ObservationTime descending
select weathertable.ObservationTime).Single();
DateTime lastTime = Convert.ToDateTime(mWeathers).AddHours(1);
if ( lastTime <= DateTime.Now)
return true;
else
return false;
}
As well as the other (perfectly valid) answers you've already been given, you may want to consider:
DateTime lastTime = db.WeatherTables.Max(table => table.ObservationTime);
or
DateTime? lastTime = db.WeatherTables.Max<DateTime?>(t => t.ObservationTime);
The latter form may cope with an empty table (where the first won't). It would be fine in LINQ to Objects, but I always hesitate to say that something will work in LINQ to SQL without trying it first :)
Since your query returns more than one element and you want to take the first element from those results, use First() instead of Single():
var mWeathers = (from weathertable in db.WeatherTables
orderby weathertable.ObservationTime descending
select weathertable.ObservationTime).First();
Or if there's a chance that the query will produce no results, you should use FirstOrDefault():
var mWeathers = (from weathertable in db.WeatherTables
orderby weathertable.ObservationTime descending
select weathertable.ObservationTime).FirstOrDefault();
And if you're not against making some other changes, you could make your code cleaner and less verbose:
var mWeathers = db.WeatherTables
.OrderByDescending(w => w.ObservationTime)
.First(); // or FirstOrDefault()
return Convert.ToDateTime(mWeathers).AddHours(1) <= DateTime.Now;
Single() says there must be one element in the result, and if there are more than one, throw an exception. What you're likely looking for is First(), or FirstOrDefault() if there's a chance there will be no results at all (in which case null is returned)
If you can be guaranteed that there will always be an element, try this:
DateTime lastDate = db.WeatherTables.OrderByDescending (w => w.ObservationTime).Select(w => w.ObservationTime).First();
return lastDate.AddHours(1) <= DateTime.Now;
Otherwise something like
DateTime? lastDate = db.WeatherTables.OrderByDescending (w => w.ObservationTime).Select(w => w.ObservationTime).FirstOrDefault();
return lastDate.HasValue ? lastDate.AddHours(1) <= DateTime.Now : false;
Assuming of course ObservationDate is declared as DateTime?