Custom sort of a string array non-alphabetical - c#

I have a string array like this:
string[] names = new string[] { "john", "paul", "ringo", "george", "janis" };
I want to sort this array using a custom criteria. It can't be alphabetical. It might be in this order: pgrj.
I've tried to implement a new IComparer, but inside the Compare() method I can't use string.Compare cause it would sort alphabetically and I don't want to do that.
The question is: How can I sort the names array in this order: pgrj ? In the 'j' case. Janis might be before John.
Thanks.

I would make an array of the letters of the alphabet, in the order that they should be sorted. Let's call this array sort. You could then do something like
for (int i = 0; i < s1.Length; i++)
{
int s1Value = sort.IndexOf(s1[i]);
int s2Value = sort.IndexOf(s2[i]);
if(s1Value > s2Value)
return 1;
if(s2Value > s1Value)
return -1;
}
return 0;
Now a few disclaimers.
I might have my -1 and 1 mixed up. I haven't looked at ICompare in a while and I forget which is which.
This does not handle the two strings being different lengths.
I have not tested this code.
You could improve on this by making a custom class to assign a weight to each character.
class CustomAlphabetSort
{
public char Character { get; set; }
public int Weight { get; set; }
}
From here you can sum of the value for each letter of a string and do the comparison that way. It might be a little more complex than your situation requires

void Main()
{
string[] names = new string[] { "john", "paul", "ringo", "george", "janis" };
var reorderedNames = names.OrderByDescending(x => x, new MyComparer());
}
class MyComparer : IComparer<string>{
int IComparer<string>.Compare(string s1, string s2){
char l1 = s1[0];
char l2 = s2[0];
int ret;
switch(l1){
case 'g': if(l2 == 'p'){
return -1;
}
else{
goto default;
}
case 'r': if(l2 == 'p' || l2 == 'g'){
return -1;
}
else{
goto default;
}
case 'j': if(l2 == 'p' || l2 == 'g' || l2 == 'r'){
return -1;
}
else{
goto default;
}
default: ret = (l2 != l1) ? 1 : s1.CompareTo(s2); return ret;
}
return ret;
}
};

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'));
}

Merging two arrays in C# [duplicate]

This question already has answers here:
How do I concatenate two arrays in C#?
(23 answers)
Joining two lists together
(15 answers)
how to sort a string array by alphabet?
(3 answers)
Sort a list alphabetically
(5 answers)
Closed 4 years ago.
In my code I have 2 arrays and I want to merge the both using right sequence. and save value to 3rd array. I tried a lot but could not find perfect solution.
public void Toolchange_T7()
{
for (int T7 = 0; T7 < sModulename_listofsafetysensor.Length; T7++)
{
if (sModulename_listofsafetysensor[T7] != null && sModulename_listofsafetysensor[T7].Contains("IR") && sModulename_listofsafetysensor[T7].Contains("FS"))
{
sElement_toolchanger[iET7] = sModulename_listofsafetysensor[T7];
iET7++;
}
}
for (int T7 = 0; T7 < sDesignation_toolchanger_t7.Length; T7++)
{
if (sDesignation_toolchanger_t7[T7] != null && sDesignation_toolchanger_t7[T7].Contains("IR") && sDesignation_toolchanger_t7[T7].Contains("FW"))
{
sDesignation_toolchanger[iMT7] = sDesignation_toolchanger_t7[T7];
iMT7++;
}
}
}
sElement_toolchanger contains:
++ST010+IR001+FW001
++ST010+IR002+FW001
++ST010+IR006+FW001
sDesignation_toolchanger contains:
++ST010+IR001.FS001
++ST010+IR001.FS002
++ST010+IR002.FS001
++ST010+IR002.FS002
++ST010+IR006.FS001
++ST010+IR006.FS002
My desired output is:
++ST010+IR001+FW001
++ST010+IR001.FS001
++ST010+IR001.FS002
++ST010+IR002+FW001
++ST010+IR002.FS001
++ST010+IR002.FS002
++ST010+IR006+FW001
++ST010+IR006.FS001
++ST010+IR006.FS002
It will be very helpful if some one know perfect solution
using System.Collections;
var mergedAndSorted = list1.Union(list2).OrderBy(o => o);
Simplest would be to:
Convert the arrays to lists:
var List1 = new List<string>(myArray1);
var List2 = new List<string>(myArray2);
Merge the two lists together:
List1.AddRange(List2);
and sort them.
List1.Sort();
According to what you said in the comments, here is a small function that will take one item from the first array, then two from the second array and so on to make a third one.
This code could be improved...
static void Main(string[] args)
{
string[] t1 = new string[] { "a", "b", "c" };
string[] t2 = new string[] { "a1", "a2", "b1", "b2", "c1", "c2" };
List<string> merged = Merge(t1.ToList(), t2.ToList());
foreach (string item in merged)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
private static List<T> Merge<T>(List<T> first, List<T> second)
{
List<T> ret = new List<T>();
for (int indexFirst = 0, indexSecond = 0;
indexFirst < first.Count && indexSecond < second.Count;
indexFirst++, indexSecond+= 2)
{
ret.Add(first[indexFirst]);
ret.Add(second[indexSecond]);
ret.Add(second[indexSecond + 1]);
}
return ret;
}
An example here
From your given sample Output, it doesn't look to be alphabetically sorted. In that scenario, you would need to use a Custom Comparer along with your OrderBy Clause after taking the Union.
For example, (since your sorting algorithm is unknown, am assuming one here for showing the example)
var first = new[]{"++ST010+IR001+FW001","++ST010+IR002+FW001","++ST010+IR006+FW001"};
var second = new[]{"++ST010+IR001.FS001",
"++ST010+IR001.FS002",
"++ST010+IR002.FS001",
"++ST010+IR002.FS002",
"++ST010+IR006.FS001",
"++ST010+IR006.FS002"};
var customComparer = new CustomComparer();
var result = first.Union(second).OrderBy(x=>x,customComparer);
Where your custom comparer is defined as
public class CustomComparer : IComparer<string>
{
int IComparer<string>.Compare(string x, string y)
{
var items1 = x.ToCharArray();
var items2 = y.ToCharArray();
var temp = items1.Zip(items2,(a,b)=> new{Item1 = a, Item2=b});
var difference = temp.FirstOrDefault(item=>item.Item1!=item.Item2);
if(difference!=null)
{
if(difference.Item1=='.' && difference.Item2=='+')
return 1;
if(difference.Item1=='+' && difference.Item2=='.')
return -1;
}
return string.Compare(x,y);
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
Output
Here's a solution if the two input arrays are already sorted. Since I suspect your comparison function is non-straightforward, I include a separate comparison function (not very efficient, but it will do). The comparison function splits the string into two (the alpha part and the numeric part) and then compares them so that the numeric part is more "important" in the sort.
Note that it doesn't do any sorting - it relies on the inputs being sorted. It just picks the lower valued item from the current position in one of the two arrays for transfer to the output array. The result is O(N).
The code makes a single pass through the two arrays (in one loop). When I run this, the output is AA00, AA01, AB01, AB02, AC02, AA03, AA04. I'm sure there are opportunities to make this code cleaner, I just popped it off. However, it should give you ideas to continue:
public class TwoArrays
{
private string[] _array1 = {"AA01", "AB01", "AB02", "AC02"};
private string[] _array2 = {"AA00", "AA03", "AA04"};
public void TryItOut()
{
var result = ConcatenateSorted(_array1, _array2);
var concatenated = string.Join(", ", result);
}
private int Compare(string a, string b)
{
if (a == b)
{
return 0;
}
string a1 = a.Substring(0, 2);
string a2 = a.Substring(2, 2);
string b1 = b.Substring(0, 2);
string b2 = b.Substring(2, 2);
return string.Compare(a2 + a1, b2 + b1);
}
private string[] ConcatenateSorted(string[] a, string[] b)
{
string[] ret = new string[a.Length + b.Length];
int aIndex = 0, bIndex = 0, retIndex = 0;
while (true) //do this forever, until time to "break" and return
{
if (aIndex >= a.Length && bIndex >= b.Length)
{
return ret;
}
if (aIndex >= a.Length)
{
ret[retIndex++] = b[bIndex++];
continue;
}
if (bIndex >= b.Length)
{
ret[retIndex++] = a[aIndex++];
continue;
}
if (Compare(a[aIndex], b[bIndex]) > 0)
{
ret[retIndex++] = b[bIndex++];
}
else
{
ret[retIndex++] = a[aIndex++];
}
}
}
}
To get it to work with your data, you need to change the input arrays and provide your own comparison function (which you could pass in as a delegate).

Searching an array with foreach loop and getting the index of element

I have a class like so:
public class MyClass
{
public char letter { get; set; }
public double result { get; set; }
public bool test { get; set; }
}
I declare an array:
MyClass[] myArray = new MyClass[counter];
and fill it with some data.
I sort the array:
myArray = myArray.OrderBy(a => a.letter).ThenByDescending(a => a.result).ToArray();
Now let's say I have an int i = 100 variable.
How would I iterate through this array fields and get the index of the first element that:
Has specified letter in letter field.
Has test == false
result < i
I'm thinking of something like this:
foreach(MyClass t in myArray.Where(a => a.letter == 'a')
{
if(t.result < i && t.test == false) get index of that field
}
However, I'm unsure how to get the index of it. How do I do this?
Array.FindIndex should solve the problem for you:
int correctIndex = Array.FindIndex( myArray , item => item.letter == 'a' && item.result < i && !item.test );
The second parameter is functionally equivalent to how you would describe it in a .Where() clause.
Also, just like similar indexing functions, it returns -1 if the element isn't found.
You can do it using the overload of Select that provides an index, like this:
var res = myArray
.Select((val, ind) => new {val, ind}))
.Where(p => p.val.result < i && p.val.letter == 'a' && !p.val.test)
.Select(p => p.ind);
The first Select pairs up MyClass objects, as val, with their index, as ind. Then the Where method expresses the three conditions, including the one that pairs result and ind. Finally, the last Select drops the MyClass object, because it is no longer needed.
I see the guys already did a great job answering your question with better alternatives, but just in case you still want to know how to do it with for each, here is how
int counter = 5 ; // size of your array
int i = 100 ; // the limit to filter result by
int searchResult = -1; // The index of your result [If exists]
int index = 0; // index in the array
MyClass[] myArray = new MyClass[counter]; // Define you array and fill it
myArray[0] = new MyClass {letter = 'f' ,result = 12.3 , test = false } ;
myArray[1] = new MyClass {letter = 'a' ,result = 102.3 , test = true} ;
myArray[2] = new MyClass {letter = 'a' ,result = 12.3 , test = false } ;
myArray[3] = new MyClass {letter = 'b' ,result = 88 , test = true } ;
myArray[4] = new MyClass { letter = 'q', result = 234, test = false };
myArray = myArray.OrderBy(a => a.letter).ThenByDescending(a => a.result).ToArray(); // Sort the array
foreach(MyClass t in myArray.Where(a => a.letter == 'a')) // The foreach part
{
if (t.result < i && t.test == false)
{
searchResult = index;
break;
}
index++;
}
// And finally write the resulting index [If the element was found]
Please note : Of course the resulting index will be the index in the sorted array
Without foreach:
var item = myArray.FirstOrDefault(e => e.letter == 'a' && e.result < i && e.test == false);
int index = Array.IndexOf(myArray, item);

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

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

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