【问题标题】:Custom List<string[]> Sort自定义列表<string[]> 排序
【发布时间】:2014-09-14 00:32:17
【问题描述】:

我有一个string[] 的列表。

List<string[]> cardDataBase;

我需要按每个列表项的第二个字符串值 (item[1]) 以自定义顺序对该列表进行排序。

自定义顺序有点复杂,按开头的字符排序:

"MW1"
"FW"
"DN"
"MWSTX1CK"
"MWSTX2FF"

然后按上述起始字母后的这些字母排序:

"A"
"Q"
"J"
"C"
"E"
"I"
"A"

然后按上面的数字。

一个样本,无序列表左,右排序:

MW1E10              MW1Q04
MWSTX2FFI06         MW1Q05
FWQ02               MW1E10
MW1Q04              MW1I06
MW1Q05              FWQ02
FWI01               FWI01
MWSTX2FFA01         DNC03
DNC03               MWSTX1CKC02
MWSTX1CKC02         MWSTX2FFI03
MWSTX2FFI03         MWSTX2FFI06
MW1I06              MWSTX2FFA01

我尝试过 Linq,但我现在还不是很擅长,无法靠我自己解决这个问题。我需要字典、正则表达式还是带有正则表达式的字典?最好的方法是什么?

【问题讨论】:

  • 您的“这些信件”部分重复“A”;这使得任何排序都模棱两可。鉴于您的示例,似乎 A 跟随 I,因此最初的 A 是错误的。

标签: c# string sorting


【解决方案1】:

我认为您的处理方法不正确。您不是对字符串进行排序,而是在对被错误表示为字符串的结构化对象进行排序(有人恰当地将这种反模式命名为"stringly typed")。您的要求表明您知道这个结构,但它没有在数据结构List&lt;string[]&gt; 中表示,这让您的生活变得艰难。您应该将该结构解析为真实类型(结构或类),然后对其进行排序。

enum PrefixCode { MW1, FW, DN, MWSTX1CK, MWSTX2FF, }
enum TheseLetters { Q, J, C, E, I, A, }
struct CardRecord : IComparable<CardRecord> {
    public readonly PrefixCode Code;
    public readonly TheseLetters Letter;
    public readonly uint Number;
    public CardRecord(string input) {
        Code = ParseEnum<PrefixCode>(ref input);
        Letter = ParseEnum<TheseLetters>(ref input);
        Number = uint.Parse(input);
    }
    static T ParseEnum<T>(ref string input) { //assumes non-overlapping prefixes
        foreach(T val in Enum.GetValues(typeof(T))) {
            if(input.StartsWith(val.ToString())) {
                input = input.Substring(val.ToString().Length);
                return val;
            }
        }
        throw new InvalidOperationException("Failed to parse: "+input);
    }
    public int CompareTo(CardRecord other) {
        var codeCmp = Code.CompareTo(other.Code);
        if (codeCmp!=0) return codeCmp;
        var letterCmp = Letter.CompareTo(other.Letter);
        if (letterCmp!=0) return letterCmp;
        return Number.CompareTo(other.Number);
    }
    public override string ToString() { 
        return Code.ToString() + Letter + Number.ToString("00");
    }
}

使用上述方法处理您的示例的程序可能是:

static class Program {
    static void Main() {
        var inputStrings = new []{ "MW1E10", "MWSTX2FFI06", "FWQ02", "MW1Q04", "MW1Q05", 
            "FWI01", "MWSTX2FFA01", "DNC03", "MWSTX1CKC02", "MWSTX2FFI03", "MW1I06" };
        var outputStrings = inputStrings
            .Select(s => new CardRecord(s))
            .OrderBy(c => c)
            .Select(c => c.ToString());
        Console.WriteLine(string.Join("\n", outputStrings));
    }
}

这会生成与您的示例相同的排序。在实际代码中,我建议您根据它们所代表的内容命名类型,而不是例如TheseLetters

这个解决方案 - 具有真正的解析步骤 - 非常出色,因为它几乎可以肯定您在某些时候会想要对这些数据做更多的事情,这使您可以轻松地实际访问数据的组成部分。此外,对于未来的维护者来说,这是可以理解的,因为排序背后的原因 有点清楚。相比之下,如果您选择进行复杂的基于字符串的处理,通常很难理解正在发生的事情(尤其是如果它是更大程序的一部分,而不是像这里这样的小例子)。

制作新类型很便宜。如果您的方法的返回值不太“适合”现有类型,只需创建一个新类型,即使这意味着有 1000 种类型。

【讨论】:

  • 哇,没想到回答这么详细,这么快,谢谢大家!你的方法似乎是很好的做法,你是对的,我在某个时候再次需要这些数据。为了告诉你更多关于我的案例,这些名称是一个不断增长的卡片纹理列表,它们也是现实生活中卡片的实际标识符(卡片游戏是 Mage Wars)。前缀是扩展名,“这些字母”是卡类型,数字是扩展中每种类型从 01 重新开始的索引。谢谢你,我学到了smth。今天!
  • 是的,我只是经常看到这个错误。人们制作这些极其复杂的解决方案来处理他们的数据——而且它确实有效——但以后很难改变或理解,即使你是编写原始代码的人 :-)。这是只写代码。不要害怕中间解决方案:我认为编程就是将解决方案封装到微不足道的问题中,然后将这些解决方案组合成更大的块,直到你得到有用的东西。
【解决方案2】:

有点啰嗦,但我发现这个问题很有趣,也许对其他人有用,还添加了一些cmets来解释:

void Main()
{
    var cardDatabase = new List<string>{
        "MW1E10",          
        "MWSTX2FFI06",         
        "FWQ02",               
        "MW1Q04",              
        "MW1Q05",              
        "FWI01",               
        "MWSTX2FFA01",         
        "DNC03",               
        "MWSTX1CKC02",         
        "MWSTX2FFI03",        
        "MW1I06",  
    };


    var orderTable = new List<string>[]{
        new List<string>
        {
            "MW1",
            "FW",
            "DN",
            "MWSTX1CK",
            "MWSTX2FF"
        },

        new List<string>
        {
            "Q",
            "J",
            "C",
            "E",
            "I",
            "A"
        }
    };


    var test = cardDatabase.Select(input => {
        var r = Regex.Match(input, "^(MW1|FW|DN|MWSTX1CK|MWSTX2FF)(A|Q|J|C|E|I|A)([0-9]+)$");
        if(!r.Success) throw new Exception("Invalid data!");

        // for each input string,
        // we are going to split it into "substrings",
        // eg: MWSTX1CKC02 will be
        // [MWSTX1CK, C, 02]
        // after that, we use IndexOf on each component
        // to calculate "real" order,

        // note that thirdComponent(aka number component)
        // does not need IndexOf because it is already representing the real order,
        // we still want to convert string to integer though, because we don't like
        // "string ordering" for numbers.

        return  new 
        {
            input = input,
            firstComponent = orderTable[0].IndexOf(r.Groups[1].Value), 
            secondComponent = orderTable[1].IndexOf(r.Groups[2].Value), 
            thirdComponent = int.Parse(r.Groups[3].Value)
        };

        // and after it's done,
        // we start using LINQ OrderBy and ThenBy functions
        // to have our custom sorting.
    })
    .OrderBy(calculatedInput => calculatedInput.firstComponent)
    .ThenBy(calculatedInput => calculatedInput.secondComponent)
    .ThenBy(calculatedInput => calculatedInput.thirdComponent)
    .Select(calculatedInput => calculatedInput.input)
    .ToList();


    Console.WriteLine(test);
}

【讨论】:

    【解决方案3】:

    您可以使用Array.Sort() 方法。您的第一个参数是您要排序的字符串[],第二个参数包含确定顺序的复杂逻辑。

    【讨论】:

      【解决方案4】:

      您可以使用 System.Linq 命名空间提供的 IEnumerable.OrderBy 方法。

      【讨论】:

        猜你喜欢
        • 2012-11-18
        • 1970-01-01
        • 2015-02-09
        • 2017-11-06
        • 2012-08-04
        • 2011-03-02
        • 2021-04-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多