2014-09-14 3 views
3

У меня есть список string[].Пользовательский список <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 следует за I, и, следовательно, исходный A является ошибкой. –

ответ

1

Я думаю, вы приближаетесь к этому неправильно. Вы не Сортировка строк, вы сортируете структурированные объекты, которые искажены как строки (кто-то метко назвал этот противник "stringly typed"). Ваши требования показывают, что вы знаете эту структуру, но она не представлена ​​в datastructure List<string[]>, и это усложняет вашу жизнь. Вы должны разобрать эту структуру в реальном типе (struct или class), а затем отсортировать ее.

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 типов.

+0

Вау, я не ожидал таких подробных и быстрых ответов, спасибо, ребята! Ваш подход кажется хорошей практикой, и вы правы, мне нужны эти данные снова в какой-то момент. Чтобы рассказать вам больше о моем случае, эти имена представляют собой большой растущий список карточных текстур, которые также являются фактическими идентификаторами карточек в реальной жизни (карточная игра - это Войны магов). Префикс - это имя расширения, «TheseLetters» - это тип карты, а номера - это номера, перезапускаемые в 01 для каждого типа в расширении. Спасибо, я узнал что-л. Cегодня! – Marrt

+0

Да, я только что видел эту ошибку слишком часто. Люди делают эти гиперкомплексные решения обработкой своих данных - и это работает - но это очень сложно изменить или понять позже, даже если вы тот, кто написал оригинальный код :-). Это код только для записи. Не бойтесь промежуточных решений: я бы сказал, что программирование - это все, что касается инкапсулирующих решений для проблем, которые тривиальны, и затем составлять эти решения в любых больших кусках, пока вы не получите что-то полезное. –

0

Вы можете использовать метод Array.Sort(). Где ваш первый параметр - это строка [], которую вы сортируете, а второй параметр содержит сложную логику определения порядка.

0

Вы можете использовать метод IEnumerable.OrderBy, предоставляемый пространством имен System.Linq.

1

Немного spoonfeeding, но я нашел этот вопрос довольно интересный и, возможно, это будет полезно для других, также добавлены некоторые комментарии, чтобы объяснить:

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); 
} 
Смежные вопросы