.net 使用Sort()对具有特定条件的列表数据进行排序,

js81xvg6  于 2023-04-13  发布在  .NET
关注(0)|答案(2)|浏览(163)

我想对一些数据进行有效的排序。假设我有这个字符串列表:

var somedata = new List<string> {"<some data>",
    "  <some data else>",
    "</some data>",
    "<Animal>",
    "  <Animal Name=\"Lion\" Group=\"Feline\" Colour=\"Brown\" />",
    "  <Animal Name=\"Zebra\" Group=\"Equidae\" Colour=\"Black and White\" />",
    "  <Animal Name=\"Tuna\" Group=\"Fish\" Colour=\"Gray\" />",
    "  <Animal Name=\"Horse1\" Group=\"Equidae\" Colour=\"Black\" />",
    "  <Animal Name=\"Horse10\" Group=\"Equidae\" Colour=\"White\" />",
    "  <Animal Name=\"Horse2\" Group=\"Equidae\" Colour=\"White\" />",
    "  <Animal Name=\"Dog20\" Group=\"Canidae\" Colour=\"Black\" />",
    "  <Animal Name=\"Dog2\" Group=\"Canidae\" Colour=\"Black\" />",
    "  <Animal Name=\"Cat\" Group=\"Feline\" Colour=\"Various\" />",
    "  <Animal Name=\"Falcon\" Group=\"Bird 1\" Colour=\"Brown\" />",
    "  <Animal Name=\"Duck\" Group=\"Bird 2\" Colour=\"White\" />",
    "  <Animal Name=\"Eagle\" Group=\"Bird 1\" Colour=\"Brown\" />",
    "  <Animal Name=\"Shark\" Group=\"Fish\" Colour=\"Gray\" />",
    "  <Animal Name=\"Mouse\" Group=\"Rodent\" Colour=\"Brown\" />",
    "</Animal>",
    "<some other data>"
    "  <bla bla bla>"
    "</some other data>"};

实际上,我用这个简单的方法来排序

somedata.Sort();

然后我把它们复制到一个新的列表中迭代Group=的列表(猫科,马科,鱼类,猫科,鸟类1,Bird2…)参数,因为我会按组类型划分列表。在第二个新列表中没有AnimalName,然后我在标记"<Animal>""</Animal>"之间使用命令templist.Add(line)进行合并。(这里不会有双字符串,因为我复制了所有的数据,而没有后面复制的要排序的数据)。
好吧,我们可以说我遵循的方式在99%(我有数字问题),但我想直接使用Sort()。我如何为参数<Animal Name=Group=创建一个自定义排序过滤器,留下任何没有<Animal Name=的字符串?
顺便说一句,当我用我现在的方法排序时,我发现了一个问题。带有数字的数据不遵循语法顺序规则,所以我得到了Horse1Horse10Horse2,而不是我想得到的:Horse1Horse2Horse10
如何解决最后一个问题?
先谢谢你了
输出结果如下所示:

<some data>
  <some data else>
</some data>
<Animal>,
  <Animal Name="Eagle" Group="Bird 1" Colour="Brown" />
  <Animal Name="Falcon" Group="Bird 1" Colour="Brown" />
  <Animal Name="Duck" Group="Bird 2" Colour="White" />
  <Animal Name="Dog2" Group="Canidae" Colour="Black" />
  <Animal Name="Dog20" Group="Canidae" Colour="Black" />
  <Animal Name="Horse1" Group="Equidae" Colour="Black" />
  <Animal Name="Horse2" Group="Equidae" Colour="White" />
  <Animal Name="Horse10" Group="Equidae" Colour="White" />
  <Animal Name="Zebra" Group="Equidae" Colour="Black and White" />
  <Animal Name="Cat" Group="Feline" Colour="Various" />
  <Animal Name="Lion" Group="Feline" Colour="Brown" />
  <Animal Name="Shark" Group="Fish" Colour="Gray" /> 
  <Animal Name="Tuna" Group="Fish" Colour="Gray"  />
  <Animal Name="Mouse" Group="Rodent" Colour="Brown" />
</Animal> />
<some other data>
  <bla bla bla>
</some other data>
ndh0cuux

ndh0cuux1#

要在horse2之后对horse10进行排序,请参阅natural sort order in c#。您可以通过调用本机函数并将其 Package 在IComparer中来使用与windows相同的排序:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string psz1, string psz2);

然而,尝试像这样对字符串进行排序而不实际解析它们似乎非常脆弱。由于数据似乎是某种XML,我建议使用某种类型的XML解析将其转换为对象列表。如果您想根据多个属性对对象进行排序,可以使用LINQ .OrderBy(...).ThenBy(...)

bttbmeg0

bttbmeg02#

从纯粹的 * 排序 * Angular 来看,这个请求的问题是,您只要求按多个维度对列表中的项目的子集进行排序,但希望在更大的集合中保持该子集的一般位置。
我们不能用Array.Sort()或自定义IComparable实现以确定性的方式实现这一点,因为外部集需要保持其原始的任意序列,而序列信息不包含在字符串值本身中。
但是我们可以用LINQ来做这件事。
我想高效地分类
你的意思是你想要最少的代码,或者最少的迭代,或者最少的执行时间,或者最少的内存使用量?

  • 以这种方式处理大型数据集很容易消耗大量资源。

认识到这个字符串列表一起形成了许多XML元素意味着我们可能会反序列化为对象,重新处理数据,然后重新序列化回字符串列表。这在代码和时间方面很可能是效率最低的,但会产生最可靠的结果,逻辑也会简单得多理解。内存消耗应该是可管理的...只要您能保证输入是有效的XML片段,这个(在撰写本文时)就不是
你的第一次尝试并不坏,但它会导致然而,有一种技术,不涉及维护单独的孤立的记录列表。我们可以将数据分类(归类)到分组集合中,这样我们就可以根据这些集合中的组或值的索引进行排序,而无需实际存储它们或修改原始字符串。

  • 这里的技术通常用于SQL Gap and Island分析查询,这就是这类问题的本质。

首先,让我们来处理数字排序。我发现最简单的解决方案是用零填充所有数值到一个固定的长度,下面的方法接受一个字符串和在字符串中找到的任何数字将被填充到的位数。
这个逻辑使用分类技术来识别文本中的数字,我们确定这些组是全数字或全字母(当然不是数字)的字符,并为每个组分配一个索引。然后,我们可以使用LINQ组表达式根据这些索引进行分组,并在数字组中使用左填充。
在此答案中使用此解决方案而不是PInvoke:https://stackoverflow.com/a/75986862/1690217

/// <summary> Pad numeric portions of a string to allow natural lexicographic sorting to behave like numerical sorting for the numeric components</summary>
/// <remarks> 
/// using the same classification technique because we can. 
/// <para>- It has not been tested for performance.</para>
/// Sorry about the name, couldn't help myself ;)
/// </remarks>
/// <param name="value">The string value to pad the numbers in</param>
/// <param name="size">The number of digits to pad the numeric values out to, by default 3 digits</param>
/// <returns> alphanumeric string that so that "Bird 2" becomes "Bird 02", to allow "Bird 2" to be sorted before "Bird 10"<returns>
public static string LexicographicToAlphaNumeric(string value, int size = 3)
{
    // exit if there is nothing to process.
    if (String.IsNullOrWhiteSpace(value)) return value;
    
    int groupId = 0;
    bool isNumberGroup = false;
    return String.Join("", value.Select(c => {
            var isNumber = Char.IsDigit(c); // NOTE: will not support decimals
            if( isNumber != isNumberGroup)
            {
                groupId ++;
                isNumberGroup = isNumber;
            }
            return new { c, isNumber, groupId };
        })
        .GroupBy(c => c.groupId)
        .Select(g => g.First().isNumber 
            ? new string(g.Select(x => x.c).ToArray()).PadLeft(size, '0') 
            : new string(g.Select(x => x.c).ToArray())
        )
    );
}

您可以test this here

var tests = new string [] {
        "Bird 10",
        "Bird 2",
        "Bird 1",
        "20Lions",
        "5Lions",
        "red10blue",
        "Red9blue",
        "red100Blue"
    };
    foreach(var x in tests.Select(x => LexicographicToAlphaNumeric(x)).OrderBy(x => x))
        Console.WriteLine(x);

生成以下输出:

005Lions
020Lions
Bird 001
Bird 002
Bird 010
Red009blue
red010blue
red100Blue

回到最初的问题...

因此,排序顺序需要如下:
1.对于所有不以<Animal Name=开头的项目,请遵循原始的整体顺序
1.按 Animal 项中的Group=属性值排序

  • 按相同 Group 值中的Name=属性排序
  • 调整数字后缀后

我们以同样的方式来实现这一点,使用LINQ projection(Select)来 classify 我们的分组集,然后在组上使用LINQ sort (OrderBy)来实现所需的排序层次结构:https://dotnetfiddle.net/707YIW

// Helpers for the top level grouping set
    int setId = 0;
    string sortSetPrefix = "<Animal Name=";
    bool isInSortSet = false;
    
    // Regex Helpers for the attribute grouping sets
    var groupRegex = new System.Text.RegularExpressions.Regex(@"Group=\""([^\""]*)\""");
    var nameRegex = new System.Text.RegularExpressions.Regex(@"Name=\""([^\""]*)\""");
    
    var sortedData = somedata.Select((line, index) => 
                    {
                        var lineInSortSet = line.Trim().StartsWith(sortSetPrefix);
                        if (lineInSortSet != isInSortSet) 
                        {
                            setId ++;
                            isInSortSet = lineInSortSet;
                        }

                        // NOTE: Match.Value is NOT the captured value, but the whole value: Group="Equidae"
                        //       This will produce the correct results for less code/effort 
                        var groupName = LexicographicToAlphaNumeric(groupRegex.Match(line).Value);
                        var animalName = LexicographicToAlphaNumeric(nameRegex.Match(line).Value);
                                    
                        return new { line, index, setId, groupName, animalName };
                    })
                 .ToList() // Optional, cache the results to reduce re-entry of the above logic during sorting.
                 .OrderBy(x => x.setId).ThenBy(x => x.groupName).ThenBy(x => x.animalName).ThenBy(x => x.index)
                 .Select(x => x.line) // discard the classifications
                 .ToList(); // Deliberate evaluation of the sort expression here prevent re-evaluation later

对于那些仔细看过或者刚接触LINQ的人来说,你可以看到我使用了一个overload of Select来从底层枚举器中公开索引。这就是我们如何访问整个序列,而不需要先故意迭代整个集合。
这解决了Array.SortIComparable不能解决的问题,如果我们不以这种方式使用IEnumerable.Select,那么我们将不得不使用for循环来迭代这些项...
以下输出包括分类数据,因此您可以看到它是如何工作的:

Test Categorized Sorting output:
0,,,000:                                          <some data>
0,,,001:                                            <some data else>
0,,,002:                                          </some data>
0,,,003:                                          <Animal>
1,Group="Bird 001",Name="Eagle",015:                <Animal Name="Eagle" Group="Bird 1" Colour="Brown" />
1,Group="Bird 001",Name="Falcon",013:               <Animal Name="Falcon" Group="Bird 1" Colour="Brown" />
1,Group="Bird 002",Name="Duck",014:                 <Animal Name="Duck" Group="Bird 2" Colour="White" />
1,Group="Canidae",Name="Dog002",011:                <Animal Name="Dog2" Group="Canidae" Colour="Black" />
1,Group="Canidae",Name="Dog020",010:                <Animal Name="Dog20" Group="Canidae" Colour="Black" />
1,Group="Equidae",Name="Horse001",007:              <Animal Name="Horse1" Group="Equidae" Colour="Black" />
1,Group="Equidae",Name="Horse002",009:              <Animal Name="Horse2" Group="Equidae" Colour="White" />
1,Group="Equidae",Name="Horse010",008:              <Animal Name="Horse10" Group="Equidae" Colour="White" />
1,Group="Equidae",Name="Zebra",005:                 <Animal Name="Zebra" Group="Equidae" Colour="Black and White" />
1,Group="Feline",Name="Cat",012:                    <Animal Name="Cat" Group="Feline" Colour="Various" />
1,Group="Feline",Name="Lion",004:                   <Animal Name="Lion" Group="Feline" Colour="Brown" />
1,Group="Fish",Name="Shark",016:                    <Animal Name="Shark" Group="Fish" Colour="Gray" />
1,Group="Fish",Name="Tuna",006:                     <Animal Name="Tuna" Group="Fish" Colour="Gray" />
1,Group="Rodent",Name="Mouse",017:                  <Animal Name="Mouse" Group="Rodent" Colour="Brown" />
2,,,018:                                          </Animal>
2,,,019:                                          <some other data>
2,,,020:                                            <bla bla bla>
2,,,021:                                          </some other data>

这个排序逻辑对于这个特定的输入是如此独特,以至于尝试将排序逻辑打包到IComparable实现中几乎没有价值,即使你可以。然而,IEnumerable逻辑可以抽象为一个自定义的Enumeration,它看起来很漂亮,但这并没有使代码更容易维护,相反,它对用户隐藏了逻辑,这可能会对您的团队产生负面影响。的能力,以理解和维护这一逻辑在长期内:https://dotnetfiddle.net/lxI16F

public static IEnumerable<string> SortAnimalsWithoutDisturbingTheOtherLines(IEnumerable<string> lines)
{
    // Helpers for the top level grouping set
    int setId = 0;
    string sortSetPrefix = "<Animal Name=";
    bool isInSortSet = false;
    
    // Regex Helpers for the attribute grouping sets
    var groupRegex = new System.Text.RegularExpressions.Regex(@"Group=\""([^\""]*)\""");
    var nameRegex = new System.Text.RegularExpressions.Regex(@"Name=\""([^\""]*)\""");
    
    foreach(var line in lines.Select((line, index) => 
                                {
                                    var lineInSortSet = line.Trim().StartsWith(sortSetPrefix);
                                    if (lineInSortSet != isInSortSet) 
                                    {
                                        setId ++;
                                        isInSortSet = lineInSortSet;
                                    }

                                    // NOTE: Match.Value is NOT the captured value, but the whole value: Group="Equidae"
                                    //       This will produce the correct results for less code/effort 
                                    var groupName = LexicographicToAlphaNumeric(groupRegex.Match(line).Value);
                                    var animalName = LexicographicToAlphaNumeric(nameRegex.Match(line).Value);
                                    
                                    return new { line, index, setId, groupName, animalName };
                                })
                             .ToList() // Cache the results to reduce re-entry of the above logic during sorting.
                             .OrderBy(x => x.setId).ThenBy(x => x.groupName).ThenBy(x => x.animalName).ThenBy(x => x.index)
                             .Select(x => x.line) 
                             )
            yield return line;
}

用法,接近.Sort()

var sortedData = SortAnimalsWithoutDisturbingTheOtherLines(somedata).ToList(); // ToList() => Deliberate evaluation of the sort expression here prevent re-evaluation

注意事项:

  • 将此逻辑作为List<string>的扩展公开是没有意义的,因为实现太具体了。您可以对某种业务域类型这样做,但不能对字符串列表这样做,这将是一种反模式。
  • 如果需要支持大于3位的数字,则更改名称不恰当的LexicographicToAlphaNumeric的默认size实现,或者为size参数提供一个值
var animalName = LexicographicToAlphaNumeric(nameRegex.Match(line).Value, 7);

如果不首先解析 ALL 可能的值,我们就无法知道函数内部的最大长度是多少,这将不会那么有效。

相关问题