4

I am very new to c# programming, so if my question is trivial I apologize. I am running some c# code where I need to iterate over some files in a folder and am using:

foreach (string f in Directory.GetFiles(@"C:\temp\GeneralStats"))
{

However, I would like to have these files read in a specific order according to file name. My file names are of the below format.

generalstats_2012_11_1.csv
generalstats_2012_11_2.csv
generalstats_2012_11_3.csv
.....

When my code reads in the files it starts with generalstats_2012_11_1.csv but then jumps directly to generalstats_2012_11_10.csv, instead of generalstats_2012_11_2.csv.

I have tried searching the web for answers but have been unable to find any. I of course ordered the files according to date (in the name) in the specific folder, but the code does not acknowledge that at all. Can anyone help me - is there some order function in c# that I have missed?

Ravi Y
  • 4,296
  • 2
  • 27
  • 38
  • Search for `human sort` or `natural sort order` – abatishchev Feb 26 '13 at 08:20
  • Use two digits for your months and days! Or have fun implementing a [natural sort](http://www.interact-sw.co.uk/iangblog/2007/12/13/natural-sorting) – alldayremix Feb 26 '13 at 08:27
  • Possible duplicate of [Natural Sort Order in C#](http://stackoverflow.com/questions/248603/natural-sort-order-in-c-sharp). (The question isn't great but the top answer should be a solution for you - it basically shows how to order the files using the same underlying function that Windows does.) – Rawling Feb 26 '13 at 08:31
  • Seems a bit odd to go to the trouble of writing dates in YMD order and then use single digits for the days... I'd fix that rather than piddle about with natural sort order. – Matthew Watson Feb 26 '13 at 08:40
  • Thanks guys, I really appreciate your help and very fast replies. What worked for me was the answer from Ivan G :-) – Lene Jung Kjær Feb 26 '13 at 08:50
  • 1
    @LeneJungKjær - that's what the "tick" option next to Ivan's answer is for - so you can indicate that your question is answered. – Damien_The_Unbeliever Feb 26 '13 at 08:57
  • Ok, Sorry - I am new to this. – Lene Jung Kjær Feb 26 '13 at 10:52

2 Answers2

2

You can use this:

        var paths = Directory.GetFiles(@"C:\temp\GeneralStats")
            .OrderBy(path => 
                Convert.ToInt32(path.Split('_', '.')[1]) * 10000 + 
                Convert.ToInt32(path.Split('_', '.')[2]) * 100 + 
                Convert.ToInt32(path.Split('_', '.')[3]));

        foreach (string path in paths)
        {
        }

This gets the date parts from the file name and creates an integer out of them, for example 2012_11_10 from file name is 2012110 as integer, 2012_11_1 is 20121101 etc. These numbers are used for sorting.

Another variation of the above approach would be:

        var paths = Directory.GetFiles(@"C:\temp\GeneralStats")
            .OrderBy(path =>
                Convert.ToInt32(
                    String.Concat(
                        path.Split('_', '.')
                            .Skip(1)
                            .Take(3)
                            .Select(num => num.PadLeft(2, '0'))
                            .ToArray())
                )
            );

It also parses numbers from file names and creates integers used for sorting.

Instead of Directory.GetFiles method you can use Directory.EnumerateFiles (but this is available only in .NET 4 and higher versions).

Ivan Golović
  • 8,732
  • 3
  • 25
  • 31
2

If you want to order by the date behind the file-name you have to cast it to one, otherwise the order is alphabetically(therefore "10" is before "2").

You can use Enumerable.OrderBy in this linq-query:

var files = Directory.EnumerateFiles(@"C:\temp\GeneralStats", "*.csv")
    .Select(fn => new
    {
        Path = fn,
        Split = Path.GetFileNameWithoutExtension(fn).Split('_')
    })
    .Where(x => x.Split.Length == 4)
    .Select(x => new
    {
        x.Path,
        Date = new DateTime(int.Parse(x.Split[1]), int.Parse(x.Split[2]), int.Parse(x.Split[3]))
    })
    .OrderBy(x => x.Date)
    .Select(x => x.Path);

First i'm selecting an anonymous type with some useful properties from the Path class like GetFileNameWithoutExtension. That will make the rest of the query more efficient and more readable. The Where is to prevent errors when there are files which don't have the date part in the file-name. The next select parses the DateTime from the tokens derived from string.Split. The last three parts contain the year, month and day. The OrderBy will order by the date. Finally i select again the full-path since you originally wanted to order the files.

You can enumerate the result with a foreach or use e.g. ToList if you want to persist it:

foreach (string f in files)
{
    // ...
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Thanks I really appreciate it - I couldn't quite get that to work but the below answer from Ivan G solved it for me. – Lene Jung Kjær Feb 26 '13 at 08:51
  • @LeneJungKjær: Maybe you are on .NET 3.5, `EnumerateFiles` requires .NET framework 4, then simply replace it with `GetFiles` which reads all files into memory first. The rest works also on 3.5. – Tim Schmelter Feb 26 '13 at 08:53