2011-09-01 2 views
3

У меня есть словарь ID, который с указанной являются буквенно-цифровыми (например, À10À10 & d10a9), из которого я хочу больше всего ID, то есть 9 < < 10 и ...Получить Max() алфавитно-цифрового значения

Когда я использую следующий код, d10a9 является MAX с 9 сортируется до 10

var lsd = new Dictionary<string, string>(); 
lsd.Add("a", "d10a10"); 
lsd.Add("b", "d10a9"); 
string max = lsd.Max(kvp => kvp.Value); 

Как я могу получить значение Max идентификаторов с самой длинной строки в сочетании?

+0

Ваша проблема в том, что если отсортирован в алфавитном, d10a10 приходит первым, потому что 1 сортируется перед тем 9. Если d4a13 отсортирован перед оба они по вашей логике? –

+2

является первой частью идентификатора всегда «d10a»? – Tsar

+0

Прежде всего, длина важна x1 доходит до a10, вторая может быть любой комбинацией, нет префикса, такого как d10a –

ответ

1

Одним из способов были бы сделать это

string max =lsd.Where(kvp=>kvp.Value.Length==lsd.Max(k=>k.Value.Length)).Max(kvp => kvp.Value); 

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

int maxLength=lsd.Max(kvp=>kvp.Value.Length); 
string max = lsd.Where(kvp=>kvp.Value.Length == maxLength).Max(kvp => kvp.Value); 

Если у вас есть нулевые строки, вам может потребоваться также выполнить нулевые проверки

int maxLength=lsd.Max(kvp=>(kvp.Value??String.Empty).Length); 
string max = lsd.Where(kvp=>(kvp.Value??String.Empty).Length == maxLength).Max(kvp => kvp.Value); 

Альтернативно обрабатывайте свою строку как номер Base36 и конвертируйте ее в режим максимальной функции, а затем конвертируйте обратно, чтобы получить максимальную строку.

string max =lsd.Max(tvp=>tvp.Value.FromBase36()).ToBase36(); 

public static class Base36 { 

    public static long FromBase36(this string src) { 
    return src.ToLower().Select(x=>(int)x<58 ? x-48 : x-87).Aggregate(0L,(s,x)=>s*36+x); 
    } 

    public static string ToBase36(this long src) { 
    StringBuilder result=new StringBuilder(); 
    while(src>0) { 
     var digit=(int)(src % 36); 
     digit=(digit<10) ? digit+48 :digit+87; 
     result.Insert(0,(char)digit); 
     src=src/36; 
     } 
    return result.ToString(); 
    } 
} 

Наконец только лишь метод расширения Agregate вместо Max, так как это позволяет сделать всю логику сравнения ....

lsd.Agregate(string.Empty,(a,b)=> a.Length == b.Length ? (a>b ? a:b) : (a.Length>b.Length ? a:b)); 

Это может не иметь нулевые чеки, но вы легко можете добавить их в.

+0

работает как шарм :-) –

3

Я думаю, вы можете попробовать свернуть свой собственный IComparer<string>

class HumanSortComparer : IComparer<string> 
{ 
    public int Compare(string x, string y) 
    { 
     // your human sorting logic here 
    } 
} 

Использование:

var last = collection.OrderBy(x => x.Value, new HumanSortComparer()).LastOrDefault(); 
if (last != null) 
    string max = last.Value; 
+1

Это правильно, но только для уточнения OrderByDescending и FirstOrDefault будут работать так же хорошо. –

+0

см. (Краткое исследование эффективности LINQ) [http://msmvps.com/blogs/jon_skeet/archive/2005/10/02/a-short-case-study-in-linq-efficiency.aspx] Порядок/firstorDefault не очень эффективна. –

0

Я думаю, что если вы сделали это:

var max = lsd.OrderByDescending(x => x.Value) 
    .GroupBy(x => x.Value.Length) 
    .OrderByDescending(x => x.Key) 
    .SelectMany(x => x) 
    .FirstOrDefault(); 

Это может дать вам то, что вы хотеть.

2

это работает как шарм предполагающей идентификаторы всегда начинаются с "D10A":

int max = lsd.Max(kvp => Convert.ToInt32(kvp.Value.Substring(4))); 
Console.Write(string.Format("d10a{0}", max)); 
+0

Или 'lsd.Select (kvp => kvp.Value.Substring (4)). Выберите (i => Int32.Parse (i)). Max()', чтобы сделать его более читаемым – abatishchev

0

Вам нужно StringComparer.OrdinalIgnoreCase.

Без необходимости использовать linq, функция, которая делает это довольно просто. Сложность, конечно, O (n).

public static KeyValuePair<string, string> FindMax(IEnumerable<KeyValuePair<string, string>> lsd) 
    { 
     var comparer = StringComparer.OrdinalIgnoreCase; 
     var best = default(KeyValuePair<string, string>); 
     bool isFirst = true; 
     foreach (KeyValuePair<string, string> kvp in lsd) 
     { 
      if (isFirst || comparer.Compare(kvp.Value, best.Value) > 0) 
      { 
       isFirst = false; 
       best = kvp; 
      } 
     } 
     return best; 
    } 
0

ОК - Я думаю, вам нужно сначала превратить каждый ключ в ряд строк и цифр - так как вам нужно все число, чтобы определить сравнение. Затем вы реализуете IComparer - я тестировал это с помощью двух входных строк, а также с несколькими другими, и он, похоже, делает то, что вы хотите. Возможно, производительность может быть улучшена - но я был мозговой штурм!

Создать этот класс:

public class ValueChain 
{ 
    public readonly IEnumerable<object> Values; 
    public int ValueCount = 0; 

    private static readonly Regex _rx = 
    new Regex("((?<alpha>[a-z]+)|(?<numeric>([0-9]+)))", 
     RegexOptions.Compiled | RegexOptions.IgnoreCase); 

    public ValueChain(string valueString) 
    { 
    Values = Parse(valueString); 
    } 

    private IEnumerable<object> Parse(string valueString) 
    { 
    var matches = _rx.Matches(valueString); 
    ValueCount = matches.Count; 

    foreach (var match in matches.Cast<Match>()) 
    { 
     if (match.Groups["alpha"].Success) 
     yield return match.Groups["alpha"].Value; 
     else if (match.Groups["numeric"].Success) 
     yield return int.Parse(match.Groups["numeric"].Value); 
    } 
    } 
} 

Сейчас этот компаратор:

public class ValueChainComparer : IComparer<ValueChain> 
{ 
    private IComparer<string> StringComparer; 
    public ValueChainComparer() 
    : this(global::System.StringComparer.OrdinalIgnoreCase) 
    { 

    } 

    public ValueChainComparer(IComparer<string> stringComparer) 
    { 
    StringComparer = stringComparer; 
    } 

    #region IComparer<ValueChain> Members 

    public int Compare(ValueChain x, ValueChain y) 
    { 
    //todo: null checks 
    int comparison = 0; 
    foreach (var pair in x.Values.Zip 
     (y.Values, (xVal, yVal) => new { XVal = xVal, YVal = yVal })) 
    { 
     //types match? 
     if (pair.XVal.GetType().Equals(pair.YVal.GetType())) 
     { 
     if (pair.XVal is string) 
      comparison = StringComparer.Compare(
      (string)pair.XVal, (string)pair.YVal); 
     else if (pair.XVal is int) //unboxing here - could be changed 
      comparison = Comparer<int>.Default.Compare(
      (int)pair.XVal, (int)pair.YVal); 
     if (comparison != 0) 
      return comparison; 
     } 
     else //according to your rules strings are always greater than numbers. 
     { 
     if (pair.XVal is string) 
      return 1; 
     else 
      return -1; 
     } 
    } 

    if (comparison == 0) //ah yes, but were they the same length? 
    { 
     //whichever one has the most values is greater 
     return x.ValueCount == y.ValueCount ? 
     0 : x.ValueCount < y.ValueCount ? -1 : 1; 
    } 

    return comparison; 
    } 

    #endregion 
} 

Теперь вы можете получить максимум с помощью OrderByDescending на IEnumerable<ValueChain> и FirstOrDefault:

[TestMethod] 
public void TestMethod1() 
{ 
    List<ValueChain> values = new List<ValueChain>(new [] 
          { 
           new ValueChain("d10a9"), 
           new ValueChain("d10a10") 
          }); 

    ValueChain max = 
    values.OrderByDescending(v => v, new ValueChainComparer()).FirstOrDefault(); 
} 

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

var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value), 
         new ValueChainComparer()).FirstOrDefault(); 
Смежные вопросы