Reputation: 55
i have a class:
public class Essay
{
public int ID{get;set;}
public string Name{get;set;}
}
and list of Essay type
List<Essay> essays=new List<Essay>();
on the name property contains numbers and letters.
i want to sort the list by the name property
for example:
essays=
{1,"ccccc"},
{2,"aaaa"},
{3,"bbbb"},
{4,"10"},
{5,"1"},
{6,"2"},
{7,"1a"}
i want to sort:
essays=
{2,"aaaa"},
{3,"bbbb"},
{1,"ccccc"},
{5,"1"},
{7,"1a"},
{6,"2"},
{4,"10"}
how i do it?
thank to all.
Upvotes: 1
Views: 2794
Reputation: 109567
There are several elements to the answer.
The first part is being able to in-place sort a List using Sort() and a lambda comparison method. That's solved by using an extension method for IList and a helper "ComparisonDelegator" class. Combining those, it's possible to pass a lambda to List.Sort().
The second part has been addressed in another post here (which I have upvoted) and the code from which I have shamelessly pasted into the AlphanumComparator class in this answer.
(As a side note, I should point out that all the Linq examples posted elsewhere in this thread make a COPY of the list. This is fine for short lists, but if you have a long list it can cause performance problems. The solution presented here does NOT make a copy of the list.)
Putting it all together, we get the following code, which outputs:
ID=2, Name=aaaa
ID=3, Name=bbbb
ID=1, Name=ccccc
ID=5, Name=1
ID=7, Name=1a
ID=6, Name=2
ID=4, Name=10
And the full code sample (compilable as a console application):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Demo
{
public static class Program
{
public static void Main()
{
var list = new List<Essay>
{
new Essay {ID=1, Name="ccccc"},
new Essay {ID=2, Name="aaaa"},
new Essay {ID=3, Name="bbbb"},
new Essay {ID=4, Name="10"},
new Essay {ID=5, Name="1"},
new Essay {ID=6, Name="2"},
new Essay {ID=7, Name="1a"}
};
var comp = new AlphanumComparator();
list.Sort((lhs, rhs) => comp.Compare(lhs.Name, rhs.Name));
foreach (var essay in list)
{
Console.WriteLine("ID={0}, Name={1}", essay.ID, essay.Name);
}
}
}
public class Essay
{
public int ID
{
get;
set;
}
public string Name
{
get;
set;
}
}
/// <summary>Extensions for IList{T}</summary>
public static class ListExt
{
/// <summary> Sorts an IList{T} in place. </summary>
public static void Sort<T>(this IList<T> list, Comparison<T> comparison)
{
ArrayList.Adapter((IList)list).Sort(new ComparisonDelegator<T>(comparison));
}
}
/// <summary>
/// Provides a mechanism for easily converting a Comparison<> delegate (or lambda) to an IComparer<>.
/// This can be used for List.BinarySearch(), for example.
/// </summary>
/// <typeparam name="T">The type of items to be compared.</typeparam>
public sealed class ComparisonDelegator<T>: IComparer<T>, IComparer
{
/// <summary>Create from a Comparison<> delegate.</summary>
/// <param name="comparison">A Comparison<> delegate.</param>
public ComparisonDelegator(Comparison<T> comparison)
{
this._comparison = comparison;
}
/// <summary>Implements the IComparer.Compare() method.</summary>
public int Compare(T x, T y)
{
return _comparison(x, y);
}
/// <summary>Implements the IComparer.Compare() method.</summary>
public int Compare(object x, object y)
{
return _comparison((T)x, (T)y);
}
/// <summary>Used to store the Comparison delegate.</summary>
private readonly Comparison<T> _comparison;
}
/// <summary>
/// Special class to sort strings "naturally",
/// but to place non-numeric items *before* numeric items.
/// </summary>
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 if (char.IsDigit(thisChunk[0]) && !char.IsDigit(thatChunk[0]))
{
return 1; // Ensure that non-numeric sorts before numeric.
}
else if (!char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
return -1; // Ensure that non-numeric sorts before numeric.
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
}
Upvotes: 2
Reputation: 40818
I have created an IComparer implementation and use OrderBy to sort the list. I use a regex to capture the digits at the start of the Name
and if they exist use those to order the items, if not then just compare the names using a string comparison. A name starting with a number always comes after all names starting with letters. It gives the output you have requested.
public class EssayNameComparer : IComparer<string>
{
private Regex _digits = new Regex("(\\d+)(.*)");
public int Compare(string a, string b)
{
Match matcha = _digits.Match(a);
Match matchb = _digits.Match(b);
if (matcha.Success && matchb.Success)
{
int numa = int.Parse(matcha.Groups[1].Value);
int numb = int.Parse(matchb.Groups[1].Value);
return numa.CompareTo(numb);
}
else if (matcha.Success)
{
return 1;
}
else if (matchb.Success)
{
return -1;
}
else
{
return string.Compare(a, b);
}
}
}
public class Program
{
public static void Main(string[] args)
{
List<Essay> essays= new List<Essay>() {
new Essay { ID = 1, Name = "ccccc"},
new Essay { ID = 2, Name = "aaaa"},
new Essay { ID = 3, Name = "bbbb"},
new Essay { ID = 4, Name = "10"},
new Essay { ID = 5, Name = "1"},
new Essay { ID = 6, Name = "2"},
new Essay { ID = 7, Name = "1a"}
};
foreach(Essay essay in essays.OrderBy(e => e.Name, new EssayNameComparer()))
{
Console.WriteLine(essay.Name);
}
}
}
Output:
Upvotes: 0
Reputation: 14919
You may just change numeric chars with chars with higher ascii values and do a orderby
and revert back.
List<Essay> essays = new List<Essay>();
essays.Add(new Essay(){ID = 1, Name = "ccccc"});
essays.Add(new Essay(){ID = 2, Name = "aaaa"});
essays.Add(new Essay(){ID = 3, Name = "bbbb"});
essays.Add(new Essay(){ID = 4, Name = "10"});
essays.Add(new Essay(){ID = 5, Name = "1"});
essays.Add(new Essay(){ID = 6, Name = "2"});
essays.Add(new Essay(){ID = 7, Name = "1a"});
essays.ForEach(q => Replace(q));
var result = essays.OrderBy(q => q.Name).ToList();
result.ForEach(q => Revert(q));
Related Functions:
public void Replace(Essay x)
{
x.Name = x.Name.Replace('0', ((char)240));
x.Name = x.Name.Replace('1', ((char)241));
x.Name = x.Name.Replace('2', ((char)242));
x.Name = x.Name.Replace('3', ((char)243));
x.Name = x.Name.Replace('4', ((char)244));
x.Name = x.Name.Replace('5', ((char)245));
x.Name = x.Name.Replace('6', ((char)246));
x.Name = x.Name.Replace('7', ((char)247));
x.Name = x.Name.Replace('8', ((char)248));
x.Name = x.Name.Replace('9', ((char)249));
}
public void Revert(Essay x)
{
x.Name = x.Name.Replace(((char)240), '0');
x.Name = x.Name.Replace(((char)241), '1');
x.Name = x.Name.Replace(((char)242), '2');
x.Name = x.Name.Replace(((char)243), '3');
x.Name = x.Name.Replace(((char)244), '4');
x.Name = x.Name.Replace(((char)245), '5');
x.Name = x.Name.Replace(((char)246), '6');
x.Name = x.Name.Replace(((char)247), '7');
x.Name = x.Name.Replace(((char)248), '8');
x.Name = x.Name.Replace(((char)249), '9');
}
Upvotes: 0
Reputation: 45101
The name mixes characters and numbers, which leads to a awkward sorting if you simply sort alphabetically.
What you more seems to prefer is called naturally sorting, where the numbers within the text are really recognized as a number and the sort short apply their on a numeric basis.
A few implementations of this algorithm can be found on Jeffs webpage.
The one i most prefer is the one from Dave:
/*
* 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
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
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;
}
}
}
Upvotes: 4