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
Related
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'));
}
I need to sort a List<string> following 2 rules.
My string element will always be formatted as XXXnnnnE or XXXnnnnD where X are capitalized letters from A to Z and n are digit from 0 to 9.
I want to sort my list alphabetically, but I want E string to come before D string as shown below
DFG0001D
AWK0007E
ORK0127E
AWK0007D
DFG0001E
ORK0127D
need to be sorted as
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D
How could I achieve this ?
Thanks for help
Here is snippet how you can do this with Linq OrderBy and ThenByDescending operations:
string[] arr = { "DFG0001D", "AWK0007E", "ORK0127E", "AWK0007D", "DFG0001E", "ORK0127D" };
arr = arr
.OrderBy(r => r.Substring(0, 7))
.ThenByDescending(s => s.Substring(7, 1))
.ToArray();
you can use a custom delegate and compare the 1st 3 chars and the last one:
List<string> x = new List<string>();
x.Add("DFG0001D");
x.Add("AWK0007E");
x.Add("ORK0127E");
x.Add("AWK0007D");
x.Add("DFG0001E");
x.Add("ORK0127D");
x.Sort(delegate(string c1, string c2) {
string a = c1.Substring(0, 3)+c1.Substring(c1.Length-1, 1);
string b = c2.Substring(0, 3)+c2.Substring(c2.Length-1, 1);
return (a.CompareTo(b));
});
Console.WriteLine("After sort...");
foreach (string i in x)
{
Console.WriteLine(i);
}
Fiddle example : https://dotnetfiddle.net/YAzvB4
var list = new List<string>{
"DFG0001D",
"AWK0007E",
"ORK0127E",
"AWK0007D",
"DFG0001E",
"ORK0127D"
};
list.Sort((str1, str2) => {
var eq = string.Compare(str1.Substring(0, str1.Length - 1), str2.Substring(0, str2.Length - 1));
if (eq == 0)
eq = string.Compare(str2[str2.Length - 1].ToString(), "E");
return eq;
});
foreach (var str in list)
Console.WriteLine(str);
Output:
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D
just implement your own comparer like this:
class CustomStringComparer : IComparer<string>
{
private readonly IComparer<string> _baseComparer;
public CustomStringComparer(IComparer<string> baseComparer)
{
_baseComparer = baseComparer;
}
public int Compare(string x, string y)
{
// strings are completely same
if (_baseComparer.Compare(x, y) == 0)
{
return 0;
}
// strings are completely same except last char
if (_baseComparer.Compare(x.Substring(0, x.Length - 2), y.Substring(0, y.Length - 2)) == 0)
{
// if last char is E then win
return x.Last() == 'E' ? -1 : 1;
}
// defaut compare everything else
return _baseComparer.Compare(x, y);
}
}
Then you are able doing this:
static void Main(string[] args)
{
List<string> list = new List<string>()
{
"DFG0001D",
"AWK0007E",
"ORK0127E",
"AWK0007D",
"DFG0001E",
"ORK0127D"
};
list.Sort(new CustomStringComparer(StringComparer.CurrentCulture));
foreach (var item in list)
{
Console.WriteLine(item);
}
}
And output is this:
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D
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()
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);
I have a dictionary containg ID which are alphanumeric (e.g. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...
When I use the following code, d10a9 is MAX since 9 is sorted before 10
var lsd = new Dictionary<string, string>();
lsd.Add("a", "d10a10");
lsd.Add("b", "d10a9");
string max = lsd.Max(kvp => kvp.Value);
How can I get the Max value of the IDs with the Longest string combined?
I think you may try to roll your own IComparer<string>
class HumanSortComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// your human sorting logic here
}
}
Usage:
var last = collection.OrderBy(x => x.Value, new HumanSortComparer()).LastOrDefault();
if (last != null)
string max = last.Value;
this works like a charm assuming IDs always start with "d10a":
int max = lsd.Max(kvp => Convert.ToInt32(kvp.Value.Substring(4)));
Console.Write(string.Format("d10a{0}", max));
One way would be to do this
string max =lsd.Where(kvp=>kvp.Value.Length==lsd.Max(k=>k.Value.Length)).Max(kvp => kvp.Value);
however I think that this method would evalute the max length for each item so you may be better to extract it to a variable first
int maxLength=lsd.Max(kvp=>kvp.Value.Length);
string max = lsd.Where(kvp=>kvp.Value.Length == maxLength).Max(kvp => kvp.Value);
If you are going to have null strings in there you may need to perform null checks too
int maxLength=lsd.Max(kvp=>(kvp.Value??String.Empty).Length);
string max = lsd.Where(kvp=>(kvp.Value??String.Empty).Length == maxLength).Max(kvp => kvp.Value);
Alternatively treat your string as Base36 number and convert to long for the max function and then convert back again to get the max string.
string max =lsd.Max(tvp=>tvp.Value.FromBase36()).ToBase36();
public static class Base36 {
public static long FromBase36(this string src) {
return src.ToLower().Select(x=>(int)x<58 ? x-48 : x-87).Aggregate(0L,(s,x)=>s*36+x);
}
public static string ToBase36(this long src) {
StringBuilder result=new StringBuilder();
while(src>0) {
var digit=(int)(src % 36);
digit=(digit<10) ? digit+48 :digit+87;
result.Insert(0,(char)digit);
src=src / 36;
}
return result.ToString();
}
}
Finally just just the Agregate extension method instead of Max as this lets you do all the comparison logic....
lsd.Agregate(string.Empty,(a,b)=> a.Length == b.Length ? (a>b ? a:b) : (a.Length>b.Length ? a:b));
This could doesn't have null checks but you easily add them in.
I think if you did this:
var max = lsd.OrderByDescending(x => x.Value)
.GroupBy(x => x.Value.Length)
.OrderByDescending(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
It may give you what you want.
You need StringComparer.OrdinalIgnoreCase.
Without the need to use linq, the function that do that is quite simple.
Complexity is, of course, O(n).
public static KeyValuePair<string, string> FindMax(IEnumerable<KeyValuePair<string, string>> lsd)
{
var comparer = StringComparer.OrdinalIgnoreCase;
var best = default(KeyValuePair<string, string>);
bool isFirst = true;
foreach (KeyValuePair<string, string> kvp in lsd)
{
if (isFirst || comparer.Compare(kvp.Value, best.Value) > 0)
{
isFirst = false;
best = kvp;
}
}
return best;
}
Okay - I think you need to first turn each key into a series of strings and numbers - since you need the whole number to be able to determine the comparison. Then you implement an IComparer - I've tested this with your two input strings as well as with a few others and it appears to do what you want. The performance could possibly be improved - but I was brainstorming it!
Create this class:
public class ValueChain
{
public readonly IEnumerable<object> Values;
public int ValueCount = 0;
private static readonly Regex _rx =
new Regex("((?<alpha>[a-z]+)|(?<numeric>([0-9]+)))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public ValueChain(string valueString)
{
Values = Parse(valueString);
}
private IEnumerable<object> Parse(string valueString)
{
var matches = _rx.Matches(valueString);
ValueCount = matches.Count;
foreach (var match in matches.Cast<Match>())
{
if (match.Groups["alpha"].Success)
yield return match.Groups["alpha"].Value;
else if (match.Groups["numeric"].Success)
yield return int.Parse(match.Groups["numeric"].Value);
}
}
}
Now this comparer:
public class ValueChainComparer : IComparer<ValueChain>
{
private IComparer<string> StringComparer;
public ValueChainComparer()
: this(global::System.StringComparer.OrdinalIgnoreCase)
{
}
public ValueChainComparer(IComparer<string> stringComparer)
{
StringComparer = stringComparer;
}
#region IComparer<ValueChain> Members
public int Compare(ValueChain x, ValueChain y)
{
//todo: null checks
int comparison = 0;
foreach (var pair in x.Values.Zip
(y.Values, (xVal, yVal) => new { XVal = xVal, YVal = yVal }))
{
//types match?
if (pair.XVal.GetType().Equals(pair.YVal.GetType()))
{
if (pair.XVal is string)
comparison = StringComparer.Compare(
(string)pair.XVal, (string)pair.YVal);
else if (pair.XVal is int) //unboxing here - could be changed
comparison = Comparer<int>.Default.Compare(
(int)pair.XVal, (int)pair.YVal);
if (comparison != 0)
return comparison;
}
else //according to your rules strings are always greater than numbers.
{
if (pair.XVal is string)
return 1;
else
return -1;
}
}
if (comparison == 0) //ah yes, but were they the same length?
{
//whichever one has the most values is greater
return x.ValueCount == y.ValueCount ?
0 : x.ValueCount < y.ValueCount ? -1 : 1;
}
return comparison;
}
#endregion
}
Now you can get the max using OrderByDescending on an IEnumerable<ValueChain> and FirstOrDefault:
[TestMethod]
public void TestMethod1()
{
List<ValueChain> values = new List<ValueChain>(new []
{
new ValueChain("d10a9"),
new ValueChain("d10a10")
});
ValueChain max =
values.OrderByDescending(v => v, new ValueChainComparer()).FirstOrDefault();
}
So you can use this to sort the string values in your dictionary:
var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value),
new ValueChainComparer()).FirstOrDefault();