文字列が数値の場合、値を考慮しながら文字列をアルファベット順にソートするにはどうすればよいですか?


100

文字列である数値の配列を並べ替えようとしています。数値で並べ替えてください。

問題は、数値をintに変換できないことです。です。

これがコードです:

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => x))
{
    Console.WriteLine(thing);
}

出力:101、102、103、105、90

希望:90、101、102、103、105

編集:出力を090、101、102にすることはできません...

「サイズ」の代わりに「もの」を言うようにコードサンプルを更新しました。配列は次のようになります。

string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };

つまり、アルファベット順および番号順に並べ替える必要があります。

007、90、ボブ、ローレン、ポール


8
それらをintに変換できないのはなぜですか?
Femaref

1
「サイズ」は「名前」のような他の何かにすることができます。コードサンプルは単純化されています。
sf。

2
負の数値はありますか?それらはすべて整数になりますか?整数の範囲は何ですか?
Eric Lippert、2011年

「モノ」は、どのような種類の文字列でもかまいません。リストを、コンピューターに詳しくない人が論理的にソートできるようにしてください。負の数は正の前でなければなりません。文字列の長さに関しては、100文字を超えることはできません。
sf。

5
どこまで行きたいですか?image10後に来る必要がありimage2ますか?January前に来るべきFebruaryですか?
11年

回答:


104

カスタムコンパレータをOrderByに渡します。Enumerable.OrderByすると、任意の比較演算子を指定できます。

これはそのための1つの方法です。

void Main()
{
    string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};

    foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
    {    
        Console.WriteLine(thing);
    }
}


public class SemiNumericComparer: IComparer<string>
{
    /// <summary>
    /// Method to determine if a string is a number
    /// </summary>
    /// <param name="value">String to test</param>
    /// <returns>True if numeric</returns>
    public static bool IsNumeric(string value)
    {
        return int.TryParse(value, out _);
    }

    /// <inheritdoc />
    public int Compare(string s1, string s2)
    {
        const int S1GreaterThanS2 = 1;
        const int S2GreaterThanS1 = -1;

        var IsNumeric1 = IsNumeric(s1);
        var IsNumeric2 = IsNumeric(s2);

        if (IsNumeric1 && IsNumeric2)
        {
            var i1 = Convert.ToInt32(s1);
            var i2 = Convert.ToInt32(s2);

            if (i1 > i2)
            {
                return S1GreaterThanS2;
            }

            if (i1 < i2)
            {
                return S2GreaterThanS1;
            }

            return 0;
        }

        if (IsNumeric1)
        {
            return S2GreaterThanS1;
        }

        if (IsNumeric2)
        {
            return S1GreaterThanS2;
        }

        return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
    }
}

1
与えられた入力に対して、これはPadLeft()を含む再帰の答えと同じ結果を生成します。あなたの入力は実際にはこの例が示すよりも複雑であると想定しています。その場合、カスタムコンパレーターが適しています。
ジェフポールセン2011年

乾杯。このソリューションは機能し、実装するための読みやすくクリーンな方法のようです。OrderByでIComparerを使用できることを示すための+1 :)
sf。

17
IsNumericこの方法は、コーディング駆動例外は常に悪い、悪いです。int.TryParse代わりに使用してください。大きなリストを使用してコードを試してください。
Nean Der Thal、2015年

それが役に立つならば、私はこのバージョンに拡張機能を追加し、ここで言葉でソートするためのサポートを追加します。私のニーズでは、スペースでの分割で十分であり、混合使用の単語(たとえば、test12とtest3)について心配する必要はほとんどありませんでした
matt.bungard

@NeanDerThalデバッグしている場合やExceptionオブジェクトにアクセスしている場合は、ループ内の多くの例外の処理が遅い/悪いだけだと確信しています。
ケリーエルトン

90

同じ長さにゼロで埋めるだけです:

int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));

1簡単な解決策のために、つべこべは(すでに編集で行われ、素敵な)う
マリノシミック

いい考えですが、次の問題は、これらの値を表示する必要があるため、「90」を「090」ではなく「90」にする必要があるということ
です

6
@sf:試してみてください。結果が気になるかもしれません。注文キーは注文されるものではありません。姓で顧客のリストを注文する場合、姓のリストではなく、顧客のリストが表示されます。変換された文字列で文字列のリストを並べ替えると、結果は、変換された文字列ではなく、元の文字列の並べられたリストになります。
Eric Lippert、2011年

これを機能させるには、「sizes = sizes.OrderBy(...)」を追加する必要がありました。それは正常ですか、それとも回答を編集する必要がありますか?
gorgabal

1
@gorgabal:sizes結果が別の型であるため、一般にへの再割り当ても機能しません。2行目は結果を式として表示しますが、それを使って何かを行うのは読者次第です。それをより明確にするために、別の変数割り当てを追加しました。
再帰的

74

そして、これは...

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

var size = from x in sizes
           orderby x.Length, x
           select x;

foreach (var p in size)
{
    Console.WriteLine(p);
}

へへ、私はこれが本当に好きです-とても賢いです。初期データの完全なセットを提供しなかった場合は申し訳ありません
sf。

3
これは、上記のパッドオプションと同じで、IMOがはるかに優れています。
dudeNumber4 2013

3
var size = sizes.OrderBy(x => x.Length).ThenBy(x => x);
フィリップデイビス

1
しかし、これは次のようにアルファベット文字列を混合します:"b", "ab", "101", "103", "bob", "abcd"
アンドリュー

67

値は文字列です

List = List.OrderBy(c => c.Value.Length).ThenBy(c => c.Value).ToList();

作品


2
この答えは私のお気に入りです。
LacOniC 2016年

2
ありがとう、「ThenBy」メソッドが終了していることがわかりました。
ganchito55 2016年

これは、入力がnewstring[] { "Object 1", "Object 9", "Object 14" }
thelem

2
これが最良の答えです。それは機能し、良い学習です。ありがとう!!
7

1
しかし、これは次のようにアルファベット文字列を混合します:"b", "ab", "101", "103", "bob", "abcd"
アンドリュー

13

ウィンドウStrCmpLogicalWでは、文字列ではなく文字ではなく数字として数値を比較するネイティブ関数があります。その関数を呼び出し、それを比較に使用する比較演算子を作成するのは簡単です。

public class StrCmpLogicalComparer : Comparer<string>
{
    [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string x, string y);

    public override int Compare(string x, string y)
    {
        return StrCmpLogicalW(x, y);
    }
}

テキストと数字の両方を持つ文字列でも機能します。ここではデフォルトの並べ替えとの間diffrence表示されます例のプログラムでStrCmpLogicalWソートが

class Program
{
    static void Main()
    {
        List<string> items = new List<string>()
        {
            "Example1.txt", "Example2.txt", "Example3.txt", "Example4.txt", "Example5.txt", "Example6.txt", "Example7.txt", "Example8.txt", "Example9.txt", "Example10.txt",
            "Example11.txt", "Example12.txt", "Example13.txt", "Example14.txt", "Example15.txt", "Example16.txt", "Example17.txt", "Example18.txt", "Example19.txt", "Example20.txt"
        };

        items.Sort();

        foreach (var item in items)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine();

        items.Sort(new StrCmpLogicalComparer());

        foreach (var item in items)
        {
            Console.WriteLine(item);
        }
        Console.ReadLine();
    }
}

出力する

Example1.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example2.txt
Example20.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt

Example1.txt
Example2.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example20.txt

C#でシステムライブラリを使用する方が簡単だったらいいのに
カイルデラニー

これは完璧だったでしょうが、残念ながら負の数は処理しません。-1 0 10 2次のようにソートされます0 -1 2 10
nphx

5

これを試して

sizes.OrderBy(x => Convert.ToInt32(x)).ToList<string>();

注:これは、すべて文字列をintに変換できる場合に役立ちます....


1
このちょっと文字列をintに変換します。
Femaref

1
「サイズ」は非数値にすることもできます
sf。

"LINQ to SQL"の場合は、ToList()before =>sizes.ToList().OrderBy(x => Convert.ToInt32(x))
A. Morel

5

文字列に数値が含まれている場合、これははるかに優れていると思います。それがお役に立てば幸いです。

PS:パフォーマンスや複雑な文字列値についてはわかりませんが、次のようにうまくいきました:

lorem ipsum
lorem ipsum 1
lorem ipsum 2
lorem ipsum 3
...
lorem ipsum 20
lorem ipsum 21

public class SemiNumericComparer : IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        int s1r, s2r;
        var s1n = IsNumeric(s1, out s1r);
        var s2n = IsNumeric(s2, out s2r);

        if (s1n && s2n) return s1r - s2r;
        else if (s1n) return -1;
        else if (s2n) return 1;

        var num1 = Regex.Match(s1, @"\d+$");
        var num2 = Regex.Match(s2, @"\d+$");

        var onlyString1 = s1.Remove(num1.Index, num1.Length);
        var onlyString2 = s2.Remove(num2.Index, num2.Length);

        if (onlyString1 == onlyString2)
        {
            if (num1.Success && num2.Success) return Convert.ToInt32(num1.Value) - Convert.ToInt32(num2.Value);
            else if (num1.Success) return 1;
            else if (num2.Success) return -1;
        }

        return string.Compare(s1, s2, true);
    }

    public bool IsNumeric(string value, out int result)
    {
        return int.TryParse(value, out result);
    }
}

まさに私が探していたもの。ありがとうございました!
klugerama

4

配列にはintに変換できない要素を含めることができるため、数値をintに変換できないと言いますが、試行しても害はありません。

string[] things = new string[] { "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" };
Array.Sort(things, CompareThings);

foreach (var thing in things)
    Debug.WriteLine(thing);

次に、次のように比較します。

private static int CompareThings(string x, string y)
{
    int intX, intY;
    if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
        return intX.CompareTo(intY);

    return x.CompareTo(y);
}

出力:007、90、90、101、102、103、105、ボブ、ローレン、ポール


ところで、私は簡単にするためにArray.Sortを使用しましたが、IComparerで同じロジックを使用してOrderByを使用することもできます。
Ulf Kristiansen 2014年

このソリューションは、IComparerを使用するよりも高速に見えます(私の意見)。15000の結果で、これにより2番目の違いが生じると感じています。
Jason Foglia

3

これは奇妙なリクエストのようであり、奇妙な解決策に値します:

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

foreach (var size in sizes.OrderBy(x => {
    double sum = 0;
    int position = 0;
    foreach (char c in x.ToCharArray().Reverse()) {
        sum += (c - 48) * (int)(Math.Pow(10,position));
        position++;
    }
    return sum;
}))

{
    Console.WriteLine(size);
}

もちろん0x30です。また、配列には数値以外の文字列が含まれている可能性があり、そのためにソリューションは興味深い結果を生成します。
Femaref

また、-48はまったく何も変更しないことに注意してください。charの整数値を直接使用できるため、気になる場合は-48を削除してください...
MarinoŠimićJun

char値は、あなたがそれをintに変換する場合、それはまだ数0でない0x30を、となり、0x30のである
Femaref

唯一のものは、整数に変換Math.Powから返されるダブルです
マリノシミック

femarefゼロかどうかは関係ありません。10年制のシステムが処理します。必要に応じてbeにすることもできます。これは、文字セットで数字が昇順であり、それよりも少ないことだけが重要です。 10より
MarinoŠimić11年

3

このサイトでは、英数字の並べ替えについて説明し、ASCIIではなく論理的な意味で数値を並べ替えます。また、周囲のアルファも考慮に入れます。

http://www.dotnetperls.com/alphanumeric-sorting

例:

  • C:/TestB/333.jpg
  • 11
  • C:/TestB/33.jpg
  • 1
  • C:/TestA/111.jpg
  • 111F
  • C:/TestA/11.jpg
  • 2
  • C:/TestA/1.jpg
  • 111D
  • 22
  • 111Z
  • C:/TestB/03.jpg

  • 1
  • 2
  • 11
  • 22
  • 111D
  • 111F
  • 111Z
  • C:/TestA/1.jpg
  • C:/TestA/11.jpg
  • C:/TestA/111.jpg
  • C:/TestB/03.jpg
  • C:/TestB/33.jpg
  • C:/TestB/333.jpg

コードは次のとおりです。

class Program
{
    static void Main(string[] args)
    {
        var arr = new string[]
        {
           "C:/TestB/333.jpg",
           "11",
           "C:/TestB/33.jpg",
           "1",
           "C:/TestA/111.jpg",
           "111F",
           "C:/TestA/11.jpg",
           "2",
           "C:/TestA/1.jpg",
           "111D",
           "22",
           "111Z",
           "C:/TestB/03.jpg"
        };
        Array.Sort(arr, new AlphaNumericComparer());
        foreach(var e in arr) {
            Console.WriteLine(e);
        }
    }
}

public class AlphaNumericComparer : IComparer
{
    public int Compare(object x, object y)
    {
        string s1 = x as string;
        if (s1 == null)
        {
            return 0;
        }
        string s2 = y as string;
        if (s2 == null)
        {
            return 0;
        }

        int len1 = s1.Length;
        int len2 = s2.Length;
        int marker1 = 0;
        int marker2 = 0;

        // Walk through two the strings with two markers.
        while (marker1 < len1 && marker2 < len2)
        {
            char ch1 = s1[marker1];
            char ch2 = s2[marker2];

            // Some buffers we can build up characters in for each chunk.
            char[] space1 = new char[len1];
            int loc1 = 0;
            char[] space2 = new char[len2];
            int loc2 = 0;

            // Walk through all following characters that are digits or
            // characters in BOTH strings starting at the appropriate marker.
            // Collect char arrays.
            do
            {
                space1[loc1++] = ch1;
                marker1++;

                if (marker1 < len1)
                {
                    ch1 = s1[marker1];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));

            do
            {
                space2[loc2++] = ch2;
                marker2++;

                if (marker2 < len2)
                {
                    ch2 = s2[marker2];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));

            // If we have collected numbers, compare them numerically.
            // Otherwise, if we have strings, compare them alphabetically.
            string str1 = new string(space1);
            string str2 = new string(space2);

            int result;

            if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
            {
                int thisNumericChunk = int.Parse(str1);
                int thatNumericChunk = int.Parse(str2);
                result = thisNumericChunk.CompareTo(thatNumericChunk);
            }
            else
            {
                result = str1.CompareTo(str2);
            }

            if (result != 0)
            {
                return result;
            }
        }
        return len1 - len2;
    }
}

2

ジェフポールセンの答えは正しいですが、Comprarerこれはかなり単純化できます。

public class SemiNumericComparer: IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        if (IsNumeric(s1) && IsNumeric(s2))
          return Convert.ToInt32(s1) - Convert.ToInt32(s2)

        if (IsNumeric(s1) && !IsNumeric(s2))
            return -1;

        if (!IsNumeric(s1) && IsNumeric(s2))
            return 1;

        return string.Compare(s1, s2, true);
    }

    public static bool IsNumeric(object value)
    {
        int result;
        return Int32.TryParse(value, out result);
    }
}

これが機能するのは、 Comparerは、結果がより大きいか、小さいか、ゼロに等しい場合のみであるためです。単に値を別の値から差し引くだけでよく、戻り値を処理する必要はありません。

また、このIsNumericメソッドはtry-block を使用する必要はなく、から利益を得ることができTryParseます。

わからない人のために:このComparerは値を並べ替えるので、数値以外の値は常にリストの最後に追加されます。最初にそれらが必要な場合は、2番目と3番目のifブロックを交換する必要があります。


TryParseメソッドの呼び出しにはおそらくオーバーヘッドがあるため、最初にs1とs2のisNumeric値をブール値に格納し、代わりにそれらの比較を行います。このようにして、それらは複数回評価されません。
Optavius 2016

1

これを試して :

string[] things= new string[] { "105", "101", "102", "103", "90" };

int tmpNumber;

foreach (var thing in (things.Where(xx => int.TryParse(xx, out tmpNumber)).OrderBy(xx =>     int.Parse(xx))).Concat(things.Where(xx => !int.TryParse(xx, out tmpNumber)).OrderBy(xx => xx)))
{
    Console.WriteLine(thing);
}

1
public class NaturalSort: IComparer<string>
{
          [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
          public static extern int StrCmpLogicalW(string x, string y);

          public int Compare(string x, string y)
          {
                 return StrCmpLogicalW(x, y);
          }
}

arr = arr.OrderBy(x => x、new NaturalSort())。ToArray();

私がそれを必要とした理由は、ファイル名が数字で始まるディレクトリにファイルされるようにするためでした:

public static FileInfo[] GetFiles(string path)
{
  return new DirectoryInfo(path).GetFiles()
                                .OrderBy(x => x.Name, new NaturalSort())
                                .ToArray();
}

0
Try this out..  



  string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "-10" };

        List<int> num = new List<int>();
        List<string> str = new List<string>();
        for (int i = 0; i < things.Count(); i++)
        {

            int result;
            if (int.TryParse(things[i], out result))
            {
                num.Add(result);
            }
            else
            {
                str.Add(things[i]);
            }


        }

次に、リストを並べ替えてマージします...

        var strsort = from s in str
                      orderby s.Length
                      select s;

        var numsort = from n in num
                     orderby n
                     select n;

        for (int i = 0; i < things.Count(); i++)
        {

         if(i < numsort.Count())
             things[i] = numsort.ElementAt(i).ToString();
             else
             things[i] = strsort.ElementAt(i - numsort.Count());               
               }

私はjsutがこの興味深い質問に貢献しようとしました...


0

私の優先する解決策(すべての文字列が数値のみの場合):

// Order by numerical order: (Assertion: all things are numeric strings only) 
foreach (var thing in things.OrderBy(int.Parse))
{
    Console.Writeline(thing);
}

0
public class Test
{
    public void TestMethod()
    {
        List<string> buyersList = new List<string>() { "5", "10", "1", "str", "3", "string" };
        List<string> soretedBuyersList = null;

        soretedBuyersList = new List<string>(SortedList(buyersList));
    }

    public List<string> SortedList(List<string> unsoredList)
    {
        return unsoredList.OrderBy(o => o, new SortNumericComparer()).ToList();
    }
}

   public class SortNumericComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        int xInt = 0;
        int yInt = 0;
        int result = -1;

        if (!int.TryParse(x, out xInt))
        {
            result = 1;
        }

        if(int.TryParse(y, out yInt))
        {
            if(result == -1)
            {
                result = xInt - yInt;
            }
        }
        else if(result == 1)
        {
             result = string.Compare(x, y, true);
        }

        return result;
    }
}

あなたのコードを説明できますか?コードのみの回答は削除される可能性があります。
Wai Ha Lee

ジェフポールセンの投稿は、IComparer <string>を実装して私の問題を修正するのに役立ちました。。
kumar 2015年

0

ジェフ・ポールセンの答えを拡張します。文字列に含まれる数値または文字グループの数が問題にならないようにしたかったのです。

public class SemiNumericComparer : IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        if (int.TryParse(s1, out var i1) && int.TryParse(s2, out var i2))
        {
            if (i1 > i2)
            {
                return 1;
            }

            if (i1 < i2)
            {
                return -1;
            }

            if (i1 == i2)
            {
                return 0;
            }
        }

        var text1 = SplitCharsAndNums(s1);
        var text2 = SplitCharsAndNums(s2);

        if (text1.Length > 1 && text2.Length > 1)
        {

            for (var i = 0; i < Math.Max(text1.Length, text2.Length); i++)
            {

                if (text1[i] != null && text2[i] != null)
                {
                    var pos = Compare(text1[i], text2[i]);
                    if (pos != 0)
                    {
                        return pos;
                    }
                }
                else
                {
                    //text1[i] is null there for the string is shorter and comes before a longer string.
                    if (text1[i] == null)
                    {
                        return -1;
                    }
                    if (text2[i] == null)
                    {
                        return 1;
                    }
                }
            }
        }

        return string.Compare(s1, s2, true);
    }

    private string[] SplitCharsAndNums(string text)
    {
        var sb = new StringBuilder();
        for (var i = 0; i < text.Length - 1; i++)
        {
            if ((!char.IsDigit(text[i]) && char.IsDigit(text[i + 1])) ||
                (char.IsDigit(text[i]) && !char.IsDigit(text[i + 1])))
            {
                sb.Append(text[i]);
                sb.Append(" ");
            }
            else
            {
                sb.Append(text[i]);
            }
        }

        sb.Append(text[text.Length - 1]);

        return sb.ToString().Split(' ');
    }
}

また、ファイル名を処理するように修正した後、SOページからSplitCharsAndNumsを取得しました。


-1

これは古い質問ですが、解決策を提供したいと思います。

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => Int32.Parse(x) )
{
    Console.WriteLine(thing);
}

とてもシンプルですね?:D


-1
namespace X
{
    public class Utils
    {
        public class StrCmpLogicalComparer : IComparer<Projects.Sample>
        {
            [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
            private static extern int StrCmpLogicalW(string x, string y);


            public int Compare(Projects.Sample x, Projects.Sample y)
            {
                string[] ls1 = x.sample_name.Split("_");
                string[] ls2 = y.sample_name.Split("_");
                string s1 = ls1[0];
                string s2 = ls2[0];
                return StrCmpLogicalW(s1, s2);
            }
        }

    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.