前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C# 字符串排序时指定偏好的排列顺序

C# 字符串排序时指定偏好的排列顺序

作者头像
独立观察员
发布2023-09-02 17:33:49
2110
发布2023-09-02 17:33:49
举报

C# 字符串排序时指定偏好的排列顺序

独立观察员 2023 年 8 月 25 日

不知道大家有没有遇到过某些字符串数据在显示到界面上时需要按一定顺序排列的情况,如果内容是数值或字母自然好办,默认的排序功能就搞定了。那么如果是中文字符串呢?本文将会提供一个能在调用 OrderBy 方法时传入的字符串比较器,能够在一定程度上指定你偏爱的排列顺序,下面就一起来看看吧。

首先来看看原始数据:

代码语言:javascript
复制
List list1 = new List { "大 * 长", "中 * 长", "小 * 长", "大 * 宽", "中 * 宽", "小 * 宽", "大 * 高", "中 * 高", "小 * 高" };
List list2 = new List { "酸 * 红", "甜 * 红", "苦 * 红", "辣 * 红", "酸 * 白", "甜 * 白", "苦 * 白", "辣 * 白", "酸 * 黄", "甜 * 黄", "苦 * 黄", "辣 * 黄" };

可以看出这两个字符串列表都是一些选项的排列组合,每一个字符串目前是由两个选项组合在一起。拿第一个列表来说,就是 “大、中、小” 与 “长、宽、高” 的排列组合。原始数据呈现的顺序是,第一个选项依次出现,第二个选项依次与第一个选项组合。

这样第一个需求就来了,也就是要求第一个选项的某一项与第二个选项全部组合过之后,再换下一项。这么说可能有点抽象,反正这个直接使用 OrderBy 就能实现了,所以直接给大家看结果吧:

代码语言:javascript
复制
// 大 * 高,大 * 宽,大 * 长,小 * 高,小 * 宽,小 * 长,中 * 高,中 * 宽,中 * 长
string defaultOrder1 = string.Join(", ", list1.OrderBy(x => x));
// 苦 * 白,苦 * 红,苦 * 黄,辣 * 白,辣 * 红,辣 * 黄,酸 * 白,酸 * 红,酸 * 黄,甜 * 白,甜 * 红,甜 * 黄
string defaultOrder2 = string.Join(", ", list2.OrderBy(x => x));

可以看到,通过 List 调用 OrderBy 方法就排好了(string.Join 只是把列表连接成字符串),效果就是 “大” 与 “长、宽、高” 都组合完之后才换下一项进行组合,这样看上去就更有条理一些。

但是,新的问题又显现出来了,就是 “大” 组合完之后,竟然是 “小” 进行组合,而不是符合常理的 “中” 参与组合;第二项也是 “高、宽、长” 而不是 “长、宽、高”。这个应该就是中文默认排序导致的,目测是按拼音首字母进行排序(“长” 可能被认为是 “zhang”)。那么如果我们想按照 大 -> 中 -> 小 这样的顺序进行排列,要怎么做呢?

实际上,OrderBy 方法除了第一个参数(lamda 表达式)之外,还有第二个参数(比较器):

也就是需要传一个 IComparer 泛型接口对象,来执行自定义的比较。

本人实现了一个 “字符串偏好比较器”,能够使用指定的偏好排序列表进行排序纠正,完整代码如下:

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
/*
 * 源码己托管: https://gitee.com/dlgcy/dotnetcodes
 */
namespace DotNet.Utilities
{
  /// <summary>
  /// [dlgcy] 字符串偏好比较器
  /// </summary>
  public class StringPreferenceComparer : IComparer<string>
  {
    /// <summary>
    /// 字符串中的分隔符
    /// </summary>
    private readonly string _splitStr;

    /// <summary>
    /// 偏好的排序列表
    /// </summary>
    public List<List<string>> _preferenceList;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="preferenceList">偏好的排序列表</param>
    /// <param name="splitStr">字符串中的分隔符</param>
    public StringPreferenceComparer(List<List<string>> preferenceList, string splitStr)
{
      _splitStr = splitStr;
      _preferenceList = preferenceList ?? new List<List<string>>();
    }

    /// <inheritdoc />
    public int Compare(string x, string y)
{
      if (!_preferenceList.Any())
      {
        return DefaultCompare(x, y);
      }

      var strsX = x?.Split(new[] { _splitStr }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
      var strsY = y?.Split(new[] { _splitStr }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();

      if (!strsX.Any() && !strsY.Any())
      {
        return 0;
      }
      else if (strsX.Any() && !strsY.Any())
      {
        return 1;
      }
      else if (!strsX.Any() && strsY.Any())
      {
        return -1;
      }

      int countX = strsX.Length;
      int countY = strsY.Length;
      int index = 0;

      while (index < countX && index < countY)
      {
        string currentPartX = strsX[index];
        string currentPartY = strsY[index];

        List<string> matchedList = SearchMatchedList(currentPartX, currentPartY);
        if (matchedList == null)
        {
          return DefaultCompare(currentPartX, currentPartY);
        }

        int compareX = -1;
        int compareY = -1;
        for (int i = 0; i < matchedList.Count; i++)
        {
          string str = matchedList[i];
          if (compareX == -1 && currentPartX.Contains(str))
          {
            compareX = i;
          }

          if (compareY == -1 && currentPartY.Contains(str))
          {
            compareY = i;
          }
        }

        if (compareX != compareY)
        {
          return compareX > compareY ? 1 : -1;
        }

        index++;
      }

      if (countX == countY)
      {
        return DefaultCompare(x, y);
      }
      else if(countX < countY)
      {
        return DefaultCompare(x, strsY.Take(countX).ToString());
      }
      else
      {
        return DefaultCompare(strsX.Take(countY).ToString(), y);
      }
    }

    /// <summary>
    /// 默认比较
    /// </summary>
    private int DefaultCompare(string x, string y)
{
      return string.Compare(x, y, false, CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// 搜寻匹配的列表
    /// </summary>
    /// <param name="currentPartX">X字符串的当前比较部分</param>
    /// <param name="currentPartY">Y字符串的当前比较部分</param>
    /// <returns></returns>
    private List<string> SearchMatchedList(string currentPartX, string currentPartY)
    {
      List<string> matchedList = null;
      foreach (var list in _preferenceList)
      {
        if (list.Exists(currentPartX.Contains) && list.Exists(currentPartY.Contains))
        {
          matchedList = list;
          break;
        }
      }

      return matchedList;
    }
  }
}

为了显得更高深,我就没加注释了,哈哈。简单解释一下吧,也就是实现了 IComparer 接口,接口里有个方法 int Compare (string x, string y) 用于比较两个字符串。我添加了一个偏好排序列表以及一个分隔符字段,在构造函数中传入。在比较方法中,先使用分隔符,将两个字符串分别分割成多个部分,然后对于两者对应的部分,查找是否有适用的排序偏好列表,有的话,按照列表来排序,没有则使用默认的字符串排序。另外说一下 Compare 方法的规则:x > y 则需返回大于 0 的值(一般用 1),x < y 需返回小于 0 的值(一般用 -1),x == y 则返回 0。

然后看看如何使用吧:

代码语言:javascript
复制
List<List> preferenceList = new List<List>()
{
    new (){"大", "中", "小"},
    new (){"长", "宽", "高"},
    new (){"红", "黄", "白"},
    new (){"酸", "甜", "苦", "辣"},
};
var comparer = new StringPreferenceComparer(preferenceList, "*");
// 大 * 长,大 * 宽,大 * 高,中 * 长,中 * 宽,中 * 高,小 * 长,小 * 宽,小 * 高
string myOrder1 = string.Join(", ", list1.OrderBy(x => x, comparer));
// 酸 * 红,酸 * 黄,酸 * 白,甜 * 红,甜 * 黄,甜 * 白,苦 * 红,苦 * 黄,苦 * 白,辣 * 红,辣 * 黄,辣 * 白
string myOrder2 = string.Join(", ", list2.OrderBy(x => x, comparer));

可以看到最终结果已经按照给定的排序偏好进行排序了。

整个单元测试代码如下,可以整体对比一下:

最后,说明一下,本方法实现了本次需求,其余的情况应该也是可以的,不过未进行测试,大家可以自行测试,也欢迎提出修改建议。

代码托管:https://gitee.com/dlgcy/dotnetcodes

https://gitee.com/dlgcy/dotnetcodes/blob/dlgcy/DotNet.Utilities/%E5%AD%97%E7%AC%A6%E4%B8%B2/StringPreferenceComparer.cs

全文完,感谢阅读。

原创文章,转载请注明: 转载自 独立观察员 (dlgcy.com)

本文链接地址: [C# 字符串排序时指定偏好的排列顺序](http://dlgcy.com/csharp-string-orderby-preference-comparer/)

C#

对于 C# 中 Task 的 StartNew 与 WhenAll 相互配合的实验

【问题】为什么 System.Timers.Timer 更改间隔时间后的第一次触发时间是设定时间的三倍?

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑

【分享】C# 字节帮助类 ByteHelper

C# 在自定义的控制台输出重定向类中整合调用方信息

C# 枚举转列表

.NET

Windows 服务 同时启动多个服务

PostSharp 中 AOP 功能的简单使用

.NET SDK-Style 项目(Core、Standard、.NET5)中的版本号

将 .NET Framework 项目转换为 .NET Standard 项目

ASP.NET Core MVC 网站学习笔记

Unity 容器简单使用方法

Unity容器依赖注入之属性注入使用备忘

添加服务引用来使用WebService

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 独立观察员博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C# 字符串排序时指定偏好的排列顺序
    • C#
      • .NET
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档