I have a program that find all .csv reports file in a directory. This reports are stored in a List
List<string> _items = new List<string>();
this reports are named like this:
“month year.csv” (example: “Avgust
2010.csv”)
I would like to sort my reports by month.
Months are in this order:
januar, februar, marec, april, maj,
junij, julij, avgust, september,
oktober, november, december
How can I do that?
Br, Wolfy
The most compact complete solution would be something like:
The above assigns this list back to itself. If you were going to do so, then you could gain in performance for relatively little increase in code complexity by sorting in-place:
(If you are running this on a system running in a Slovene locale, then you could use the
CurrentCulturerather than the constructedsloveneCultureInfoabove).Further improvements can be made by using a comparer class rather than a lambda, and indeed the comparison itself could be more efficient than the above, but I wouldn’t worry about this unless the sort proved to be a bottle-neck.
Edit: A break down on just what works here.
There are two convenient ways of sorting a list (strictly, one way of sorting a list and another of sorting any IEnumerable or IQueryable).
OrderBytakes a parameter that computes a sort key, and returns anIOrderedEnumerable<T>(orIOrderedQueryable<T>from now on I’m going to ignore the fact that this can be done onIQueryableas well, as the principle is the same). This class is one that in most ways acts like anIEnumerable<T>that is sorted by the key, with the only difference being that if you do a subsequentThenByon it, it keeps ordered by the first key, and hence lets you have multi-stage orderings.So, in the code:
SomeMethodwill return values that can be sorted, and on the basis of this, x will be given anIOrderedEnumerable<T>sorted accordingly. For example ifSomeMethodtook a string and returned the length of the string, then we could use it to sort a list of strings by length.If we were then going to iterate through the _items, then this is our job done. If we wanted to have a new list then we could call
Tolist()on the results, and that would return such a list.The other approach, that only works on lists, is to call
Sort(). There is a parameterless form that just sorts according to the default comparison of the type in question (if there is one), a form that takes anIComparer<T>object (more involved than necessary in most cases, though sometimes very useful) and a form that takes either a delegate or a lambda (strictly it takes a delegate, but we can use a lambda to create that delegate).This delegate must receive two values of the type of the list’s items and return a number that is negative if the first should be sorted earlier than the latter, zero if they are equivalent and a positive number otherwise.
Sort()changes the list its done on. This has an efficiency gain, but is clearly disastrous if you need to hold onto the original sort order too.Okay, so far we’ve got two ways to sort a list. Both of them need a way to turn your file names into something that can be sorted on.
Since the file names relate to dates, and since dates are already sortable, the most sensible approach is to obtain those dates.
We can’t create a date directly from the file name, because Avgust 2010 isn’t a date, just a month-year value and there isn’t a month year class in the BCL (there may be in other libraries but let’s not gild lilies).
However, every month has a first day, and therefore we can create a valid date from “01 ” concatenated with the file name. So our first step is to say we will act on
"01 " + fnwherefnis the file name.This gives us strings in the form of e.g.
"01 Avgust 2010.csv". Examining how date parsing works, we know we can useddfor the two-digit date,MMMMfor the full name of the month in the relevant language (Slovenian in this case) andyyyyfor the full year. We just need to add on\.\c\s\vto mean that it will be followed with a “.csv” that we don’t parse, and we’re set. We can hence turn the file name into the first of its month withDateTime.ParseExact("01 " + fn, @"dd MMMM yyyy\.\c\s\v", new Culture("sl-SI"))where fn is the file name. To make this a lambda expression we usefn => DateTime.ParseExact("01 " + fn, @"dd MMMM yyyy\.\c\s\v", new Culture("sl-SI")).The first approach is now complete, we call
_items.OrderBy(fn => DateTime.ParseExact("01 " + fn, @"dd MMMM yyyy\.\c\s\v", new Culture("sl-SI")))and depend uponOrderByknowing how to sortDateTimevalues.For the second approach we need to take two values and compare them ourselves. We can do this by calling
CompareTo()on theDateTimes returned byParseExact.Finally, we move the
CultureInfoconstruction out to initialise a variable calledsloveneCultureInfoto avoid wasteful calls to the multiple creations of essentially the same object.