开发者

Get Max() of alphanumeric value

开发者 https://www.devze.com 2023-04-01 14:56 出处:网络
I have a dictionary containg ID which are alphanumeric (e.g开发者_Python百科. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...

I have a dictionary containg ID which are alphanumeric (e.g开发者_Python百科. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...

When I use the following code, d10a9 is MAX since 9 is sorted before 10

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

How can I get the Max value of the IDs with the Longest string combined?


I think you may try to roll your own IComparer<string>

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

Usage:

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


this works like a charm assuming IDs always start with "d10a":

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


One way would be to do this

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

however I think that this method would evalute the max length for each item so you may be better to extract it to a variable first

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

If you are going to have null strings in there you may need to perform null checks too

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

Alternatively treat your string as Base36 number and convert to long for the max function and then convert back again to get the max string.

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

Finally just just the Agregate extension method instead of Max as this lets you do all the comparison logic....

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

This could doesn't have null checks but you easily add them in.


I think if you did this:

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

It may give you what you want.


You need StringComparer.OrdinalIgnoreCase.

Without the need to use linq, the function that do that is quite simple. Complexity is, of course, 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;
    }


Okay - I think you need to first turn each key into a series of strings and numbers - since you need the whole number to be able to determine the comparison. Then you implement an IComparer - I've tested this with your two input strings as well as with a few others and it appears to do what you want. The performance could possibly be improved - but I was brainstorming it!

Create this class:

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

Now this comparer:

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
}

Now you can get the max using OrderByDescending on an IEnumerable<ValueChain> and 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();
}

So you can use this to sort the string values in your dictionary:

var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value), 
                      new ValueChainComparer()).FirstOrDefault();
0

精彩评论

暂无评论...
验证码 换一张
取 消