developer tip

c # linq orderby 숫자는 문자열이며 int로 변환 할 수 없습니다.

copycodes 2020. 9. 6. 10:12
반응형

c # linq orderby 숫자는 문자열이며 int로 변환 할 수 없습니다.


문자열 인 숫자 배열을 정렬하려고하는데 숫자로 정렬하고 싶습니다.

문제 는 숫자를 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, 밥, 로렌, 폴


사용자 지정 비교자를 OrderBy에 전달합니다. Enumerable.OrderBy 를 사용하면 원하는 비교자를 지정할 수 있습니다.

이를 수행하는 한 가지 방법입니다.

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>
{
    public int Compare(string s1, string s2)
    {
        if (IsNumeric(s1) && IsNumeric(s2))
        {
            if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
            if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
            if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
        }

        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)
    {
        try {
            int i = Convert.ToInt32(value.ToString());
            return true; 
        }
        catch (FormatException) {
            return false;
        }
    }
}

같은 길이로 0으로 채 웁니다.

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

그리고 이것은 어떻습니까 ...

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

값은 문자열입니다.

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

공장


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

텍스트와 숫자가 모두있는 문자열에서도 작동합니다. 여기에 기본 정렬과 사이의 디퍼 런스 세라마이드 표시하는 예제 프로그램입니다 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

이 시도

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

참고 : 이것은 모든 문자열이 int .....로 변환 가능한 경우에 유용합니다.


배열에 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, bob, lauren, paul


문자열에 숫자가 있으면 훨씬 더 좋을 것 같습니다. 도움이되기를 바랍니다.

추신 : 성능이나 복잡한 문자열 값에 대해서는 잘 모르겠지만 다음과 같이 잘 작동했습니다.

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

이것은 이상한 요청으로 보이며 이상한 해결책이 필요합니다.

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

이 사이트에서는 영숫자 정렬에 대해 설명하고 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;
    }
}

Jeff Paulsen이 제공 한 대답은 정확하지만 다음과 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결과가 더 크거나 작거나 0인지 여부입니다. 하나는 단순히 다른 값에서 값을 뺄 수 있으며 반환 값을 처리 할 필요가 없습니다.

또한이 IsNumeric메서드는 try-block 을 사용할 필요가 없으며 TryParse.

그리고 확실하지 않은 사람들을 위해 :이 비교기는 숫자가 아닌 값이 항상 목록 끝에 추가되도록 값을 정렬합니다. 처음에 원하는 경우 두 번째와 세 번째 if블록을 교체해야합니다.


이 시도 :

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

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

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

나는이 흥미로운 질문에 기여하려고 노력했습니다 ...


내가 선호하는 솔루션 (모든 문자열이 숫자 전용 인 경우) :

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

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

Jeff Paulsen 답변을 확장합니다. 문자열에 몇 개의 숫자 또는 문자 그룹이 있는지는 중요하지 않은지 확인하고 싶었습니다.

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를 가져 왔습니다 .


이것은 오래된 질문이지만 해결책을 제시하고 싶습니다.

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

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

Woha 아주 간단 하죠? :디


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

    }
}

참고 URL : https://stackoverflow.com/questions/6396378/c-sharp-linq-orderby-numbers-that-are-string-and-you-cannot-convert-them-to-int

반응형