문제

int (max 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

나는 그것을 구문 분석하려고 시도하는 것과 관련하여 그것을 형식화하는 아이디어를 가지고있다. 만약 그것이 성공적이라면 그것이 내 자신의 custom stringformatter와 함께 포맷하기 위해 그것을 성공적으로 만들기 위해 그것을 성공적으로 만들었다. 나는 더 간단하고 성능이있는 것을 바라고 있습니다.

편집하다
나는 나중에 사용하기 위해 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# 구현과 같은 알고리즘 여기.

다른 팁

두 가지 방법이 떠 오릅니다. 어느 것이 더 성능이 있는지 확실하지 않습니다. 사용자 정의 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 ) );

다른 과부하를 사용하십시오 OrderBy 그것은 필요합니다 IComparer 매개 변수.

그런 다음 직접 구현할 수 있습니다 IComparer 그것은 사용합니다 int.TryParse 숫자인지 아닌지를 말해야합니다.

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+$", item)) {
        numbers.Add(int.Parse(item));
    }
    else {
        words.Add(item);
    }
}

그런 다음 두 목록을 사용하여 각 목록을 정렬 한 다음 원하는 형식으로 다시 병합 할 수 있습니다.

기능 만 사용할 수 있습니다 Win32 API가 제공합니다:

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

그리고 그것을 an에서 부릅니다 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);
}

여전히 변환을 수행하지만 한 번만/항목 만 수행합니다.

사용자 정의 비교를 사용할 수 있습니다. 그러면 순서 명령문은 다음과 같습니다.

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

이것은 약간 장점으로 보일 수 있지만 정렬 로직을 적절한 유형으로 캡슐화합니다. 그러면 원하는 경우 비교를 자동 테스트 (단위 테스트)에 쉽게 적용 할 수 있습니다. 또한 재사용 가능합니다.

(알고리즘을 좀 더 명확하게 만드는 것이 가능할 수 있지만, 이것이 내가 빨리 함께 던질 수있는 최고였습니다.)

당신은 또한 어떤 의미에서 "속임수"일 수도 있습니다. 문제에 대한 설명을 바탕으로 길이 2의 문자열이 숫자임을 알 수 있습니다. 따라서 길이 1의 모든 문자열을 정렬 한 다음 길이 2의 모든 문자열을 분류 한 다음 스와핑을 수행하여 문자열을 올바른 순서로 다시 주문하십시오. 기본적으로 프로세스는 다음과 같이 작동합니다. (데이터가 배열에 있다고 가정합니다.)

1 단계 : 길이 2의 모든 문자열을 배열 끝으로 밀어 넣습니다. 당신이 얼마나 많은지 추적합니다.

2 단계 : 제자리에 길이 1의 문자열과 길이 2의 문자열을 정렬합니다.

3 단계 : 두 반쪽의 경계에있는 'a'에 대한 이진 검색.

4 단계 : 필요에 따라 두 자리 숫자 문자열을 문자로 바꾸십시오.

즉,이 접근법은 작동하지 않지만 정규 표현을 포함하지 않으며 INT 값이 아닌 값을 INT로 구문 분석하려고 시도하지 않습니다. 권장하지 않습니다. 이미 제안한 다른 접근법보다 훨씬 더 많은 코드를 작성할 것입니다. 그것은 당신이하려는 일의 요점을 난독하게합니다. 갑자기 두 개의 글자 또는 세 자리 문자열을 갑자기 받으면 작동하지 않습니다. 나는 단지 문제를 다르게 볼 수있는 방법을 보여주고 대체 솔루션을 제시하기 위해 그것을 포함하고 있습니다.

a Schwartzian 변환 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"

커스텀 비교를 사용하여 다음과 같습니다.

public class NaturalComparer : IComparer
{

    public NaturalComparer()
    {
        _regex = new Regex("\\d+$", 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