質問

文字またはintの文字列表現(最大2桁)を含むことができる文字列のリストがあります。 それらは、アルファベット順または(実際にはintの場合)数値でソートする必要があります。

例:

IList<string> input = new List<string>()
    {"a", 1.ToString(), 2.ToString(), "b", 10.ToString()};

input.OrderBy(s=>s)
  // 1
  // 10
  // 2
  // a
  // b

欲しいのは

  // 1
  // 2
  // 10
  // a
  // b

私はそれを解析しようとしてフォーマットすることを含むいくつかのアイデアを持っています、そしてそれが成功するなら、私の独自のカスタム文字列フォーマッターでフォーマットを試みて、それが先行ゼロを持つようにします。もっとシンプルでパフォーマンスの良いものを期待しています。

編集
後で使用するためにUtilsライブラリにダンプしたIComparerを作成しました。
その間、私もミックスでダブルを投げました。

public class MixedNumbersAndStringsComparer : IComparer<string> {
    public int Compare(string x, string y) {
        double xVal, yVal;

        if(double.TryParse(x, out xVal) && double.TryParse(y, out yVal))
            return xVal.CompareTo(yVal);
        else 
            return string.Compare(x, y);
    }
}

//Tested on int vs int, double vs double, int vs double, string vs int, string vs doubl, string vs string.
//Not gonna put those here
[TestMethod]
public void RealWorldTest()
{
    List<string> input = new List<string>() { "a", "1", "2,0", "b", "10" };
    List<string> expected = new List<string>() { "1", "2,0", "10", "a", "b" };
    input.Sort(new MixedNumbersAndStringsComparer());
    CollectionAssert.AreEquivalent(expected, input);
}
役に立ちましたか?

解決

より一般的なアプローチで、自然なC#実装などのソートアルゴリズム こちら

他のヒント

2つの方法が思い浮かびますが、どちらがパフォーマンスが良いかはわかりません。カスタムIComparerを実装します:

class MyComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        int xVal, yVal;
        var xIsVal = int.TryParse( x, out xVal );
        var yIsVal = int.TryParse( y, out yVal );

        if (xIsVal && yIsVal)   // both are numbers...
            return xVal.CompareTo(yVal);
        if (!xIsVal && !yIsVal) // both are strings...
            return x.CompareTo(y);
        if (xIsVal)             // x is a number, sort first
            return -1;
        return 1;               // x is a string, sort last
    }
}

var input = new[] {"a", "1", "10", "b", "2", "c"};
var e = input.OrderBy( s => s, new MyComparer() );

または、シーケンスを数字と数字以外に分割してから、各サブグループを並べ替え、最後に並べ替えた結果を結合します。次のようなもの:

var input = new[] {"a", "1", "10", "b", "2", "c"};

var result = input.Where( s => s.All( x => char.IsDigit( x ) ) )
                  .OrderBy( r => { int z; int.TryParse( r, out z ); return z; } )
                  .Union( input.Where( m => m.Any( x => !char.IsDigit( x ) ) )
                               .OrderBy( q => q ) );

IComparer パラメータを取る OrderBy の他のオーバーロードを使用します。

その後、 int.TryParse を使用して数値かどうかを判断する独自の IComparer を実装できます。

RegularExpressionを使用して値を分割し(すべてがintであると仮定)、それらを再結合できると思います。

//create two lists to start
string[] data = //whatever...
List<int> numbers = new List<int>();
List<string> words = new List<string>();

//check each value
foreach (string item in data) {
    if (Regex.IsMatch("^\d+<*>quot;, item)) {
        numbers.Add(int.Parse(item));
    }
    else {
        words.Add(item);
    }
}

次に、2つのリストを使用して、それぞれを並べ替えてから、必要な形式でそれらを結合します。

Win32 APIで提供される関数を使用できます。

[DllImport ("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW (String x, String y);

他の人が示したように IComparer から呼び出します。

public static int? TryParse(string s)
{
    int i;
    return int.TryParse(s, out i) ? (int?)i : null;
}

// in your method
IEnumerable<string> input = new string[] {"a", "1","2", "b", "10"};
var list = input.Select(s => new { IntVal = TryParse(s), String =s}).ToList();
list.Sort((s1, s2) => {
    if(s1.IntVal == null && s2.IntVal == null)
    {
        return s1.String.CompareTo(s2.String);
    }
    if(s1.IntVal == null)
    {
        return 1;
    }
    if(s2.IntVal == null)
    {
        return -1;
    }
    return s1.IntVal.Value.CompareTo(s2.IntVal.Value);
});
input = list.Select(s => s.String);

foreach(var x in input)
{
    Console.WriteLine(x);
}

まだ変換を行いますが、1回/アイテムのみです。

カスタム比較演算子を使用できます-順序ステートメントは次のようになります。

var result = input.OrderBy(s => s, new MyComparer());

MyComparerは次のように定義されています:

public class MyComparer : Comparer<string>
{
    public override int Compare(string x, string y)
    {

        int xNumber;
        int yNumber;
        var xIsNumber = int.TryParse(x, out xNumber);
        var yIsNumber = int.TryParse(y, out yNumber);

        if (xIsNumber && yIsNumber)
        {
            return xNumber.CompareTo(yNumber);
        }
        if (xIsNumber)
        {
            return -1;
        }
        if (yIsNumber)
        {
            return 1;
        }
        return x.CompareTo(y);
    }
}

これは少し冗長に見えるかもしれませんが、ソートロジックを適切な型にカプセル化します。その後、必要に応じて、Comparerを簡単に自動テスト(ユニットテスト)にかけることができます。また、再利用可能です。

(アルゴリズムを少し明確にすることも可能かもしれませんが、これは私がすぐに一緒に投げることができる最高のものでした。)

「チート」することもできます。ある意味で。問題の説明に基づいて、長さ2の文字列は数字になることがわかっています。したがって、長さ1のすべての文字列を並べ替えてから、長さ2のすべての文字列を並べ替えてから、一連のスワッピングを実行して、正しい順序で文字列を並べ替えます。基本的に、プロセスは次のように機能します:(データが配列にあると仮定します。)

手順1:長さ2のすべての文字列を配列の最後にプッシュします。所有者数を追跡します。

ステップ2:長さ1の文字列と長さ2の文字列をインプレースソートします。

ステップ3:2つの半分の境界にある「a」のバイナリ検索。

ステップ4:必要に応じて、2桁の文字列を文字と交換します。

とはいえ、このアプローチは機能しますが、正規表現を使用せず、int以外の値をintとして解析しようとはしません-お勧めしません。既に提案されている他のアプローチよりも大幅に多くのコードを記述します。あなたがやろうとしていることの要点を分かりにくくします。突然2文字の文字列または3桁の文字列を取得した場合は機能しません。等。私はそれを含めて、あなたがどのように問題を異なって見ることができるかを示し、代替ソリューションを考え出す。

シュワルツ変換を使用して、O(n)変換を実行します。

private class Normalized : IComparable<Normalized> {
  private readonly string str;
  private readonly int val;

  public Normalized(string s) {
    str = s;

    val = 0;
    foreach (char c in s) {
      val *= 10;

      if (c >= '0' && c <= '9')
        val += c - '0';
      else
        val += 100 + c;
    }
  }

  public String Value { get { return str; } }

  public int CompareTo(Normalized n) { return val.CompareTo(n.val); }
};

private static Normalized In(string s) { return new Normalized(s); }
private static String Out(Normalized n) { return n.Value; }

public static IList<String> MixedSort(List<String> l) {
  var tmp = l.ConvertAll(new Converter<String,Normalized>(In));
  tmp.Sort();
  return tmp.ConvertAll(new Converter<Normalized,String>(Out));
}

同様の問題があり、ここに着陸しました。次の例のように、数値の接尾辞を持つ文字列をソートします。

オリジナル:

"Test2", "Test1", "Test10", "Test3", "Test20"

デフォルトのソート結果:

"Test1", "Test10", "Test2", "Test20", "Test3"

望ましいソート結果:

"Test1", "Test2", "Test3, "Test10", "Test20"

最終的にカスタムComparerを使用しました:

public class NaturalComparer : IComparer
{

    public NaturalComparer()
    {
        _regex = new Regex("\\d+<*>quot;, RegexOptions.IgnoreCase);
    }

    private Regex _regex;

    private string matchEvaluator(System.Text.RegularExpressions.Match m)
    {
        return Convert.ToInt32(m.Value).ToString("D10");
    }

    public int Compare(object x, object y)
    {
        x = _regex.Replace(x.ToString, matchEvaluator);
        y = _regex.Replace(y.ToString, matchEvaluator);

        return x.CompareTo(y);
    }
}   

HTH; o)

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top