Custom list <string []> Sort

I have a list string[]

.

List<string[]> cardDataBase;

      

I need to sort this list by each string value of list item ( item[1]

) in custom order.

The custom ordering is a little tricky, ordering by those start symbols:

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

      

then order those letters with the following starting letters:

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

      

and then the numbers indicated above.

pattern, left unordered list, right ordered:

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

      

I tried Linq, but right now I'm not very good at it and can't solve it on my own. Do I need a dictionary, regex or regex dictionary? What would be the best approach?

+3


source to share


4 answers


I think you are approaching this the wrong way. You are not sorting strings , you are sorting structured objects that are mangled as strings (someone aptly called this antipattern "stringly typed" ). Your requirements show that you know this structure, but it is not represented in the datastructure List<string[]>

, and it complicates your life. You have to parse this structure into a real type (struct or class) and then sort it.

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");
    }
}

      

A program using the above procedure to process your example might be:

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));
    }
}

      



This creates the same ordering as in your example. In the real code, I would recommend you to specify types in accordance with what they represent rather than, for example TheseLetters

.

This solution - with a real parsing step - is excellent because you will almost certainly want to do more with this data at some point, and it will allow you to actually access the data components easily. In addition, it is understandable for a future maintainer as the reason for the order is somewhat clear. In contrast, if you decide to do complex string processing, it is often very difficult to understand what is going on (especially if it is part of a larger program, rather than a tiny example like here).

Creating new types is cheap. If the return value of a method doesn't quite "fit" in an existing type, just create a new one, even if it means 1000 types.

+1


source


A bit false feeding, but I found this question quite interesting and maybe helpful to others, also added some comments to explain:



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 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);
}

      

+1


source


You can use Array.Sort () method . Where your first parameter is the [] string you are sorting, and the second parameter contains the complex logic for determining the order.

0


source


You can use the IEnumerable.OrderBy method provided by the System.Linq namespace.

0


source







All Articles