C# 将自己的 IComparer<T> 与 Linq OrderBy 一起使用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/985657/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Use own IComparer<T> with Linq OrderBy
提问by Jürgen Steinblock
I have a generic
我有一个通用的
List<MyClass>
where MyClass
has a property InvoiceNumber
which contains values such as:
whereMyClass
有一个InvoiceNumber
包含以下值的属性:
200906/1
200906/2
..
200906/10
200906/11
200906/12
200906/1
200906/2
..
200906/10
200906/11
200906/12
My list is bound to a
我的列表绑定到一个
BindingList<T>
which supports sorting with linq:
它支持使用 linq 进行排序:
protected override void ApplySortCore(
PropertyDescriptor property, ListSortDirection direction)
{
_sortProperty = property;
_sortDirection = direction;
var items = this.Items;
switch (direction)
{
case ListSortDirection.Ascending:
items = items.OrderByDescending(x => property.GetValue(x)).ToList();
break;
case ListSortDirection.Descending:
items = items.OrderByDescending(x => property.GetValue(x)).ToList();
break;
}
this.Items = items;
}
However the default comparer sorts (as supposed) like this:
然而,默认的比较器排序(如假设)是这样的:
200906/1
200906/10
200906/11
200906/12
200906/2
200906/1
200906/10
200906/11
200906/12
200906/2
which is nasty in this case.
在这种情况下这是令人讨厌的。
Now I want to use my own IComparer<T>
with this. It looks like this:
现在我想用我自己的IComparer<T>
。它看起来像这样:
public class MyComparer : IComparer<Object>
{
public int Compare(Object stringA, Object stringB)
{
String[] valueA = stringA.ToString().Split('/');
String[] valueB = stringB.ToString().Split('/');
if(valueA .Length != 2 || valueB .Length != 2)
return String.Compare(stringA.ToString(), stringB.ToString());
if (valueA[0] == valueB[0])
{
return String.Compare(valueA[1], valueB[1]);
}
else
{
return String.Compare(valueA[0], valueB[0]);
}
}
}
and changed the ApplySortCore
code to use this IComparer
:
并更改了ApplySortCore
代码以使用它IComparer
:
case ListSortDirection.Ascending:
MyComparer comparer = new MyComparer();
items = items.OrderByDescending(
x => property.GetValue(x), comparer).ToList();
break;
When I debug my code, I see that MyComparer.Compare(object, object)
is called multiple times and returns the right values (-1, 0, 1) for a compare method.
当我调试我的代码时,我看到它MyComparer.Compare(object, object)
被多次调用并为比较方法返回正确的值 (-1, 0, 1)。
But my list is still sorted the "wrong" way. Am I missing something? I have no clue.
但是我的列表仍然以“错误”的方式排序。我错过了什么吗?我没有线索。
采纳答案by Jon Skeet
Your comparer looks wrong to me. You're still just sorting in the default text ordering. Surely you want to be parsing the two numbers and sorting based on that:
你的比较器在我看来不对劲。您仍然只是按照默认的文本顺序进行排序。当然,您想解析这两个数字并基于此进行排序:
public int Compare(Object stringA, Object stringB)
{
string[] valueA = stringA.ToString().Split('/');
string[] valueB = stringB.ToString().Split('/');
if (valueA.Length != 2 || valueB.Length != 2)
{
stringA.ToString().CompareTo(stringB.ToString());
}
// Note: do error checking and consider i18n issues too :)
if (valueA[0] == valueB[0])
{
return int.Parse(valueA[1]).CompareTo(int.Parse(valueB[1]));
}
else
{
return int.Parse(valueA[0]).CompareTo(int.Parse(valueB[0]));
}
}
(Note that this doesn't sit well with your question stating that you've debugged through and verified that Compare is returning the right value - but I'm afraid I suspect human error on that front.)
(请注意,这与您的问题不符,说明您已经调试并验证了 Compare 返回了正确的值 - 但恐怕我怀疑在这方面存在人为错误。)
Additionally, Sven's right - changing the value of items
doesn't change your bound list at all. You should add:
此外,Sven 是对的 - 更改 的值items
根本不会更改您的绑定列表。你应该添加:
this.Items = items;
at the bottom of your method.
在你的方法的底部。
回答by Sven Künzler
The sorted list is only bound to the local variable items, not to the Items property of your binding list, hence it remains unsorted.
排序列表仅绑定到局部变量 items,而不绑定到绑定列表的 Items 属性,因此它保持未排序。
[Edit] Basically, you're simply throwing away the result of your sorting efforts ;-)
[编辑] 基本上,你只是扔掉你的排序工作的结果;-)
回答by James McCormack
I encountered the issue of general natural sorting and blogged the solution here:
我遇到了一般自然排序的问题,并在这里写了解决方案:
Natural Sort Compare with Linq OrderBy()
public class NaturalSortComparer<T> : IComparer<string>, IDisposable
{
private bool isAscending;
public NaturalSortComparer(bool inAscendingOrder = true)
{
this.isAscending = inAscendingOrder;
}
#region IComparer<string> Members
public int Compare(string x, string y)
{
throw new NotImplementedException();
}
#endregion
#region IComparer<string> Members
int IComparer<string>.Compare(string x, string y)
{
if (x == y)
return 0;
string[] x1, y1;
if (!table.TryGetValue(x, out x1))
{
x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
table.Add(x, x1);
}
if (!table.TryGetValue(y, out y1))
{
y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
table.Add(y, y1);
}
int returnVal;
for (int i = 0; i < x1.Length && i < y1.Length; i++)
{
if (x1[i] != y1[i])
{
returnVal = PartCompare(x1[i], y1[i]);
return isAscending ? returnVal : -returnVal;
}
}
if (y1.Length > x1.Length)
{
returnVal = 1;
}
else if (x1.Length > y1.Length)
{
returnVal = -1;
}
else
{
returnVal = 0;
}
return isAscending ? returnVal : -returnVal;
}
private static int PartCompare(string left, string right)
{
int x, y;
if (!int.TryParse(left, out x))
return left.CompareTo(right);
if (!int.TryParse(right, out y))
return left.CompareTo(right);
return x.CompareTo(y);
}
#endregion
private Dictionary<string, string[]> table = new Dictionary<string, string[]>();
public void Dispose()
{
table.Clear();
table = null;
}
}
回答by Micha? Kuliński
You can use The Alphanum Algorithm:
您可以使用 Alphanum 算法:
(...)
items.OrderBy(x => property.GetValue(x), new AlphanumComparator())
(...)
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Based on the Java implementation of Dave Koelle's Alphanum algorithm.
* Contributed by Jonathan Ruckwood <[email protected]>
*
* Adapted by Dominik Hurnaus <[email protected]> to
* - correctly sort words where one word starts with another word
* - have slightly better performance
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
using System;
using System.Collections;
using System.Text;
/*
* Please compare against the latest Java version at http://www.DaveKoelle.com
* to see the most recent modifications
*/
namespace AlphanumComparator
{
public class AlphanumComparator : IComparer
{
private enum ChunkType {Alphanumeric, Numeric};
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
}
回答by Dr.Sai
Can't we do like this:
我们不能这样做:
public class MyComparer : IComparer<string>
{
public int Compare(string stringA, string stringB)
{
string small = stringA;
string big = stringB;
if (stringA.Length > stringB.Length)
{
small = stringB;
big = stringA;
}
else if (stringA.Length < stringB.Length)
{
small = stringA;
big = stringB;
}
for (int j = 0; j < small.Length; j++)
{
if (Convert.ToInt32(small[j]) > Convert.ToInt32(big[j])) return -1;
if (Convert.ToInt32(small[j]) < Convert.ToInt32(big[j])) return 1;
}
//big is indeed bigger
if (big.Length > small.Length) return 1;
//finally they are smae
return 0;
}
}
Usage:
用法:
string[] inputStrings = {"_abc*&","#almnp","abc" };
//string[] inputStrings = { "#", "_", "_a", "@", "_" };
MyComparer computer = new MyComparer();
var kola = inputStrings.OrderBy(x => x, new MyComparer()).ToArray();
This is same as :
这与:
Array.Sort(inputStrings, StringComparer.Ordinal);