Reputation: 863
I've tried a lot of methods, but I've always had problems with string expressions that are space characters. Then I came across this blog post and thought it might be useful but I do not know how to use it unfortunately. I have a list,
Edit: Game instead of Crysis, Star Citizen, 34 Games, Call of Duty. I use the game name to give an example
Game 3.1 1
Game 3.2 10
Game 3.3 11
Game 3.2 9
Game 3.18 7
Game 3.27 12
Game 3.11.2 13
Game 3.2 2
Game 3.8 5
Game 3.10 7
For Example;
List<GameVersion> GameVersionList = new List<GameVersion>();
GameVersionList.Add(new GameVersion() { Name = "Game 3.1", Code = "1" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "10" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.3", Code = "11" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "9" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.18", Code = "7" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.27", Code = "12" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.11.2", Code = "13" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "2" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.8", Code = "5" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.10", Code = "7" });
public class GameVersion
{
public string Name { get; set; }
public string Code { get; set; }
}
It should be like that;
Game 3.1 1
Game 3.2 10
Game 3.2 9
Game 3.2 2
Game 3.3 11
Game 3.8 5
Game 3.10 7
Game 3.11.2 13
Game 3.18 7
Game 3.27 12
I want to be sorted by name. So, how to use this CompareNumeric?
public static int CompareNumeric(this string s, string other)
{
if (s != null && other != null &&
(s = s.Replace(" ", string.Empty)).Length > 0 &&
(other = other.Replace(" ", string.Empty)).Length > 0)
{
int sIndex = 0, otherIndex = 0;
while (sIndex < s.Length)
{
if (otherIndex >= other.Length)
return 1;
if (char.IsDigit(s[sIndex]))
{
if (!char.IsDigit(other[otherIndex]))
return -1;
// Compare the numbers
StringBuilder sBuilder = new StringBuilder(), otherBuilder = new StringBuilder();
while (sIndex < s.Length && char.IsDigit(s[sIndex]))
{
sBuilder.Append(s[sIndex++]);
}
while (otherIndex < other.Length && char.IsDigit(other[otherIndex]))
{
otherBuilder.Append(other[otherIndex++]);
}
long sValue = 0L, otherValue = 0L;
try
{
sValue = Convert.ToInt64(sBuilder.ToString());
}
catch (OverflowException) { sValue = Int64.MaxValue; }
try
{
otherValue = Convert.ToInt64(otherBuilder.ToString());
}
catch (OverflowException) { otherValue = Int64.MaxValue; }
if (sValue < otherValue)
return -1;
else if (sValue > otherValue)
return 1;
}
else if (char.IsDigit(other[otherIndex]))
return 1;
else
{
int difference = string.Compare(s[sIndex].ToString(), other[otherIndex].ToString(), StringComparison.InvariantCultureIgnoreCase);
if (difference > 0)
return 1;
else if (difference < 0)
return -1;
sIndex++;
otherIndex++;
}
}
if (otherIndex < other.Length)
return -1;
}
return 0;
}
Upvotes: 3
Views: 1639
Reputation: 4036
If you have game names that can consist of more than one word and possibly no version number at all, then you need to implement something a bit more complex.
We will use a class that implements IComparer<string>
interface for comparing game names.
The public int Compare(string x, string y)
method will:
Regex
to split up game name into Name and Version parts.
as delimeter (eg 1.33.2 => {1, 33, 2})class GameNameComparer : IComparer<string>
{
static readonly Regex regx = new Regex(@"^(?<Name>.*) (?<Version>[\d\.]+)$", RegexOptions.ExplicitCapture);
private static GameNameComparer instance;
public static GameNameComparer Comparer
{
get
{
if (instance == null)
instance = new GameNameComparer();
return instance;
}
}
private GameNameComparer() { }
public int Compare(string x, string y)
{
var m1 = regx.Match(x);
var m2 = regx.Match(y);
if (m1.Success && m2.Success)
{
var name1 = m1.Groups["Name"].Value;
var ver1 = m1.Groups["Version"].Value;
var name2 = m2.Groups["Name"].Value;
var ver2 = m2.Groups["Version"].Value;
if (String.Equals(name1, name2, StringComparison.OrdinalIgnoreCase))
{
var ver1Levels = ver1.Split(new char[] { '.' });
var ver2Levels = ver2.Split(new char[] { '.' });
for (int i = 0; i < Math.Min(ver1Levels.Length, ver2Levels.Length); i++)
{
int ver1LevelNo = 0;
int ver2LevelNo = 0;
int compare = 0;
if (Int32.TryParse(ver1Levels[i], out ver1LevelNo) && Int32.TryParse(ver2Levels[i], out ver2LevelNo))
{
compare = ver1LevelNo.CompareTo(ver2LevelNo);
if (compare != 0)
return compare;
}
compare = ver1Levels[i].CompareTo(ver2Levels[i]);
if (compare != 0)
return compare;
}
return ver1Levels.Length.CompareTo(ver2Levels.Length);
}
return String.Compare(name1, name2, StringComparison.OrdinalIgnoreCase);
}
return String.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
}
We can now use this class to sort the game list correctly:
GameVersionList = GameVersionList.OrderBy(g=>g.Name, GameNameComparer.Comparer).ToList();
Upvotes: 0
Reputation: 35400
There's no need to add so much complexity. Using LINQ, split the Name field on Space and take last element (number). You can then use Version
class to perform sorting:
var Sorted = GameVersionList.OrderBy(g => new Version(g.Name.Split(' ').Last());
If you want to sort on the name part first and then by version, change the above slightly:
var Sorted = GameVersionList.OrderBy(g => g.Name).ThenBy(g => new Version(g.Name.Split(' ').Last());
Notice how the primary sort will not (effectively) be affected by the version number
Upvotes: 3
Reputation: 363
If every name is not the same as you commented, you can just take the substring after that space, convert it to double and sort it like
GameVersionList.OrderBy(x => double.Parse(
x.Name.Substring(
x.Name.IndexOf(' '))))
Upvotes: 1
Reputation: 109597
You can do this using the Windows API StrCmpLogicalW()
to compare strings using a "natural" sort order.
You can encapsulate this in a List<T>
extension:
using System.Runtime.InteropServices;
public static class ListExt
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern int StrCmpLogicalW(string lhs, string rhs);
public static void SortNatural<T>(this List<T> self, Func<T, string> stringSelector)
{
self.Sort((lhs, rhs) => StrCmpLogicalW(stringSelector(lhs), stringSelector(rhs)));
}
public static void SortNatural(this List<string> self)
{
self.Sort(StrCmpLogicalW);
}
}
Then you can use it for your example like this:
static void Main()
{
List<GameVersion> GameVersionList = new List<GameVersion>();
GameVersionList.Add(new GameVersion() { Name = "Game 3.1", Code = "1" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "10" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.3", Code = "11" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "9" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.18", Code = "7" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.27", Code = "12" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.11.2", Code = "13" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.2", Code = "2" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.8", Code = "5" });
GameVersionList.Add(new GameVersion() { Name = "Game 3.10", Code = "7" });
GameVersionList.SortNatural(item => item.Name);
foreach (var item in GameVersionList)
{
Console.WriteLine(item.Name + ": " + item.Code);
}
}
The output is:
Game 3.1: 1
Game 3.2: 10
Game 3.2: 9
Game 3.2: 2
Game 3.3: 11
Game 3.8: 5
Game 3.10: 7
Game 3.11.2: 13
Game 3.18: 7
Game 3.27: 12
which matches your requirement.
Upvotes: 2
Reputation: 53
You can try OrderBy Name
var sortedList = GameVersionList.OrderBy(x=>x.Name).ToList();
Upvotes: 0