数字混じり文字列ソート

で、なんで、Char.IsNumberについて見てたのかといえば、

数字混じり文字列ソート - どう書く?org

というか、

数字混じり文字列ソート ~ Rubyとの血みどろの闘い ~

が気になったから。
これ、どう見ても、正規表現を使い始めたら、.NETに勝ち目はない。Regex クラスの使い勝手の悪さに対処しているだけで精一杯。
そして、Linq的な処理を考えようにも、IEnumerable.Compareみたいなのがあれば良いんだけど、そういうのもないし。Enumerable.SequenceEqualは等値のチェックしか出来ない。あったところで、その後が続かない。

結局、昔ながらの文字解析を行った方が順当っぽい。
ということで、Linqスタイルとか、そういうのを全部無視して、素直にArray.Sortに渡す比較関数を考えてみることに。

static bool isDigit(Char c)
{
  return c >= '0' && c <= '9';
}

static int extractNumber(String a, ref int pos)
{
  int end = pos;
  while (end < a.Length && isDigit(a[end]))
    end++;
  int ret = int.Parse(a.Substring(pos, end - pos));
  pos = end;
  return ret;
}

static int compare(String a, String b)
{
  int iA = 0, iB = 0;
  int lenA = a.Length;
  int lenB = b.Length;
  while (iA < lenA && iB < lenB)
  {
    Char cA = a[iA], cB = b[iB];
    if (!isDigit(cA) || !isDigit(cB))
    {
      int d = (int)cA - (int)cB;
      if (d != 0)
        return d;
    }
    else
    {
      int vA = extractNumber(a, ref iA);
      int vB = extractNumber(b, ref iB);
      int d = vA - vB;
      if (d != 0)
        return d;
    }
    iA++;
    iB++;
  }
  return (lenA - iA) - (lenB - iB);
}

うーん、パフォーマンスは出そうだけど、微妙なコードだなぁ。

Char.IsNumber/GetNumericValue

【VB.NET】指定した文字が数字かどうか判定する(IsDigit, IsNumber, IsNumeric)を見ていて、Char.GetNumericValueとint.Parse/TryParseに違いがあるのかどうかが気になったので試してみた。

using System;

class NumericTest
{
  public static void Main()
  {
    Console.OutputEncoding = System.Text.Encoding.UTF8;
    
    Char[] num = {
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '鄯', '鄱', '鄴', '?', '?', '?', '?', '?', '&#12832;', '&#12833;',
      '&#12841;', '&#9332;', '&#9351;', '&#10102;', '&#10111;', '&#10112;', '&#10121;', '&#12928;', '&#12929;', '&#12937;',
      '&#9352;', '&#9371;', '&#10122;', '&#10131;', '&#185;', '&#8313;', '&#8321;', '&#8329;', '〇', '一', '九',
      '零', '壱', '弐', '玖'};
      
    foreach (Char c in num)
    {
      int v;
      if (!int.TryParse(c.ToString(), out v))
        v = -1;
      
      Console.WriteLine(
        "{0}\tIsDigit:{1}\tIsNumber:{2}\tGetNumericValue:{3}\tParse:{4}",
        c, Char.IsDigit(c), Char.IsNumber(c),
        Char.GetNumericValue(c), v);
    }
  }
}

はてながUnicode対応じゃないせいで、見にくいことになってるけど、本当は、

f:id:espresso3389:20111030184307p:image

結論から言うと、おそらく、Char.IsDigitがtrueな場合には、int.Parseも動く。一方で、Char.IsNumberがtrueでも、int.Parseできるとは限らないが、Char.GetNumericValueは数値の値を返してくれるようだ。
ただ、じゃぁ、数値解析ルーチンは、Char.GetNumericValueを使えば良いかといえば、こいつは、漢数字とかローマ数字まで相手にしてしまうので、状況に応じて判断するしかないだろう。

全角の数字文字列を処理したいだけだったら、NFKD・NFKCあたりで正規化してからint.Parseするのが良いかも。

と思ったけど、やっぱり、Char.IsDigitでチェックして、int.Parseというのには問題があるようだ。

http://blogs.msdn.com/b/oldnewthing/archive/2004/03/09/86555.aspx

結局、

static bool isDigit(Char c) { return c >= '0' && c <= '9'; }

なんていうベタなのが順当らしい・・・。