【问题标题】:Sorting a list<class> alphanumerically in c#在c#中按字母数字对列表<class>进行排序
【发布时间】:2020-07-06 18:36:31
【问题描述】:

我有一个 list&lt;job&gt;称为 _joblist,它是在表单加载时从反序列化的 json 填充的,作业是一个类:

     public class Job
        {
            public string Jobname { get; set; }
            public string State { get; set; }
            public string City { get; set; }
            public string Status = "●";
            public int COT { get; set; }
            public string Customer { get; set; }
        }

我对默认 list.sort 的问题是它按“ASCIIbetically”排序,我希望它专门根据 public string Customer 变量按字母数字排序, 例如预期输出:

job1
job2
job3
job11
job21

实际输出:

job1
job11
job2
job21
job3

关于类似的问题,我看到人们建议使用 LINQ 和 list.orderby,我对此的问题是我看到的所有示例都是针对 list&lt;string&gt;string[],因为我相对较新c# 我不确定如何在这个特定示例中执行此操作。如果问题不清楚,或者我对任何术语有误,我深表歉意,就像我说我对此比较陌生,如果有要求,很乐意提供澄清,提前致谢。

【问题讨论】:

  • job1 job2 job3 all 客户将在前缀中有“工作”?或者这只是一个例子?
  • 它应该如何知道嵌入在字符串中的特殊整数值?要么通过属性对其进行解释并对其进行排序,要么编写一个自定义比较器类型来理解编码并根据您的规范对值进行排序。
  • 1job2job 怎么样?
  • @hanan 我仅使用单词 job 作为示例,重点是我正在寻找一个包含数字和字母的字符串,即 job1 等,但按字母数字排序。
  • stackoverflow.com/questions/3716831/… 但正如你所说,无论如何这对你没有帮助。

标签: c# winforms linq natural-sort


【解决方案1】:

Here's a generic comparer 通过将字符串拆分为数字和字符串来对字符串进行排序,然后将字符串与字符串以及数字与数字(作为数字,而不是字符串)进行排序,这应该适用于任何类型的数字混合和字符串,无论多长。

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.Collections;
using System.Text.RegularExpressions;
                    
public class Program
{
    static void Main()
    {
        var arr = new[] { "job0", "job1", "job11", "job2", "z0", "z1", "z11", "z2", "0", "1", "11", "111", "2", "21", "a0b11", "a0b111", "a0b2", "a0b20" };

        var tmp = arr.Select(name => (orig: name, parts: GetParts(Regex.Match(name, @"^([a-zA-Z ]+)(?:(\d+)([a-zA-Z ]+))*(\d+)?$|^(\d+)(?:([a-zA-Z ]+)(\d+))*([a-zA-Z ]+)?$")))).ToList();
        tmp.Sort(new AlphaNumericalComparer());

        foreach(var item in tmp.Select(x => x.orig)) Console.Write(item + ", ");
    }

    static object GetObject(string s) => Regex.IsMatch(s, @"^\d+$") ? (object)Convert.ToInt32(s) : s;

    static IEnumerable<object> GetParts(Match m) =>
        char.IsDigit(m.Groups.Cast<Group>().Skip(1).First(m => m.Success).Value[0]) 
            ? Enumerable.Repeat((object)"", 1).Concat(m.Groups.Cast<Group>().Skip(1).Where(m => m.Success).Select(m => GetObject(m.Value)).ToList())
            : m.Groups.Cast<Group>().Skip(1).Where(m => m.Success).Select(m => GetObject(m.Value)).ToList();

    class AlphaNumericalComparer : IComparer<(string name, IEnumerable<object> parts)>
    {
        public int Compare((string name, IEnumerable<object> parts) left, (string name, IEnumerable<object> parts) right)
        {
            foreach(var lr in left.parts.Cast<IComparable>().Zip(right.parts.Cast<IComparable>()))
            {
                var cmp = lr.Item1.CompareTo(lr.Item2);
                if(cmp != 0) return cmp;
            }

            return 0;
        }
    }
}

如你所料,运行该程序的结果是:

0, 1, 2, 11, 21, 111, a0b2, a0b11, a0b20, a0b111, job0, job1, job2, job11, z0, z1, z2, z11

【讨论】:

    【解决方案2】:

    你能做到吗

        List<Job> jobs = new List<Job>();
        jobs.Add("job1");
        jobs.Add("job234");
        jobs.Add("job2");
        jobs.Add("job123");
        jobs.OrderBy(a=>a.Name.Length).ThenBy(a=>a.Name).Dump();
    
    public static class JobExtension {
        public static void Add(this List<Job> jobs, string name){
            jobs.Add(new Job{Name = name});
        }
    }
    
    public class Job {
        public string Name { get; set; }
    }
    

    这很简单,但它适用于您的情况

    如果你想要更复杂的东西,你需要编写自己的 IComparer

    【讨论】:

    • 你的比较器会在哪里结束“z”?
    • 现在尝试实现类似的东西,你能解释一下jobs.OrderBy(a=&gt;a.Name.Length).ThenBy(a=&gt;a.Name).Dump();中的.Dump是什么意思吗?我不明白那部分是从哪里来的。
    • @zjackson1123 sowen 正在使用 Linqpad。
    • @zjackson1123 删除它。从LINQPad 将结果转储到它的结果窗口是一种方便的方法。
    【解决方案3】:

    如果 job3 必须在 job11 之前,我想知道将客户名称约定更改为 job03、job11 之类的名称是否更容易

    这个想法是将工作 ID 格式化为两位数

    【讨论】:

    • 这是一个非常独特的解决方法,我从未考虑过,感谢您的贡献。我发现这种自然排序问题很常见,这可能是我见过的最简洁的解决方法。
    【解决方案4】:

    其他人已经给了你解决方案。由于您似乎是 LINQ 的新手,因此我认为提供一些有关 LINQ 工作原理以及如何使用 Lambda 表达式的背景信息可能会很有用。

    我看到你有两个问题:

    • 给定一系列作业,如何按特定属性对它们进行排序?
    • 如果我有两个要比较的字符串,我如何指定我希望 Job3 在 Job11 之前

    如何使用 LINQ 按属性值排序

    作业序列,是实现IEnumerable&lt;Job&gt; 的每个对象,例如您的List&lt;Job&gt;,但它也可以是数组、数据库查询、从互联网获取的一些信息,任何实现IEnumerable&lt;Job&gt; 的对象.通常所有表示类似对象序列的类都实现了这个接口

    LINQ 只不过是一组方法,它们接受一个 IEnumerable 作为输入并返回一些指定的输出。 LINQ 永远不会改变输入,它只能从中提取数据。

    其中一种 LINQ 方法是 Enumerable.OrderBy。这个方法有一个参数keySelector。此参数选择您要订购的键。您想按 Customer 的升序值排序,因此您的 keySelector 应该告诉方法使用属性 Customer。

    keySelector 的格式为Func&lt;TSource, TKey&gt;。你的输入序列是一个 Job 序列,所以 TSource 是 Job。 func 是一个函数的描述,它有一个 Job 作为输入,作为输出你想要排序的键。这将是这样的:

    string GetOrderByKey(Job job)
    {
        return job.Customer;
    }
    

    注意:返回值是一个字符串,所以在任何地方你看到TKey,你都应该想到字符串。

    幸运的是,自从引入 Lambda 表达式以来,这已经减少了很多打字:

    IEnumerable<Job> jobs = ...
    var jobsOrderedByCustomer = jobs.OrderBy(job => job.Customer);
    

    一句话:
    我们有一个 Job 序列,对于这个序列中的每个 Job 对象,返回一个值为 job.Customer 的键。按此返回的键对作业进行排序。

    第一次看到 lambda 表达式时,可能会觉得有点吓人。通常,如果您对集合(工作)使用复数名词,对集合的元素(工作)使用单数名词,这会有所帮助。

    短语job =&gt; ... 的意思是:for every job in the collection of jobs do ...

    =&gt;之后的部分不必简单,可以是块:

    job =>
    {
        if (String.IsNullOrEmpty(status))
           return job.Company;
        else
           return job.City;
    }
    

    所以如果你需要提供一个Func,你可以写x =&gt; ... 其中x被认为是TSource类型的输入元素,...是函数的返回值,应该是TKey类型

    我不想要标准排序

    如果你在没有比较器的情况下使用Enumerable.OrderBy 的重载,则 orderby 方法使用 TKey 类型的默认比较器。

    所以如果你的keySelector返回一个int,则使用标准的int比较器,如果你的keySelector返回一个字符串,则使用标准的字符串比较,即StringComparer.Ordinal。

    您不需要标准比较,您需要一个比较器,它说Job3 应该在Job11 之前。您需要提供一个比较器。此比较器应实现IComparer&lt;TKey&gt;,其中 TKey 是您的 keySelector 的返回值,在本例中为字符串。

    class CompanyNameComparer : IComparer<string>
    {
        public public int Compare (string x, string y)
        {
             // TODO implement
        }
    }
    

    实现应该返回一个数字,指示 x 应该先出现 ( 0),或者您不关心 (==0)。

    var orderedJobs = jobs.Orderby(job => job.Company, new CompanyNameComparer());
    

    其他人已经给了你比较器的解决方案,所以我不再重复。 The standard LINQ operators

    可以帮助您使用 LINQ 走上正轨

    【讨论】:

    • 非常感谢您的贡献,这有助于我理解我实际上做得更好,如果我还没有接受答案,我绝对会使用它。
    猜你喜欢
    • 2013-05-02
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多