自定义 List<string[]> 排序

3

我有一个 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,但现在对它并不熟练,无法自己解决这个问题。我需要使用字典、正则表达式还是包含正则表达式的字典?哪种方法最好?


1
你的“这些字母”部分重复了“A”,这使得任何排序都变得模糊不清。根据你的例子,似乎A跟随I,因此初始的A是错误的。 - Eamon Nerbonne
4个回答

1
我认为你的方法不正确。你并不是在排序字符串,而是在排序被误解为字符串的结构化对象(有人恰当地称之为"stringly typed"反模式)。你的需求表明你知道这个结构,但它没有在数据结构List<string[]>中表示出来,这使得你的生活变得困难。你应该将该结构解析成实际类型(结构体或类),然后对其进行排序。
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`。
这种使用了真正的解析步骤的解决方案更优秀,因为几乎可以肯定在某个时候您会想要对该数据执行更多操作,这使得您可以轻松地访问数据的组件。此外,这对于未来的维护者来说也是可以理解的,因为排序背后的原因是有些清楚的。相比之下,如果您选择进行复杂的基于字符串的处理,往往很难理解发生了什么(尤其是如果它是一个更大的程序的一部分,而不仅仅是这里的小例子)。
创建新类型是简单的。如果您的方法的返回值不完全 "适合" 存在的类型,则只需创建一个新类型,即使这意味着成千上万个类型也可以。

哇,我没想到会得到如此详细和快速的回答,谢谢你们!你们的方法似乎是一个好的实践,你们是对的,我在某个时候需要这些数据。 为了让你更了解我的情况,这些名称是一个不断增长的卡牌纹理列表,同时也是实际卡牌的标识符(卡牌游戏是Mage Wars)。前缀是扩展名,“这些字母”是卡牌类型,数字是每个扩展中每种类型的索引,从01重新开始。谢谢,我今天学到了东西! - Marrt
是的,我已经看到这个错误太多次了。人们制定这些超复杂的解决方案来处理他们的数据 - 它可以工作 - 但以后要更改或理解它真的很难,即使你是编写原始代码的人 :-)。这是只能写不能读的代码。不要害怕中间解决方案:我认为编程就是将解决问题的解决方案封装成微不足道的东西,然后将这些解决方案组合成更大的块,直到你得到有用的东西。 - Eamon Nerbonne

1
有点过于简单易懂,但我觉得这个问题非常有趣,或许对其他人也有用。我添加了一些注释来解释:
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);
}

0
你可以使用Array.Sort()方法。其中,第一个参数是你要排序的string[],第二个参数包含了决定顺序的复杂逻辑。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接