按版本号对字符串列表进行排序

3
我有一个字符串列表。每个字符串都遵循模式"{Path} \UpdateTo{Version}-{Order}"。
我需要将列表排序,以便最低版本号位于顶部。在存在多个具有相同版本号的文件的情况下,会附加可选的订单参数。如果任何字符串中存在订单,则应出现在没有订单号的具有相同版本号的字符串上面。
例如,给定以下列表(请注意,项目是随机排序的):
var files = new List<string>() {
    @"C:\Migrations\UpdateTo1.2-2",
    @"C:\Migrations\UpdateTo1.5-2",
    @"C:\Migrations\UpdateTo1.2",
    @"C:\Migrations\UpdateTo1.4",
    @"C:\Migrations\UpdateTo1.1",
    @"C:\Migrations\UpdateTo1.5",
    @"C:\Migrations\UpdateTo1.2-1",
    @"C:\Migrations\UpdateTo1.5-1"
};

结果将是:
var files = new List<string>() {
    @"C:\Migrations\UpdateTo1.1",
    @"C:\Migrations\UpdateTo1.2-1",
    @"C:\Migrations\UpdateTo1.2-2",
    @"C:\Migrations\UpdateTo1.2",
    @"C:\Migrations\UpdateTo1.4",
    @"C:\Migrations\UpdateTo1.5-1",
    @"C:\Migrations\UpdateTo1.5-2",
    @"C:\Migrations\UpdateTo1.5"
}

我一直试着想出各种创意,但到目前为止我的尝试都是一团糟。如果有人能帮忙,我会非常感激。谢谢。


2
你可以给一些你尝试过的例子吗? - Samuel Slade
除非您的版本号高于9,.9等,否则您可以使用字母排序,否则您需要编写自己的简单比较器。 - dtech
我假设路径可能是混合的? - James Michael Hare
1
@dtech:需要自定义比较,因为“-{Order}”部分优先于没有此部分的版本。 - Austin Salonen
奥斯汀是正确的。如果没有{Order}参数,那么你可以按字母顺序排序。 - nfplee
5个回答

5
我使用了一个临时类来处理解析和比较,以获得所需的输出。 我已经包含了代码,使所有内容都符合您的要求,但是“临时”类所引入的可能更有价值,而不仅仅是路径(?)。

用法:

var sorted = files.Select(f => new UpdateTo(f))
    .OrderBy(u => u)
    .Select(u => u.Path)
    .ToArray();

这段代码:

class UpdateTo : IComparable<UpdateTo>
{
    public decimal Version { get; private set; }
    public int Order { get; private set; }
    public string Path { get; private set; }

    private const string Prefix = "UpdateTo";

    public UpdateTo(string path)
    {
        /* No error-checking here -- BEWARE!! */
        Path = path;

        string toParse = Path.Substring(Path.IndexOf(Prefix, StringComparison.InvariantCultureIgnoreCase) + Prefix.Length);
        var split = toParse.Split('-');

        Version = decimal.Parse(split[0]);
        Order = split.Length == 2 ? int.Parse(split[1]) : int.MaxValue;
    }

    public int CompareTo(UpdateTo other)
    {
        int versionCompare = Version.CompareTo(other.Version);
        return versionCompare == 0 ? Order.CompareTo(other.Order) : versionCompare;
    }
}

而且这个测试...

[Test]
public void ListSort()
{
    const string first = @"C:\Migrations\UpdateTo1.1";
    const string second = @"C:\Migrations\UpdateTo1.2-1";
    const string third = @"C:\Migrations\UpdateTo1.2-2";
    const string fourth = @"C:\Migrations\UpdateTo1.2";
    const string fifth = @"C:\Migrations\UpdateTo1.4";
    const string sixth = @"C:\Migrations\UpdateTo1.5-1";
    const string seventh = @"C:\Migrations\UpdateTo1.5-2";
    const string eighth = @"C:\Migrations\UpdateTo1.5";

    var files = new List<string>{third, seventh, fourth, fifth, first, eighth, second, sixth};

    var sorted = files.Select(f => new UpdateTo(f))
        .OrderBy(u => u)
        .Select(u => u.Path)
        .ToArray();

    Assert.AreEqual(first, sorted[0]);
    Assert.AreEqual(second, sorted[1]);
    Assert.AreEqual(third, sorted[2]);
    Assert.AreEqual(fourth, sorted[3]);
    Assert.AreEqual(fifth, sorted[4]);
    Assert.AreEqual(sixth, sorted[5]);
    Assert.AreEqual(seventh, sorted[6]);
    Assert.AreEqual(eighth, sorted[7]);
}

谢谢您的回答。这是我最喜欢的答案,因为它很简单易懂,而且我之前也尝试过类似的东西,但是失败惨了,哈哈。 - nfplee

3
files.Sort(delegate(string str1, string str2)
{
    var pattern = @"(?<version>\d.*?$)";
    var version1 = System.Text.RegularExpressions.Regex.Match(str1, pattern).Groups["version"].Value;
    var version2 = System.Text.RegularExpressions.Regex.Match(str2, pattern).Groups["version"].Value;

    // TODO: Implement your version comparison logic here 
    return string.Compare(version1, version2);
});

更新

一个示例比较逻辑实现如下:

files.Sort(delegate(string str1, string str2)
{
    var pattern = @"(?<version>\d.*?$)";
    var version1 = System.Text.RegularExpressions.Regex.Match(str1, pattern).Groups["version"].Value;
    var version2 = System.Text.RegularExpressions.Regex.Match(str2, pattern).Groups["version"].Value;

    if (version1 == version2) return 0;

    // version1 != version2

    var major1 = float.Parse(version1.Split('-')[0]);
    var major2 = float.Parse(version2.Split('-')[0]);

    if (major1 > major2) return 1; // version1 > version2
    if (major1 < major2) return -1; // version1 < version2

    // major1 = major2

    if (version1.Split('-').Length > version2.Split('-').Length) return -1;
    if (version1.Split('-').Length < version2.Split('-').Length) return 1;

    var minor1 = float.Parse(version1.Split('-')[1]);
    var minor2 = float.Parse(version2.Split('-')[1]);

    return Comparer<float>.Default.Compare(minor1, minor2);
});

谢谢你的回答。它完美地解决了我的问题,但是我已经接受了Austin的答案,因为我喜欢LINQ的方法。 - nfplee

1
你可以尝试以下方法:
// Warning! To keep this code clean I
// left out all error handling. Use at own risk.
//
// requires using System.Linq;
private int VersionSortingValue(string s)
{
    int res = 0;
    string[] items = s.Split('.', '-');
    if(items.Length != 3)
    {
        res = 1;
    }

    return (int.Parse(items[0]) << 1) + res;
}

// actual sorting:
var prefix = "UpdateTo";

Func<string, string> getVersion = 
    x => x.Substring(x.LastIndexOf(prefix) + prefix.Length);

files = files
    .OrderBy(x => VersionSortingValue(getVersion(x))
    .ThenBy(x => getVersion(x))
    .ToList();

如果版本号变大了,你应该考虑使用自然排序


请注意所需列表中第二项与此输出之间的差异。 - Austin Salonen
是的。改进版应该能够处理这个。 - Nuffin
还差一点,总是先按{Order}排序。我已经接受了奥斯汀的答案,但还是谢谢你。 - nfplee

1

这里是另一种使用LINQ的版本(可能更加简洁,但可读性较差):

int prefixLength = "UpdateTo".Length;

var sorted = from file in files
             let fileName = System.IO.Path.GetFileName(file)
             let versionString = fileName.Substring(prefixLength, fileName.Length - prefixLength).Replace('-', '.')
             let version = new Version(versionString)
             orderby version
             select file;

更新: 下面的改进版本考虑了订单值的特殊情况:

var sorted = from file in files
             let fileName = System.IO.Path.GetFileName(file)
             let versionString = fileName.Substring(prefixLength, fileName.Length - prefixLength).Replace('-', '.')
             let modified = versionString.IndexOf('.') == versionString.LastIndexOf('.') ? versionString + "." + Int32.MaxValue.ToString() : versionString
             let version = new Version(modified)
             orderby version
             select file;

谢谢你的回答,但它没有处理订单参数。不过把连字符改成小数点的想法很好。我猜当没有订单参数时,你可以添加一个非常高的最后一个数字。 - nfplee
抱歉。我没有仔细阅读有关订单号的要求。 - afrischke
使用现有的System.Version类进行排序是个好主意,@afrischke,这确实帮助我解决了一个类似的问题! - pwhe23

0

可以使用简单的LINQ排序和SemVer

# This example

var sortedFiles = files
    .OrderBy(v => Semver.SemVersion.Parse(v.Split("UpdateTo")[1]))
    .ToList();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接