How search triple elements (key_value pair Dictionary) by one of them - c#

Here is my code:
public class PairedKeys
{
public byte Key_1 { get; set; }
public byte Key_2 { get; set; }
public PairedKeys(byte key_1, byte key_2)
{
Key_1 = key_1;
Key_2 = key_2;
}
}
public static class My_Class
{
static Dictionary<PairedKeys, char> CharactersMapper = new Dictionary<PairedKeys, char>()
{
{ new PairedKeys(128, 48), 'a' },
{ new PairedKeys(129, 49), 'b' }
}
}
How can I get value of CharactersMapper by searching Key_2?
Here's my attempt:
for (int j = 0; j < CharactersMapper.Count; j++)
{
try
{
char ch = CharactersMapper[new PairedKeys(????, Key_2)];
}
catch
{
}
}

Using dictionary in this manner, there's not going to be an optimized (i.e. O(1)) way of achieving this. You can, however, just loop through, which will be O(n):
var result = dictionary.Where(d => d.Key.Key_2 == 3);
assuming you're looking for 3, of course.

Using LINQ, you can do the following to return a single item:
var ch = CharactersMapper.Single(cm => cm.Key.Key_2 == 49);
Or if you're expecting more than one item:
var chList = CharactersMapper.Where(cm => cm.Key.Key_2 == 49);
These will return a KeyValuePair<‌​Classes.PairedKeys,char> and IEnumerable<KeyValuePair<‌​Classes.PairedKeys,char>> as you've noted in the comments. If you want to get at just the char contents, you can use the Select method:
//Single char
char singleChar = CharactersMapper.Single(cm => cm.Key.Key_2 == 49).Select(c => c.Value);
//list of chars
IList<char> charList = CharactersMapper.Where(cm => cm.Key.Key_2 == 49).Select(c => c.Value).ToList();

Related

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

Sort Array on on Value Difference

I Have An Array,for example
string[] stArr= new string[5] { "1#3", "19#24", "10#12", "13#18", "20#21" };
i want to sort this array on
3-1=2;
24-19=5;
12-10=2;
18-13=5;
21-20=1;
and the sorting result should be like
string[] stArr= new string[5] { "20#21", "1#3", "10#12", "13#18", "20#21" };
I have to find the solution for all possible cases.
1>length of the array is not fixed(element in the array)
2>y always greater than x e.g x#y
3> i can not use list
You can use LINQ:
var sorted = stArr.OrderBy(s => s.Split('#')
.Select(n => Int32.Parse(n))
.Reverse()
.Aggregate((first,second) => first - second));
For Your Case:
stArr = stArr.OrderBy(s => s.Split('#')
.Select(n => Int32.Parse(n))
.Reverse()
.Aggregate((first,second) => first - second)).ToArray();
try this
string[] stArr = new string[5] { "1#3", "19#24", "10#12", "13#18", "20#21" };
Array.Sort(stArr, new Comparison<string>(compare));
int compare(string z, string t)
{
var xarr = z.Split('#');
var yarr = t.Split('#');
var x1 = int.Parse(xarr[0]);
var y1 = int.Parse(xarr[1]);
var x2 = int.Parse(yarr[0]);
var y2 = int.Parse(yarr[1]);
return (y1 - x1).CompareTo(y2 - x2);
}
Solving this problem is identical to solving any other sorting problem where the order is to be specified by your code - you have to write a custom comparison method, and pass it to the built-in sorter.
In your situation, it means writing something like this:
private static int FindDiff(string s) {
// Split the string at #
// Parse both sides as int
// return rightSide-leftSide
}
private static int CompareDiff(string a, string b) {
return FindDiff(a).CompareTo(FindDiff(b));
}
public static void Main() {
... // Prepare your array
string[] stArr = ...
Array.Sort(stArr, CompareDiff);
}
This approach uses Array.Sort overload with the Comparison<T> delegate implemented in the CompareDiff method. The heart of the solution is the FindDiff method, which takes a string, and produces a numeric value which must be used for comparison.
you can try the following ( using traditional way)
public class Program
{
public static void Main()
{
string[] strArr= new string[5] { "1#3", "19#24", "10#12", "13#18", "20#21" };
var list = new List<Item>();
foreach(var item in strArr){
list.Add(new Item(item));
}
strArr = list.OrderBy(t=>t.Sort).Select(t=>t.Value).ToArray();
foreach(var item in strArr)
Console.WriteLine(item);
}
}
public class Item
{
public Item(string str)
{
var split = str.Split('#');
A = Convert.ToInt32(split[0]);
B = Convert.ToInt32(split[1]);
}
public int A{get; set;}
public int B{get; set;}
public int Sort { get { return Math.Abs(B - A);}}
public string Value { get { return string.Format("{0}#{1}",B,A); }}
}
here a working demo
hope it will help you
Without LINQ and Lists :) Old School.
static void Sort(string [] strArray)
{
try
{
string[] order = new string[strArray.Length];
string[] sortedarray = new string[strArray.Length];
for (int i = 0; i < strArray.Length; i++)
{
string[] values = strArray[i].ToString().Split('#');
int index=int.Parse(values[1].ToString()) - int.Parse(values[0].ToString());
order[i] = strArray[i].ToString() + "," + index;
}
for (int i = 0; i < order.Length; i++)
{
string[] values2 = order[i].ToString().Split(',');
if (sortedarray[int.Parse(values2[1].ToString())-1] == null)
{
sortedarray[int.Parse(values2[1].ToString())-1] = values2[0].ToString();
}
else
{
if ((int.Parse(values2[1].ToString())) >= sortedarray.Length)
{
sortedarray[(int.Parse(values2[1].ToString())-1) - 1] = values2[0].ToString();
}
else if ((int.Parse(values2[1].ToString())) < sortedarray.Length)
{
sortedarray[(int.Parse(values2[1].ToString())-1) + 1] = values2[0].ToString();
}
}
}
for (int i = 0; i < sortedarray.Length; i++)
{
Console.WriteLine(sortedarray[i]);
}
Console.Read();
}
catch (Exception ex)
{
throw;
}
finally
{
}

Locating items in List whether they occur more than once in C#

In addition to the question: Get List<> element position in c# using LINQ
First of all, let me explaing why I do what I do :)
I'm trying to encrypt a string into a one-line code which holds how many times a letter is used in the string and also with its positions. By this method, I'll be able to decrypt the code and get(reproduce) the full text again. Let's say myString is "How are you today". I'll encrypt it like
o3[1,9,13]," "3[3,7,11],a2[4,15],y2[9,17],H1[0],w1[2],r1[5],e1[6],u1[10],t1[12],d1[14],?[18]
I know it looks odd but think about working on bigger strings like e-books. This could handle all the text in one or two lines.
The encryption is not about the security, it's just about holding big datas in smaller spaces.
In my code, I can convert the string into list, count how many times a letter is used but I can't define the positions of the letters when they occur more than once.
private void btnKoda_Click(object sender, EventArgs e)
{
var yazi = txtYazi.Text;
List<char> liste = yazi.ToList();
List<string> tut = new List<string>();
foreach (char harf in liste)
{
for (int i = 0; i < liste.Count; i++)
{
char ekle = liste[i];
tut.Add(ekle.ToString());
}
foreach (var karakter in tut)
{
txtKod.Text += karakter;
}
// holds statics
var istatistik =
from c in tut
group c by c into g
select new { g.Key, say = g.Count() };
var enCok =
from giris in istatistik
orderby giris.say descending
select giris;
foreach (var giris in enCok)
{
txtHarfler.Text += string.Format("{0}: {1}\r\n", giris.Key, giris.say);
}
break;
}
Not sure what's going on in your code, but here's how I would do it:
private void button1_Click(object sender, EventArgs e)
{
Dictionary<Char, Encoding> dct = new Dictionary<char, Encoding>();
string data = "How are you today";
for(int i = 0; i < data.Length; i++)
{
Char C = data[i];
if (!dct.ContainsKey(C))
{
dct.Add(C, new Encoding(C));
}
dct[C].AddOccurence(i);
}
StringBuilder SB = new StringBuilder();
foreach(Encoding enc in dct.Values)
{
if (SB.Length == 0)
{
SB.Append(enc.ToString());
}
else
{
SB.Append("," + enc.ToString());
}
}
Console.WriteLine(SB.ToString());
}
Here's the Encoding class:
public class Encoding
{
private Char _C;
private List<int> _Positions;
private Encoding() {}
public Encoding(Char C)
{
this._C = C;
this._Positions = new List<int>();
}
public Char Character
{
get
{
return _C;
}
}
public int Count
{
get
{
return _Positions.Count;
}
}
public int[] Occurences
{
get
{
return _Positions.ToArray();
}
}
public override string ToString()
{
string[] values = Array.ConvertAll(this.Occurences.ToArray(), x => x.ToString());
return this.Character.ToString() + this.Count.ToString() + "[" + String.Join(",", values) + "]";
}
public void AddOccurence(int position)
{
this._Positions.Add(position);
}
}
Original String:
How are you today
Output:
H1[0],o3[1,9,13],w1[2], 3[3,7,11],a2[4,15],r1[5],e1[6],y2[8,16],u1[10],t1[12],d1[14]

Array.Sort for strings with numbers [duplicate]

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()

How do I sort strings alphabetically while accounting for value when a string is numeric?

I'm trying to sort an array of numbers that are strings and I'd like them to sort numerically.
The catch is that I cannot convert the numbers into int.
Here is the code:
string[] things= new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => x))
{
Console.WriteLine(thing);
}
Output:
101, 102, 103, 105, 90
I'd like:
90, 101, 102, 103, 105
EDIT:
The output can't be 090, 101, 102...
Updated the code sample to say "things" instead of "sizes". The array can be something like this:
string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };
That means it needs to be sorted alphabetically and by number:
007, 90, bob, lauren, paul
Pass a custom comparer into OrderBy. Enumerable.OrderBy will let you specify any comparer you like.
This is one way to do that:
void Main()
{
string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};
foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
{
Console.WriteLine(thing);
}
}
public class SemiNumericComparer: IComparer<string>
{
/// <summary>
/// Method to determine if a string is a number
/// </summary>
/// <param name="value">String to test</param>
/// <returns>True if numeric</returns>
public static bool IsNumeric(string value)
{
return int.TryParse(value, out _);
}
/// <inheritdoc />
public int Compare(string s1, string s2)
{
const int S1GreaterThanS2 = 1;
const int S2GreaterThanS1 = -1;
var IsNumeric1 = IsNumeric(s1);
var IsNumeric2 = IsNumeric(s2);
if (IsNumeric1 && IsNumeric2)
{
var i1 = Convert.ToInt32(s1);
var i2 = Convert.ToInt32(s2);
if (i1 > i2)
{
return S1GreaterThanS2;
}
if (i1 < i2)
{
return S2GreaterThanS1;
}
return 0;
}
if (IsNumeric1)
{
return S2GreaterThanS1;
}
if (IsNumeric2)
{
return S1GreaterThanS2;
}
return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
}
}
Just pad with zeroes to the same length:
int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));
Value is a string
List = List.OrderBy(c => c.Value.Length).ThenBy(c => c.Value).ToList();
Works
And, how about this ...
string[] sizes = new string[] { "105", "101", "102", "103", "90" };
var size = from x in sizes
orderby x.Length, x
select x;
foreach (var p in size)
{
Console.WriteLine(p);
}
There is a native function in windows StrCmpLogicalW that will compare in strings numbers as numbers instead of letters. It is easy to make a comparer that calls out to that function and uses it for it's comparisons.
public class StrCmpLogicalComparer : Comparer<string>
{
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string x, string y);
public override int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
It even works on strings that have both text and numbers. Here is a example program that will show the diffrence between the default sort and the StrCmpLogicalW sort
class Program
{
static void Main()
{
List<string> items = new List<string>()
{
"Example1.txt", "Example2.txt", "Example3.txt", "Example4.txt", "Example5.txt", "Example6.txt", "Example7.txt", "Example8.txt", "Example9.txt", "Example10.txt",
"Example11.txt", "Example12.txt", "Example13.txt", "Example14.txt", "Example15.txt", "Example16.txt", "Example17.txt", "Example18.txt", "Example19.txt", "Example20.txt"
};
items.Sort();
foreach (var item in items)
{
Console.WriteLine(item);
}
Console.WriteLine();
items.Sort(new StrCmpLogicalComparer());
foreach (var item in items)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
which outputs
Example1.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example2.txt
Example20.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example1.txt
Example2.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example20.txt
try this
sizes.OrderBy(x => Convert.ToInt32(x)).ToList<string>();
Note:
this will helpful when all are string convertable to int.....
You say you cannot convert the numbers into int because the array can contain elements that cannot be converted to int, but there is no harm in trying:
string[] things = new string[] { "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" };
Array.Sort(things, CompareThings);
foreach (var thing in things)
Debug.WriteLine(thing);
Then compare like this:
private static int CompareThings(string x, string y)
{
int intX, intY;
if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
return intX.CompareTo(intY);
return x.CompareTo(y);
}
Output: 007, 90, 90, 101, 102, 103, 105, bob, lauren, paul
This site discusses alphanumeric sorting and will sort the numbers in a logical sense instead of an ASCII sense. It also takes into account the alphas around it:
http://www.dotnetperls.com/alphanumeric-sorting
EXAMPLE:
C:/TestB/333.jpg
11
C:/TestB/33.jpg
1
C:/TestA/111.jpg
111F
C:/TestA/11.jpg
2
C:/TestA/1.jpg
111D
22
111Z
C:/TestB/03.jpg
1
2
11
22
111D
111F
111Z
C:/TestA/1.jpg
C:/TestA/11.jpg
C:/TestA/111.jpg
C:/TestB/03.jpg
C:/TestB/33.jpg
C:/TestB/333.jpg
The code is as follows:
class Program
{
static void Main(string[] args)
{
var arr = new string[]
{
"C:/TestB/333.jpg",
"11",
"C:/TestB/33.jpg",
"1",
"C:/TestA/111.jpg",
"111F",
"C:/TestA/11.jpg",
"2",
"C:/TestA/1.jpg",
"111D",
"22",
"111Z",
"C:/TestB/03.jpg"
};
Array.Sort(arr, new AlphaNumericComparer());
foreach(var e in arr) {
Console.WriteLine(e);
}
}
}
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)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int 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.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
I guess this will be much more good if it has some numeric in the string.
Hope it will help.
PS:I'm not sure about performance or complicated string values but it worked good something like this:
lorem ipsum
lorem ipsum 1
lorem ipsum 2
lorem ipsum 3
...
lorem ipsum 20
lorem ipsum 21
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
int s1r, s2r;
var s1n = IsNumeric(s1, out s1r);
var s2n = IsNumeric(s2, out s2r);
if (s1n && s2n) return s1r - s2r;
else if (s1n) return -1;
else if (s2n) return 1;
var num1 = Regex.Match(s1, #"\d+$");
var num2 = Regex.Match(s2, #"\d+$");
var onlyString1 = s1.Remove(num1.Index, num1.Length);
var onlyString2 = s2.Remove(num2.Index, num2.Length);
if (onlyString1 == onlyString2)
{
if (num1.Success && num2.Success) return Convert.ToInt32(num1.Value) - Convert.ToInt32(num2.Value);
else if (num1.Success) return 1;
else if (num2.Success) return -1;
}
return string.Compare(s1, s2, true);
}
public bool IsNumeric(string value, out int result)
{
return int.TryParse(value, out result);
}
}
This seems a weird request and deserves a weird solution:
string[] sizes = new string[] { "105", "101", "102", "103", "90" };
foreach (var size in sizes.OrderBy(x => {
double sum = 0;
int position = 0;
foreach (char c in x.ToCharArray().Reverse()) {
sum += (c - 48) * (int)(Math.Pow(10,position));
position++;
}
return sum;
}))
{
Console.WriteLine(size);
}
The answer given by Jeff Paulsen is correct but the Comprarer can be much simplified to this:
public class SemiNumericComparer: IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
return Convert.ToInt32(s1) - Convert.ToInt32(s2)
if (IsNumeric(s1) && !IsNumeric(s2))
return -1;
if (!IsNumeric(s1) && IsNumeric(s2))
return 1;
return string.Compare(s1, s2, true);
}
public static bool IsNumeric(object value)
{
int result;
return Int32.TryParse(value, out result);
}
}
This works because the only thing that is checked for the result of the Comparer is if the result is larger, smaller or equal to zero. One can simply subtract the values from another and does not have to handle the return values.
Also the IsNumeric method should not have to use a try-block and can benefit from TryParse.
And for those who are not sure:
This Comparer will sort values so, that non numeric values are always appended to the end of the list. If one wants them at the beginning the second and third if block have to be swapped.
public class NaturalSort: IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string x, string y);
public int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
arr = arr.OrderBy(x => x, new NaturalSort()).ToArray();
The reason I needed it was to get filed in a directory whose filenames started with a number:
public static FileInfo[] GetFiles(string path)
{
return new DirectoryInfo(path).GetFiles()
.OrderBy(x => x.Name, new NaturalSort())
.ToArray();
}
Try this :
string[] things= new string[] { "105", "101", "102", "103", "90" };
int tmpNumber;
foreach (var thing in (things.Where(xx => int.TryParse(xx, out tmpNumber)).OrderBy(xx => int.Parse(xx))).Concat(things.Where(xx => !int.TryParse(xx, out tmpNumber)).OrderBy(xx => xx)))
{
Console.WriteLine(thing);
}
Expanding on Jeff Paulsen answer. I wanted to make sure it didn't matter how many number or char groups were in the strings:
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
if (int.TryParse(s1, out var i1) && int.TryParse(s2, out var i2))
{
if (i1 > i2)
{
return 1;
}
if (i1 < i2)
{
return -1;
}
if (i1 == i2)
{
return 0;
}
}
var text1 = SplitCharsAndNums(s1);
var text2 = SplitCharsAndNums(s2);
if (text1.Length > 1 && text2.Length > 1)
{
for (var i = 0; i < Math.Max(text1.Length, text2.Length); i++)
{
if (text1[i] != null && text2[i] != null)
{
var pos = Compare(text1[i], text2[i]);
if (pos != 0)
{
return pos;
}
}
else
{
//text1[i] is null there for the string is shorter and comes before a longer string.
if (text1[i] == null)
{
return -1;
}
if (text2[i] == null)
{
return 1;
}
}
}
}
return string.Compare(s1, s2, true);
}
private string[] SplitCharsAndNums(string text)
{
var sb = new StringBuilder();
for (var i = 0; i < text.Length - 1; i++)
{
if ((!char.IsDigit(text[i]) && char.IsDigit(text[i + 1])) ||
(char.IsDigit(text[i]) && !char.IsDigit(text[i + 1])))
{
sb.Append(text[i]);
sb.Append(" ");
}
else
{
sb.Append(text[i]);
}
}
sb.Append(text[text.Length - 1]);
return sb.ToString().Split(' ');
}
}
I also took SplitCharsAndNums from an SO Page after amending it to deal with file names.
Example of short IComparer class.
if both string arguments can be converted to integer then arguments
are parsed to integers and compared
if only one argument can be converted to integer, then integer is
prioritized (has lower value) and are inserted before string.
If no one of arguments can be converted into integer then ordinary
string comparison is used.
Code:
public class CompareIntegerStrings : IComparer<string>
{
public int Compare(string x, string y)
{
if (int.TryParse(x, out int xOut) && int.TryParse(y, out int yOut))
return xOut.CompareTo(yOut);
else if (int.TryParse(x, out _))
return -1;
else if (int.TryParse(y, out _))
return 1;
else
return x.CompareTo(y);
}
}
In this example
List<string> intStrings = new List<string> { "01","0022","abba", "11", "deep purple", "02", };
List<string> orderedIntStrings = intStrings.OrderBy(i=>i,new CompareIntegerStrings()).ToList();
ordered list orderedIntString are { "01","02","11","0022","abba","deep purple"}.
Recommend using NaturalSort.Extension(nuget/github), as it is a reasonably difficult operation as you can see from the answer.
using NaturalSort.Extension;
var ordered = things.OrderBy(x => x, StringComparison.OrdinalIgnoreCase.WithNaturalSort());
Try this out..
string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "-10" };
List<int> num = new List<int>();
List<string> str = new List<string>();
for (int i = 0; i < things.Count(); i++)
{
int result;
if (int.TryParse(things[i], out result))
{
num.Add(result);
}
else
{
str.Add(things[i]);
}
}
Now Sort the lists and merge them back...
var strsort = from s in str
orderby s.Length
select s;
var numsort = from n in num
orderby n
select n;
for (int i = 0; i < things.Count(); i++)
{
if(i < numsort.Count())
things[i] = numsort.ElementAt(i).ToString();
else
things[i] = strsort.ElementAt(i - numsort.Count());
}
I jsut tried to make a contribution in this interesting question...
My preferred solution (if all strings are numeric only):
// Order by numerical order: (Assertion: all things are numeric strings only)
foreach (var thing in things.OrderBy(int.Parse))
{
Console.Writeline(thing);
}
public class Test
{
public void TestMethod()
{
List<string> buyersList = new List<string>() { "5", "10", "1", "str", "3", "string" };
List<string> soretedBuyersList = null;
soretedBuyersList = new List<string>(SortedList(buyersList));
}
public List<string> SortedList(List<string> unsoredList)
{
return unsoredList.OrderBy(o => o, new SortNumericComparer()).ToList();
}
}
public class SortNumericComparer : IComparer<string>
{
public int Compare(string x, string y)
{
int xInt = 0;
int yInt = 0;
int result = -1;
if (!int.TryParse(x, out xInt))
{
result = 1;
}
if(int.TryParse(y, out yInt))
{
if(result == -1)
{
result = xInt - yInt;
}
}
else if(result == 1)
{
result = string.Compare(x, y, true);
}
return result;
}
}
Using Regex.Replace is so simple yet efficient. Note that the number "3" just has to be a number equal-to or larger than your longest string, so for anyone else, increase as needed.
using System.Text.RegularExpressions;
string[] things = new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => Regex.Replace(x, #"\d+", i =>
i.Value.PadLeft(3, '0'))))
{
Console.WriteLine(thing);
}
I would have commented under recursive's answer, but my reputation is too low for that.
Because recursive's answer only works with numeric strings (if You have a string like "I am just a damn long string", it would be sorted after "Not so long string") and OP edited his answer, my Idea for the question would be to sort the strings by differentiating them into numbers and not numbers:
int maxlen = items.Max(x => x.Length);
var items = items.OrderBy(x => long.TryParse(x, out _) == true ? x.PadLeft(maxlen, '0') : x);
The underscore is for discarding the output
namespace X
{
public class Utils
{
public class StrCmpLogicalComparer : IComparer<Projects.Sample>
{
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string x, string y);
public int Compare(Projects.Sample x, Projects.Sample y)
{
string[] ls1 = x.sample_name.Split("_");
string[] ls2 = y.sample_name.Split("_");
string s1 = ls1[0];
string s2 = ls2[0];
return StrCmpLogicalW(s1, s2);
}
}
}
}
Even though this is an old question, I'd like to give a solution:
string[] things= new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => Int32.Parse(x) )
{
Console.WriteLine(thing);
}
Woha quite simple right? :D

Categories