c# - property - Utilice su propio IComparer<T> con Linq OrderBy
order by list c# desc (5)
¿No podemos hacer esto?
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;
}
}
Uso:
string[] inputStrings = {"_abc*&","#almnp","abc" };
//string[] inputStrings = { "#", "_", "_a", "@", "_" };
MyComparer computer = new MyComparer();
var kola = inputStrings.OrderBy(x => x, new MyComparer()).ToArray();
Esto es lo mismo que:
Array.Sort(inputStrings, StringComparer.Ordinal);
Tengo un genérico
List<MyClass>
donde MyClass
tiene una propiedad InvoiceNumber
que contiene valores tales como:
200906/1
200906/2
..
200906/10
200906/11
200906/12
Mi lista está vinculada a un
BindingList<T>
que admite la ordenación con 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;
}
Sin embargo, el comparador predeterminado clasifica (como se supone) de esta manera:
200906/1
200906/10
200906/11
200906/12
200906/2
que es desagradable en este caso.
Ahora quiero usar mi propio IComparer<T>
con esto. Se parece a esto:
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]);
}
}
}
y cambió el código de ApplySortCore
para usar este IComparer
:
case ListSortDirection.Ascending:
MyComparer comparer = new MyComparer();
items = items.OrderByDescending(
x => property.GetValue(x), comparer).ToList();
break;
Cuando depuro mi código, veo que MyComparer.Compare(object, object)
se llama varias veces y devuelve los valores correctos (-1, 0, 1) para un método de comparación.
Pero mi lista todavía está ordenada de la manera "incorrecta". ¿Me estoy perdiendo de algo? No tengo ni idea.
Encontré el problema de la ordenación natural general y escribí la solución en el blog aquí:
Natural Sort Comparar con 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;
}
}
La lista ordenada solo está vinculada a los elementos variables locales, no a la propiedad Elementos de su lista de enlaces, por lo que sigue sin clasificarse.
[Editar] Básicamente, simplemente estás descartando el resultado de tus esfuerzos de clasificación ;-)
Puede usar el Algoritmo 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;
}
}
}
Tu comparador me parece mal. Todavía está ordenando el orden predeterminado de texto. Seguramente quieres analizar los dos números y ordenarlos según eso:
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]));
}
}
(Tenga en cuenta que esto no concuerda con su pregunta que indica que ha depurado y verificado que Compare está devolviendo el valor correcto, pero me temo que sospecho que hay un error humano en ese frente).
Además, Sven tiene razón: cambiar el valor de los items
no cambia en absoluto su lista de límites. Debe agregar:
this.Items = items;
en la parte inferior de tu método