在 C# 中按字母数字顺序对列表 <class> 进行排序

Sorting a list<class> alphanumerically in c#

我有一个 list<job> 调用的 _joblist,它是在表单加载时从反序列化 json 填充的,作业是 class:

     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<string>string[],由于我对 c# 比较陌生,所以我不确定如何在这个特定示例中执行此操作。如果问题不清楚或者我在任何术语上有误,我深表歉意,就像我说的,我对此比较陌生,很乐意根据要求提供澄清,在此先感谢。

你能做到吗

    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

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

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

想法是将作业 ID 格式化为两位数

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

我看到你有两个问题:

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

如何使用 LINQ

按 属性 的值排序

一系列作业,是实现 IEnumerable<Job> 的每个对象,例如您的 List<Job>,但它也可以是数组、数据库查询、从互联网上获取的一些信息,任何实现 IEnumerable<Job> 的东西。通常所有表示相似对象序列的 类 都实现此接口

LINQ 只不过是一组方法,这些方法将某些东西的 IEnumerable 作为输入,returns 一些指定的输出。 LINQ 永远不会改变输入,它只能从中提取数据。

LINQ 方法之一是 Enumerable.OrderBy。这个方法有一个参数keySelector。此参数选择您要排序的密钥。您希望按 Customer 的升序排序,因此您的 keySelector 应该告诉该方法使用 属性 Customer.

keySelector 的格式为 Func<TSource, TKey>。您的输入序列是作业序列,因此 TSource 是作业。 func 是对函数的描述,该函数的输入是作业,输出是您要排序的键。这将是这样的:

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

注意:return 值是一个字符串,所以无论你在哪里看到 TKey,你都应该想到字符串。

幸运的是,自从引入 Lambda 表达式以来,输入的次数大大减少了:

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

换言之:
我们有一个作业序列,对于这个序列中的每个作业对象,return 一个值为 job.Customer 的键。按此 returned 键订购工作。

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

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

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

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

所以如果你需要提供一个Func,你可以这样写x => ... 其中 x 被认为是 TSource 类型的输入元素,并且 ... 是函数的 return 值,它应该是 TKey

类型

我不想要标准顺序

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

因此,如果您的 keySelector return 是一个整数,则使用标准的整数比较器,如果您的 keySelector return 是一个字符串,则使用标准字符串比较器,即 StringComparer.Ordinal.

你不需要标准比较,你想要一个比较器,它说 Job3 应该在 Job11 之前。您需要提供一个比较器。这个比较器应该实现 IComparer<TKey>,其中 TKey 是你的 keySelector 的 return 值,在本例中是一个字符串。

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

实现应该 return 一个数字,指示 x 应该在前面 (< 0),还是 y (> 0),或者你不关心 (==0)。

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

其他人已经给了你比较器的解决方案,我就不再赘述了。 The standard LINQ operators

可能有助于您使用 LINQ 走上正轨