【问题标题】:Order alphanumeric string numerically, and then by prefix/suffix按数字顺序排列字母数字字符串,然后按前缀/后缀
【发布时间】:2018-09-24 15:16:10
【问题描述】:

我有一个复杂的排序模式要复制,我的解决方案似乎有点笨拙。 我的输入是一个数字列表,可以有几个字母作为后缀,前缀都是字母('aaa'、'aab'、'ac' 等)。 我需要按数字排序,然后按后缀(如果有的话)排序,然后按前缀(如果有的话)。

例如

"a1a",
"5ac",
"1",
"12",
"2",
"11",
"5aa",
"3",
"5ab",
"a2b",
"abb11ca",
"1b",
"aba11ca"

将被排序为

1
a1a
1b
2
a2b
3
5aa
5ab
5ac
11
aba11ca
abb11ca
12

这是我使用 Linq 提出的解决方案。

static void Main(string[] args)
{
    var arr = new []
    {"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca"
    };
    var ordered = arr.Select(str => {
                var parts = SplitIntoPrefixNumberSuffix(str);
                var number = int.Parse(parts[1]);
                return new { str, parts, number };
            })
            .OrderBy(x => x.number).ThenBy(x => x.parts[2]).ThenBy(x => x.parts[0])
            .Select(x => x.str);

    Console.WriteLine("sorted array: ");
    foreach (var s in ordered)
    {
        Console.WriteLine("{0}", s);
    }

    Console.ReadLine();
}      

public static string[] SplitIntoPrefixNumberSuffix(string str)
{
    var numChar = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    var numLoc = str.IndexOfAny(numChar);
    var nums = "";
    foreach (var c in str)
    {
        if (char.IsDigit(c))
            nums = nums + c;
    }
    Console.WriteLine("numLoc: {0}; nums: {1}", numLoc, nums.Count());
    var prefix = str.Substring(0, numLoc);
    var suffix = str.Substring(numLoc + nums.Count());
    Console.WriteLine("prefix {0}; nums {1}; suffix {2}", prefix, nums, suffix);
    return new[] { prefix, nums, suffix };
}

这是一个工作的 .netfiddle:https://dotnetfiddle.net/C7ZA0b

虽然它有效,但感觉这不是一个很好的解决方案。我对集合进行了多次迭代,我认为我应该使用自定义的可比较对象。

我以前从未写过 Comparable;我查看了Dot Net Pearls Alphanumeric Sorting 并且可以按照它进行操作,但还不足以对其进行修改以满足我的需要。

我可以使用 IComparable 来完成上述工作吗?有什么学习写作的好地方的建议吗?

【问题讨论】:

  • 请定义“前缀”和“后缀”。
  • “ab11c22d”如何工作?
  • 我数不清我说过“这不是一个很好的解决方案”的次数,但还是检查了代码。如果它有效,那么为什么还要将逻辑移植到IComparable?老实说,我认为您不会因此而获得任何收益。请务必编写一些单元测试以使其正常工作。
  • 前缀和后缀都是字母,在“中心”数值上添加和添加。我会更新我的问题以澄清。
  • "b2" 在您的示例中丢失。

标签: c# .net linq sorting


【解决方案1】:

因此您可以使用正则表达式命名组来拆分字符串的各个组件,然后按每个组件排序:

var regex = new Regex(@"^(?<pre>\D*)(?<num>\d+)(?<suff>\D*)$");
var ordered = data.Select(d => (match: regex.Match(d), value: d))
    .Where(x => x.match.Success) //throw away anything that doesn't conform
    .Select(x => (
        x.value, 
        pre: x.match.Groups["pre"].Value, 
        num: int.Parse(x.match.Groups["num"].Value), 
        suff: x.match.Groups["suff"].Value))
    .OrderBy(x => x.num)
    .ThenBy(x => x.suff)
    .ThenBy(x => x.pre)
    .Select(x => x.value);

...但最终这与您的解决方案并没有什么不同。我真的看不出专门的IComparer 会如何简化这一点。

如果您没有可用的元组 (

data.Select(d => new { match = regex.Match(d), value = d})
    .Where(x => x.match.Success)
    .Select(x => new { 
        x.value, 
        pre = x.match.Groups["pre"].Value, 
        num = int.Parse(x.match.Groups["num"].Value), 
        suff = x.match.Groups["suff"].Value})
    .OrderBy(x => x.num)
    .ThenBy(x => x.suff)
    .ThenBy(x => x.pre)
    .Select(x => x.value)

【讨论】:

  • 我得到一个 System.ValueTuple'2' is not defined 匹配/值元组的错误。必须通过 NuGet 导入该类型。
  • @red_dorian 您使用的是什么版本的 C#/.Net 运行时?这使用 C# valuetuple,但仅在最新版本中受支持。元组可以换成匿名类。查看我的编辑。
  • 我在 4.7.1。最后一行x.value 也出现语言版本错误。推断元组元素名称“值”。
  • @red_dorian IIRC 元组至少需要 C#7.0。
  • 刚刚看到您对匿名类的编辑,谢谢!
【解决方案2】:

作为选项,您可以实现自己的自定义比较器

public class CustomStringComparer : IComparer<string>
{
    public int Compare(string first, string second)
    {
        var compareByCore = CompareCore(first, second);
        var compareBySuffix = CompareSuffix(first, second);
        var compareByPrefix = ComparePrefix(first, second);

        return compareByCore != 0 ? compareByCore 
            : compareBySuffix != 0 ? compareBySuffix
            : compareByPrefix;
    }

    private int CompareCore(string a, string b)
    {
        var firstCoreNumber = Regex.Match(a, @"\d+").Value;
        var secondCoreNumber = Regex.Match(b, @"\d+").Value;

        if (!string.IsNullOrEmpty(firstCoreNumber) && !string.IsNullOrEmpty(secondCoreNumber))
        {
            return int.Parse(firstCoreNumber).CompareTo(int.Parse(secondCoreNumber));
        }

        return 0;
    }

    private int CompareSuffix(string a, string b)
    {
        var firstSuffix = Regex.Match(a, @"\D+$").Value;
        var secondSuffix = Regex.Match(b, @"\D+$").Value;

        return firstSuffix.CompareTo(secondSuffix);
    }

    private int ComparePrefix(string a, string b)
    {
        var firstPrefix = Regex.Match(a, @"^\D+").Value;
        var secondPrefix = Regex.Match(b, @"^\D+").Value;

        return firstPrefix.CompareTo(secondPrefix);
    }
}

当您调用 order 方法时,只需发送此比较器的实例:

var arr = new[]
        { "a1a", "5ac", "1", "12", "2", "11", "5aa", "3", "5ab", "a2b", "abb11ca", "1b", "aba11ca" };

    var sortedArr = arr.OrderBy(x => x, new CustomStringComparer());

    foreach (var s in sortedArr)
    {
        Console.Write($"{s} ");
    }

【讨论】:

    【解决方案3】:

    作为我现有答案的替代方案,我不久前写了一个 ComparerBuilder&lt;T&gt; 帮助程序,它可以编写一些不错的客户端代码。

    现在你可以:

    var comparer = new ComparerBuilder<string>()
        .SortKey(k => int.Parse(Regex.Match(k, @"\d+").Value))
            .ThenKey(k => Regex.Match(k, @"\D*$").Value)
            .ThenKey(k => Regex.Match(k, @"^\D*").Value)
            .Build();
    
    var ordered = data.OrderBy(x => x, comparer);
    

    【讨论】:

      【解决方案4】:

      这是Dave Koelle的字母数字IComparerimplementation

      编辑:添加代码示例。

      void Main()
      {
          var arr = new[] {"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca" };
      
          var items = arr.OrderBy(x => x.ToString(), new AlphanumComparator()).ToList();
      
          Console.WriteLine("sorted array: ");
          foreach (var s in items)
          {
              Console.WriteLine("{0}", s);
          }
      }
      
      public class AlphanumComparator : IComparer<object>
      {
          private enum ChunkType { Alphanumeric, Numeric };
          private bool InChunk(char ch, char otherCh)
          {
              ChunkType type = ChunkType.Alphanumeric;
      
              if (char.IsDigit(otherCh))
              {
                  type = ChunkType.Numeric;
              }
      
              if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
                  || (type == ChunkType.Numeric && !char.IsDigit(ch)))
              {
                  return false;
              }
      
              return true;
          }
      
          public int Compare(object x, object y)
          {
              String s1 = x as string;
              String s2 = y as string;
              if (s1 == null || s2 == null)
              {
                  return 0;
              }
      
              int thisMarker = 0, thisNumericChunk = 0;
              int thatMarker = 0, thatNumericChunk = 0;
      
              while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
              {
                  if (thisMarker >= s1.Length)
                  {
                      return -1;
                  }
                  else if (thatMarker >= s2.Length)
                  {
                      return 1;
                  }
                  char thisCh = s1[thisMarker];
                  char thatCh = s2[thatMarker];
      
                  StringBuilder thisChunk = new StringBuilder();
                  StringBuilder thatChunk = new StringBuilder();
      
                  while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
                  {
                      thisChunk.Append(thisCh);
                      thisMarker++;
      
                      if (thisMarker < s1.Length)
                      {
                          thisCh = s1[thisMarker];
                      }
                  }
      
                  while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
                  {
                      thatChunk.Append(thatCh);
                      thatMarker++;
      
                      if (thatMarker < s2.Length)
                      {
                          thatCh = s2[thatMarker];
                      }
                  }
      
                  int result = 0;
                  // If both chunks contain numeric characters, sort them numerically
                  if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
                  {
                      thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
                      thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
      
                      if (thisNumericChunk < thatNumericChunk)
                      {
                          result = -1;
                      }
      
                      if (thisNumericChunk > thatNumericChunk)
                      {
                          result = 1;
                      }
                  }
                  else
                  {
                      result = thisChunk.ToString().CompareTo(thatChunk.ToString());
                  }
      
                  if (result != 0)
                  {
                      return result;
                  }
              }
      
              return 0;
          }
      }
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-20
      • 1970-01-01
      • 2020-09-03
      • 1970-01-01
      • 2022-01-01
      • 1970-01-01
      相关资源
      最近更新 更多