Array.Sort for strings with numbers [duplicate] - c#

This question already has answers here:
Natural Sort Order in C#
(18 answers)
Closed 8 years ago.
I have sample codes below:
List<string> test = new List<string>();
test.Add("Hello2");
test.Add("Hello1");
test.Add("Welcome2");
test.Add("World");
test.Add("Hello11");
test.Add("Hello10");
test.Add("Welcome0");
test.Add("World3");
test.Add("Hello100");
test.Add("Hello20");
test.Add("Hello3");
test.Sort();
But what happen is, the test.Sort will sort the array to:
"Hello1",
"Hello10",
"Hello100",
"Hello11",
"Hello2",
"Hello20",
"Hello3",
"Welcome0",
"Welcome2",
"World",
"World3"
Is there any way to sort them so that the string will have the correct number order as well?
(If there is no number at the end of the string, that string will always go first - after the alphabetical order)
Expected output:
"Hello1",
"Hello2",
"Hello3",
"Hello10",
"Hello11",
"Hello20",
"Hello100",
"Welcome0",
"Welcome2",
"World",
"World3"

Here is a one possible way using LINQ:
var orderedList = test
.OrderBy(x => new string(x.Where(char.IsLetter).ToArray()))
.ThenBy(x =>
{
int number;
if (int.TryParse(new string(x.Where(char.IsDigit).ToArray()), out number))
return number;
return -1;
}).ToList();

Create an IComparer<string> implementation. The advantage of doing it this way over the LINQ suggestions is you now have a class that can be passed to anything that needs to sort in this fashion rather that recreating that linq query in other locations.
This is specific to your calling a sort from a LIST. If you want to call it as Array.Sort() please see version two:
List Version:
public class AlphaNumericComparer : IComparer<string>
{
public int Compare(string lhs, string rhs)
{
if (lhs == null)
{
return 0;
}
if (rhs == null)
{
return 0;
}
var s1Length = lhs.Length;
var s2Length = rhs.Length;
var s1Marker = 0;
var s2Marker = 0;
// Walk through two the strings with two markers.
while (s1Marker < s1Length && s2Marker < s2Length)
{
var ch1 = lhs[s1Marker];
var ch2 = rhs[s2Marker];
var s1Buffer = new char[s1Length];
var loc1 = 0;
var s2Buffer = new char[s2Length];
var loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
s1Buffer[loc1++] = ch1;
s1Marker++;
if (s1Marker < s1Length)
{
ch1 = lhs[s1Marker];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(s1Buffer[0]));
do
{
s2Buffer[loc2++] = ch2;
s2Marker++;
if (s2Marker < s2Length)
{
ch2 = rhs[s2Marker];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(s2Buffer[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(s1Buffer);
string str2 = new string(s2Buffer);
int result;
if (char.IsDigit(s1Buffer[0]) && char.IsDigit(s2Buffer[0]))
{
var thisNumericChunk = int.Parse(str1);
var thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return s1Length - s2Length;
}
}
call like so:
test.sort(new AlphaNumericComparer());
//RESULT
Hello1
Hello2
Hello3
Hello10
Hello11
Hello20
Hello100
Welcome0
Welcome2
World
World3
Array.sort version:
Create class:
public class AlphaNumericComparer : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
var ch1 = s1[marker1];
var ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
var space1 = new char[len1];
var loc1 = 0;
var space2 = new char[len2];
var loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
var str1 = new string(space1);
var str2 = new string(space2);
var result = 0;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
var thisNumericChunk = int.Parse(str1);
var thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
Call like so:
This time test is an array instead of a list.
Array.sort(test, new AlphaNumericComparer())

You can use LINQ combined with regex to ensure that you use only numbers that occur at the end of the string for your secondary ordering
test
.Select(t => new{match = Regex.Match(t, #"\d+$"), val = t})
.Select(x => new{sortVal = x.match.Success
?int.Parse(x.match.Value)
:-1,
val = x.val})
.OrderBy(x => x.val)
.ThenBy(x => x.sortVal)
.Select(x => x.val)
.ToList()

Related

How to sort descending a string list contains strings with alphabets & numbers in C# .net?

I have values in a string list like
AB1001_A
AB1001_B
AB1002_2
AB1002_C
AB1003_0
AB1003_
AB1003_B
AB1003_A
AB1001_0
AB1001_1
AB1001_2
AB1001_C
AB1002_B
AB1002_A
And I wanted to sort this by ascending order and the suffixes in descending order like below
AB1001_2
AB1001_1
AB1001_0
AB1001_C
AB1001_B
AB1001_A
AB1002_0
AB1002_B
AB1002_A
AB1003_0
AB1003_B
AB1003_A
AB1003_
How can I code it in C#.net?
It is quite strange sorting, but if you really need it, try something like this:
List<string> lItemsOfYourValues = new List<string>() {"AB1001_A","AB1001_B","AB1001_0" /*and next your values*/};
List<Tuple<string,string,string>> lItemsOfYourProcessedValues = new List<Tuple<string,string,string>>();
string[] arrSplitedValue;
for(int i = 0; i < lItemsOfYourValues.Count; i++)
{
arrSplitedValue = lItemsOfYourValues[i].Split("_");
lItemsOfYourProcessedValues.add(new Tuple<string,string,string>(lItemsOfYourValues[i], arrSplitedValue[0], arrSplitedValue[1]));
}
List<string> lSortedValues = lItemsOfYourProcessedValues.OrderBy(o => o.Item2).ThenByDescending(o => o.Item3).Select(o => o.Item1).ToList();
It looks like you have an error in your expected results, since AB1002_2 is in the input but not in the expected results.
Assuming that's just an error, and further assuming that the suffixes are limited to a single character or digit, you can solve the sorting by writing a special comparer like so:
static int compare(string x, string y)
{
var xParts = x.Split('_', StringSplitOptions.RemoveEmptyEntries);
var yParts = y.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (xParts.Length != yParts.Length)
return yParts.Length - xParts.Length; // No suffix goes after suffix.
if (xParts.Length == 0) // Should never happen.
return 0;
int comp = string.Compare(xParts[0], yParts[0], StringComparison.Ordinal);
if (comp != 0 || xParts.Length == 1)
return comp;
if (char.IsDigit(xParts[1][0]) && !char.IsDigit(yParts[1][0]))
return -1; // Digits go before non-digit.
if (!char.IsDigit(xParts[1][0]) && char.IsDigit(yParts[1][0]))
return 1; // Digits go before non-digit.
return string.Compare(yParts[1], xParts[1], StringComparison.Ordinal);
}
Which you can then use to sort a string list, array or IEnumerable<string>, like so:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
static class Program
{
static void Main()
{
var strings = new []
{
"AB1001_A",
"AB1001_B",
"AB1002_2",
"AB1002_C",
"AB1003_0",
"AB1003_",
"AB1003_B",
"AB1003_A",
"AB1001_0",
"AB1001_1",
"AB1001_2",
"AB1001_C",
"AB1002_B",
"AB1002_A",
};
static int compare(string x, string y)
{
var xParts = x.Split('_', StringSplitOptions.RemoveEmptyEntries);
var yParts = y.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (xParts.Length != yParts.Length)
return yParts.Length - xParts.Length;
if (xParts.Length == 0)
return 0;
int comp = string.Compare(xParts[0], yParts[0], StringComparison.Ordinal);
if (comp != 0 || xParts.Length == 1)
return comp;
if (char.IsDigit(xParts[1][0]) && !char.IsDigit(yParts[1][0]))
return -1; // Digits go before non-digit.
if (!char.IsDigit(xParts[1][0]) && char.IsDigit(yParts[1][0]))
return 1; // Digits go before non-digit.
return string.Compare(yParts[1], xParts[1], StringComparison.Ordinal);
}
var stringList = strings.ToList();
stringList.Sort(compare);
Console.WriteLine("Sorted list:");
Console.WriteLine(string.Join("\n", stringList));
var stringArray = strings.ToArray();
Array.Sort(stringArray, compare);
Console.WriteLine("\nSorted array:");
Console.WriteLine(string.Join("\n", stringArray));
var sequence = strings.Select(element => element);
var sortedSeq = sequence.OrderBy(element => element, Comparer<string>.Create(compare));
Console.WriteLine("\nSorted sequence:");
Console.WriteLine(string.Join("\n", sortedSeq));
}
}
}
Try it online on .Net Fiddle
Finally I got the soln by this
var mystrings = new []
{
"AB1001_A",
"AB1001_B",
"AB1002_2",
"AB1002_C",
"AB1003_0",
"AB1003_",
"AB1003_B",
"AB1003_A",
"AB1001_0",
"AB1001_1",
"AB1001_2",
"AB1001_C",
"AB1002_B",
"AB1002_A",
};
mystrings.Cast<string>().OrderBy(x => PadNumbers(x));
and then PadNumbers function as like below
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}

Icomparer c# List

I have a list of image name like this {"1.jpg", "10.jpg", "2.jpg"}.
I would like to sort like this {"1.jpg", "2.jpg", "10.jpg"}.
I created this comparer. That means if x or y == "DSC_10.jpg", so if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...} don't sort and keep the list.
var comparer = new CompareImageName();
imageUrls.Sort(comparer);
return imageUrls;
public class CompareImageName : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var l = x.Split('/');
var l1 = y.Split('/');
int a, b;
var rs = int.TryParse(l[l.Length - 1].Split('.')[0], out a);
var rs2 = int.TryParse(l1[l1.Length - 1].Split('.')[0], out b);
if (!rs || !rs2) return 0;
if (a == b || a == 0 && b == 0) return 0;
return a > b ? 1 : -1;
}
}
This sort correctly with name {"1.jpg", "10.jpg", "2.jpg"}, but incorrectly if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...}.
I read in MSDN:
What wrong with my code?
I think you're better off doing a bit of Regex for this. Try this solution:
public class CompareImageName : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var regex = new Regex(#"/(((?<prefix>\w*)_)|)((?<number>\d+))\.jpg$");
var mx = regex.Match(x);
var my = regex.Match(y);
var r = mx.Groups["prefix"].Value.CompareTo(my.Groups["prefix"].Value);
if (r == 0)
{
r = int.Parse(mx.Groups["number"].Value).CompareTo(int.Parse(my.Groups["number"].Value));
}
return r;
}
}
Apart from the Regex string itself this is easier to follow the logic.
Here is your solution check this example, following class will do the comparison
public class NumericCompare : IComparer<string>
{
public int Compare(string x, string y)
{
int input1,input2;
input1=int.Parse(x.Substring(x.IndexOf('_')+1).Split('.')[0]);
input2= int.Parse(y.Substring(y.IndexOf('_')+1).Split('.')[0]);
return Comparer<int>.Default.Compare(input1,input2);
}
}
You can make use of this class like the following:
var imageUrls = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };
var comparer = new NumericCompare();
imageUrls.Sort(comparer);
Console.WriteLine(String.Join("\n",imageUrls));
Try this with simple OrderBy
var SortedList = imageUrls.OrderBy(
x=>int.Parse(
x.Substring(x.IndexOf('_')+1).Split('.')[0])
).ToList();
Basically what you want to do is sort by the numeric part within the string. You are almost there. You just have to handle the part when you split a case like this DSC_2.jpg using a . then the first part is not all digits. So you need to get digits and then compare those. Here is the code. Please note I have made the assumption you will have backslash and if that is not the case then please handle it:
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var nameX = x.Substring(x.LastIndexOf('/'));
var nameY = y.Substring(y.LastIndexOf('/'));
var nameXParts = nameX.Split('.');
var nameYParts = nameY.Split('.');
int a, b;
var rs = int.TryParse(nameXParts[0], out a);
var rs2 = int.TryParse(nameYParts[0], out b);
var nameXDigits = string.Empty;
if (!rs)
{
for (int i = 0; i < nameXParts[0].Length; i++)
{
if (Char.IsDigit(nameXParts[0][i]))
nameXDigits += nameXParts[0][i];
}
}
var nameYDigits = string.Empty;
if (!rs2)
{
for (int i = 0; i < nameYParts[0].Length; i++)
{
if (Char.IsDigit(nameYParts[0][i]))
nameYDigits += nameYParts[0][i];
}
}
int.TryParse(nameXDigits, out a);
int.TryParse(nameYDigits, out b);
if (a == b || a == 0 && b == 0) return 0;
return a > b ? 1 : -1;
}
Don't use imageUrls.Sort(comparer); on List because it doesn't accept 0 value as keeping the order of elements.
Reason:
The Sort performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.
Link: https://msdn.microsoft.com/en-gb/library/w56d4y5z.aspx
Solution: Let's try to use OrderBy with your compare
var imageUrls1 = new List<string>() { "1.jpg", "10.jpg", "2.jpg" };
var imageUrls2 = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };
var comparer = new CompareImageName();
//Sort normally
imageUrls1 = imageUrls1.OrderBy(p=>p, comparer).ToList();
//Keep the order as your expectation
imageUrls2 = imageUrls2.OrderBy(p=>p, comparer).ToList();
Maybe you can try doing this in a function instead of writing a comparator. I can't think of a good way to implement this logic as a comparator since there are different rules based on the contents (don't sort if the file name is not numeric).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace sortinglists
{
public class MainProgram
{
public static void Main()
{
var imageUrlsNumbers = new List<string>();
imageUrlsNumbers.Add("c:/a/b/1.jpg");
imageUrlsNumbers.Add("c:/a/b/10.jpg");
imageUrlsNumbers.Add("c:/a/b/2.jpg");
CustomSort(ref imageUrlsNumbers);
foreach (var imageUrl in imageUrlsNumbers)
{
Console.WriteLine(imageUrl);
}
var imageUrlsText = new List<string>();
imageUrlsText.Add("c:/a/b/DSC_1.jpg");
imageUrlsText.Add("c:/a/b/DSC_10.jpg");
imageUrlsText.Add("c:/a/b/DSC_2.jpg");
CustomSort(ref imageUrlsText);
foreach (var imageUrl in imageUrlsText)
{
Console.WriteLine(imageUrl);
}
}
public static void CustomSort(ref List<string> imageUrls)
{
if (imageUrls
.Select(s => s.Substring(s.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.Select(t => t.Substring(0, t.IndexOf(".", StringComparison.OrdinalIgnoreCase)))
.Where(u => new Regex("[A-Za-z_]").Match(u).Success)
.Any())
{
imageUrls = imageUrls
.Select(x => x.Substring(x.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.ToList();
}
else
{
imageUrls = imageUrls
.Select(v => v.Substring(v.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.OrderBy(w => Convert.ToInt32(w.Substring(0, w.LastIndexOf(".", StringComparison.OrdinalIgnoreCase))))
.ToList();
}
}
}
}
The output for imageUrlsNumbers after sorting is:
1.jpg
2.jpg
10.jpg
And the output for imageUrlsText after sorting is:
DSC_1.jpg
DSC_10.jpg
DSC_2.jpg

How to split a item in array to two item with conditional?

I have an example above.
Have array like:
array[0] = TODO 06:15PMJoin Michael
array[1] = WakeUp
array[2] = Going to schools
I want it become like:
array[0] = TODO 06:15PM
array[1] = Join Michael
array[2] = WakeUp
array[3] = Going to schools
In this example, I split item have content TODO 06:15PMJoin Michael to new two item.
Have two separate questions here:
How to create a role for creating a new item in an array?
I tried with my code:
var splitList = words.SelectMany(x => x.Contains("AM") || x.Contains("PM"))
But I don't know how to split from text AM or PM to the new item in arrays.
You can try finding AM/PM and get substrings:
String[] array = new String[] {
"TODO 06:15PMJoin Michael",
"WakeUp",
"Going to schools"
};
var result = array
.SelectMany(line => {
int p = line.IndexOf("AM");
if (p >= 0)
return new String[] {
line.Substring(0, p + "AM".Length),
line.Substring(p + "AM".Length) };
p = line.IndexOf("PM");
if (p >= 0)
return new String[] {
line.Substring(0, p + "PM".Length),
line.Substring(p + "PM".Length) };
return new String[] { line };
}
);
//.ToArray(); // if you want to have array representation
// Test
Console.Write(String.Join(Environment.NewLine, result));
You can also try this:
string[] array = new string[] {"TODO 06:15PMJoin Michael",
"WakeUp",
"Going to schools"};
string[] SplitArray(string[] array)
{
List<string> returnArray = new List<string>();
foreach (string item in array)
{
int index = GetIndex(item);
if (index >= 0)
{
string s1 = item.Substring(0, index + 2);
string s2 = item.Substring(index + 2);
returnArray.Add(s1);
returnArray.Add(s2);
}
else
{
returnArray.Add(item);
}
}
return returnArray.ToArray();
}
int GetIndex(string s)
{
int index = GetIndexOf(s, "AM");
if (index == -1)
{
index = GetIndexOf(s, "PM");
}
return index;
}
int GetIndexOf(string s, string delim)
{
int index = -1;
int tempIndex = 0;
do
{
tempIndex = s.IndexOf(delim, tempIndex);
if (tempIndex > 0)
{
if (char.IsDigit(s[tempIndex-1]))
{
index = tempIndex;
break;
}
}
}
while(tempIndex >= 0);
return index;
}

Compare strings for equality

I want to compare a collection of strings and return the the equal parts until a not equal part occurs. (and remove traling whitespace).
example:
List<string> strList = new List<string>
{
"string xyz stop",
"string abc stop",
"string qrt stop"
};
string result = GetEqualName(strList); // This should return "string"
I made the following method that works
string GetEqualName(IEnumerable<string> strList)
{
string outString = "";
bool firstTime = true;
foreach (var subString in strList)
{
if (firstTime)
{
outString = subString;
firstTime = false;
}
else
{
string stringBuilder = "";
for (int i = 0; i < outString.Count(); i++)
{
if (outString[i] == subString[i])
stringBuilder = stringBuilder + outString[i];
else
break;
}
outString = stringBuilder;
}
}
outString = outString.TrimEnd(' '); // Remove traling whitespace
return outString;
}
I just feel that this is something that can be done in a few lines and I am overdoing it. Do you guys have any suggestions?
You can Zip two strings together, take the pairs that are equal, and then create a string of those characters.
public static string LargestCommonPrefix(string first, string second)
{
return new string(first.Zip(second, Tuple.Create)
.TakeWhile(pair => pair.Item1 == pair.Item2)
.Select(pair => pair.Item1)
.ToArray());
}
Once you've solved the problem for the case of combining two strings, you can easily apply it to a sequence of strings:
public static string LargestCommonPrefix(IEnumerable<string> strings)
{
return strings.Aggregate(LargestCommonPrefix);
}
This little function does basically the same as your version, but shorter.
string GetEqualName(IEnumerable<string> strList)
{
int limit = strList.Min(s => s.Length);
int i = 0;
for (; i < limit; i++)
{
if (strList.Select(s => s.Substring(0,i+1)).Distinct().Count() > 1)
{
break;
}
}
return strList.First().Substring(0, i).Trim();
}
Here's a different method which does what you want. I looks for the longest common substring from left to right using a HashSet<string>:
string GetCommonStartsWith(IEnumerable<string> strList, StringComparer comparer = null)
{
if(!strList.Any() || strList.Any(str => string.IsNullOrEmpty(str)))
return null;
if(!strList.Skip(1).Any())
return strList.First(); // only one
if(comparer == null) comparer = StringComparer.CurrentCulture;
int commonLength = strList.Min(str => str.Length);
for (int length = commonLength; length > 0; length--)
{
HashSet<string> duptester = new HashSet<string>(comparer);
string first = strList.First().Substring(0, length).TrimEnd();
duptester.Add(first);
bool allEqual = strList.Skip(1)
.All(str => !duptester.Add(str.Substring(0, length).TrimEnd()));
if (allEqual)
return first;
}
return null;
}
Here's a version that uses less LINQ than some of the other answers and might possibly be more performant.
string GetEqualName(IEnumerable<string> strList)
{
StringBuilder builder = new StringBuilder();
int minLength = strList.Min(s => s.Length);
for (int i = 0; i < minLength; i++)
{
char? c = null;
foreach (var s in strList)
{
if (c == null)
c = s[i];
else if (s[i] != c)
return builder.ToString().TrimEnd();
}
builder.Append(c);
}
return builder.ToString().TrimEnd();
}

string sorting in C#

I have an array of strings like the following:
"access"
"Addition"
"account"
"base"
"Brick"
"zammer"
"Zilon"
I want them to sort them witht the following rules"
Capital letters for a given character should come first.
The capital and small letters should be sorted in their own groups.
Thus, the output should be:
"Addition"
"access"
"account"
"Brick"
"base"
"Zilon"
"zammer"
The language I am using is C# and .Net 4.0.
Proper set of OrderBy/ThenBy calls will do the trick.
Order by first letter lowercased, to get all as and As first, then bs and Bs, etc.
Then by IsLower(firstCharacter), which will get the uppercased items for each letter first.
Then by the entire string.
var sorted = source.OrderBy(s => char.ToLower(s[0]))
.ThenBy(s => char.IsLower(s[0]))
.ThenBy(s => s)
.ToList();
Try like this
List<string> list = new List<string>();
list.Add("access");
list.Add("Addition");
list.Add("account");
list.Add("base")
list.Add("Brick")
list.Add("zammer")
list.Add("Zilon")
list = list.Where(r => char.IsLower(r[0])).OrderBy(r => r)
.Concat(list.Where(r => char.IsUpper(r[0])).OrderBy(r => r)).ToList();
for (int i = 0; i < list.Count; i++)
Console.WriteLine(list[i]);
Below solution works for more than one Caps.
static void Main(string[] args)
{
var names = new List<String>() {
"access",
"Addition",
"ADDition",
"ADdition",
"account",
"base",
"Brick",
"zammer",
"Zilon"
};
names.Sort((one, two) =>
{
int result = 0;
var oneArray = one.ToCharArray();
var twoArray = two.ToCharArray();
var minLength = Math.Min(oneArray.Length, twoArray.Length) - 1;
var i = 0;
while (i < minLength)
{
//Diff Letter
if (Char.ToUpper(one[i]) != Char.ToUpper(two[i]))
{
result = Char.ToUpper(one[i]) - Char.ToUpper(two[i]);
break;
}
// Same Letter, same case
if (oneArray[i] == twoArray[i])
{
i++;
continue;
}
// Same Letter, diff case
result = one[i] - two[i];
break;
}
return result;
});
foreach (string s in names)
Console.WriteLine(s);
Console.WriteLine("done");
If you want to go beyond the first character, I should implement a comparer:
class MyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
if ((x == null) && (y == null))
{
return 0;
}
if (x == null)
{
return 1;
}
if (y == null)
{
return -1;
}
var l = Math.Min(x.Length, y.Length);
for (var i = 0; i < l; i++)
{
var c = x[i];
var d = y[i];
if (c != d)
{
if (char.ToLowerInvariant(c) == char.ToLowerInvariant(d))
{
return StringComparer.Ordinal.Compare(new string(c, 1), new string(d, 1));
}
else
{
return StringComparer.OrdinalIgnoreCase.Compare(new string(c, 1), new string(d, 1));
}
}
}
return x.Length == y.Length ? 0 : x.Length > y.Length ? 1 : -1;
}
}
And then use it:
var myComparer = new MyComparer();
source.OrderBy(s => s, myComparer);

Categories