혼합 숫자와 문자열 분류
-
06-07-2019 - |
문제
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);
}
다른 팁
두 가지 방법이 떠 오릅니다. 어느 것이 더 성능이 있는지 확실하지 않습니다. 사용자 정의 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)