Mark Richman
Mark Richman

Reputation: 29710

Finding filenames containing version numbers in C#

I have file names with version numbers embedded, similar to NuGet's naming scheme. Examples:

A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.3.4.5.dll
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
X.Y.Z.7.8.9.0.zip
X.Y.Z.7.8.9.1.zip

Given a pattern "A.B.C.1.2.3", how do I find all those files and directories that match, regardless of version number? I support both major.minor.build.revision and major.minor.build schemes.

That is, given "A.B.C.1.2.3", return the following list:

A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
A.B.C.3.4.5.dll

Bonus points for determining which file name has the highest version.

Upvotes: 2

Views: 1180

Answers (7)

jdweng
jdweng

Reputation: 34419

Try this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] input = new string[] {
                "A.B.C.1.2.3.4.zip",
                "A.B.C.1.2.3.5.zip",
                "A.B.C.3.4.5.dll",
                "A.B.C.1.2.3.6.zip",
                "A.B.C.1.2.3.dll",
                "X.Y.Z.7.8.9.0.zip",
                "X.Y.Z.7.8.9.1.zip"
            };

            var parsed = input.Select(x => x.Split(new char[] { '.' }))
                .Select(y => new
                {
                    name = string.Join(".", new string[] { y[0], y[1], y[2] }),
                    ext = y[y.Count() - 1],
                    major = int.Parse(y[3]),
                    minor = int.Parse(y[4]),
                    build = int.Parse(y[5]),
                    revision = y.Count() == 7 ? (int?)null : int.Parse(y[6])
                }).ToList();

            var results = parsed.Where(x => (x.major >= 1) && (x.major <= 3)).ToList();

            var dict = parsed.GroupBy(x => x.name, y => y)
                .ToDictionary(x => x.Key, y => y.ToList());

            var abc = dict["A.B.C"];
        }
    }
}
​
​

Upvotes: 1

Cory
Cory

Reputation: 1802

Credits to jdwweng for his answer as well as 31eee384 for his thoughts. This answer basically combines both ideas.

First, you can create a custom class like so:

class CustomFile
{
    public string FileName { get; private set; }
    public Version FileVersion { get; private set; }

    public CustomFile(string file)
    {
        var split = file.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

        int versionIndex;
        int temp;

        for (int i = split.Length - 2; i >= 0; i--)
        {
            if (!Int32.TryParse(split[i], out temp))
            {
                versionIndex = i+1;
                break;
            }
        }

        FileName = string.Join(".", split, 0, versionIndex);
        FileVersion = Version.Parse(string.Join(".", split, versionIndex, split.Length - versionIndex - 1));
    }
}

Using it to parse the filename, you can then filter based on it.

string[] input = new string[] {
    "A.B.C.D.1.2.3.4.zip",
    "A.B.C.1.2.3.5.zip",
    "A.B.C.3.4.5.dll",
    "A.B.C.1.2.3.6.zip",
    "A.B.C.1.2.3.dll",
    "X.Y.Z.7.8.9.0.zip",
    "X.Y.Z.7.8.9.1.zip"
};

var parsed = input.Select(x => new CustomFile(x));
var results = parsed
    .Where(cf => cf.FileName == "A.B.C")
    .OrderByDescending(cf=>cf.FileVersion)
    .ToList();

In this example, the first element would have the highest version.

Upvotes: 2

laphbyte
laphbyte

Reputation: 32

First of all I think you can use Version class for comparison. I believe function below can get you versions starting with certain name. It matches the starting name then performs a non greedy search until a dot and digit followed by 2 or 3 dot digit pair and any character after.

public static List<Version> GetLibraryVersions(List<string> files, string Name)
{
    string regexPattern = String.Format(@"\A{0}(?:.*?)(?:\.)(\d+(?:\.\d+){{2,3}})(?:\.)(?:.*)\Z", Regex.Escape(Name));
    Regex regex = new Regex(regexPattern);
    return files.Where(f => regex.Match(f).Success).
            Select(f => new Version(regex.Match(f).Groups[1].Value)).ToList();
}

Upvotes: 0

w.b
w.b

Reputation: 11228

This works using only LINQ, assuming the file name itself doesn't end with a digit:

List<string> names = new List<string> { "A.B.C.1.2.3.4.zip",
                                        "A.B.C.1.2.3.5.zip",
                                        "A.B.C.3.4.5.dll",
                                        "A.B.C.1.2.3.6.zip" ,
                                        "A.B.C.1.2.3.dll",
                                        "X.Y.Z.7.8.9.0.zip",
                                        "X.Y.Z.7.8.9.1.zip" };

var groupedFileNames = names.GroupBy(file => new string(Path.GetFileNameWithoutExtension(file)
                                                         .Reverse()
                                                         .SkipWhile(c => Char.IsDigit(c) || c == '.')
                                                         .Reverse().ToArray()));

foreach (var g in groupedFileNames)
{
    Console.WriteLine(g.Key);
    foreach (var file in g)
        Console.WriteLine("    " + file);
}

Upvotes: 0

Artsiom Marzavin
Artsiom Marzavin

Reputation: 176

Try to use regular expression like in example below

    var firstPart = Console.ReadLine();

    var names = new List<string>
    {
        "A.B.C.1.2.3.4.zip",
        "A.B.C.1.2.3.5.zip",
        "A.B.C.1.2.3.6.zip",
        "A.B.C.1.2.3.dll",
        "X.Y.Z.7.8.9.0.zip",
        "X.Y.Z.7.8.9.1.zip"
    };

    var versionRegexp = new Regex("^" + firstPart + "\\.([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.)?[\\w\\d]+$");

    foreach (var name in names)
    {
        if (versionRegexp.IsMatch(name))
        {
            Console.WriteLine(name);
            foreach (Group group in versionRegexp.Match(name).Groups)
            {
                Console.WriteLine("Index {0}: {1}", group.Index, group.Value);
            }
        }
    }

    Console.ReadKey();

Upvotes: 0

Juan
Juan

Reputation: 5050

you can use new Version() to compare versions like this:

List<string> fileNames = new List<string>();
            fileNnames.AddRange(new[] {
                "A.B.C.1.2.3.4.zip",
                "A.B.C.1.2.3.5.zip",
                "A.B.C.3.4.5.dll",
                "A.B.C.1.2.3.6.zip",
                "A.B.C.1.2.3.dll",
                "X.Y.Z.7.8.9.0.zip",
                "X.Y.Z.7.8.9.1.zip" });

            string filter = "a.b.c";

            var files = fileNames
                //Filter the filenames that start with your filter
                .Where(f => f
                      .StartsWith(filter, StringComparison.InvariantCultureIgnoreCase)
                      )
               //retrieve the version number and create a new version element to order by
                .OrderBy(f =>
                    new Version( 
                        f.Substring(filter.Length + 1, f.Length - filter.Length - 5)
                        )
                );

Results

Upvotes: 0

31eee384
31eee384

Reputation: 2803

If you know the filenames end with the version, you could Split the filename string on .. Then iterate backwards from the end (skipping the extension) and stop on the first non-numeric string. (TryParse is probably good for this.) Then you can string.Join the remaining parts and you have the package name.

Do this for the search term to find the package name, then each file in the directory, and you can compare just the package names.

Upvotes: 2

Related Questions