Order an Array like another Array in C# - c#

What is the best algorithm to take array like below:
A {0,1,2,3}
I expected to order it like array below:
B {3,1,0,2}
Any ideas?

So if you have two arrays and they hold the same data just in different order then just do this:
A = B
I suspect that is not your situation so I think we need more info.

What you need to do is determine the ordering of B and then apply that ordering to A. One way to accomplish this is to undo the ordering of B and keep track of what happens along the way. Then you can do the reverse to A.
Here's some sketchy C# (sorry, I haven't actually run this)...
Take a copy of B:
List<int> B2 = new List<int>(B);
Now sort it, using a sort function that records the swaps:
List<KeyValuePair<int,int>> swaps = new List<KeyValuePair<int,int>>();
B2.Sort( delegate( int x, int y ) {
if( x<y ) return -1;
if( x==y ) return 0;
// x and y must be transposed, so assume they will be:
swaps.Add( new KeyValuePair<int,int>(x,y) );
return 1;
});
Now apply the swaps, in reverse order, to A:
swaps.Reverse();
foreach( KeyValuePair<int,int> x in swaps )
{
int t = A[x.key];
A[x.key] = A[x.value];
A[x.value] = t;
}
Depending how the built-in sort algorithm works, you might need to roll your own. Something nondestructive like a merge sort should give you the correct results.

Here's my implementation of the comparer (uses LINQ, but can be easily adapted to older .net versions). You can use it for any sorting algorithms such as Array.Sort, Enumerable.OrderBy, List.Sort, etc.
var data = new[] { 1, 2, 3, 4, 5 };
var customOrder = new[] { 2, 1 };
Array.Sort(data, new CustomOrderComparer<int>(customOrder));
foreach (var v in data)
Console.Write("{0},", v);
The result is 2,1,3,4,5, - any items not listed in the customOrder are placed at the end in the default for the given type (unless a fallback comparator is given)
public class CustomOrderComparer<TValue> : IComparer<TValue>
{
private readonly IComparer<TValue> _fallbackComparer;
private const int UseDictionaryWhenBigger = 64; // todo - adjust
private readonly IList<TValue> _customOrder;
private readonly Dictionary<TValue, uint> _customOrderDict;
public CustomOrderComparer(IList<TValue> customOrder, IComparer<TValue> fallbackComparer = null)
{
if (customOrder == null) throw new ArgumentNullException("customOrder");
_fallbackComparer = fallbackComparer ?? Comparer<TValue>.Default;
if (UseDictionaryWhenBigger < customOrder.Count)
{
_customOrderDict = new Dictionary<TValue, uint>(customOrder.Count);
for (int i = 0; i < customOrder.Count; i++)
_customOrderDict.Add(customOrder[i], (uint) i);
}
else
_customOrder = customOrder;
}
#region IComparer<TValue> Members
public int Compare(TValue x, TValue y)
{
uint indX, indY;
if (_customOrderDict != null)
{
if (!_customOrderDict.TryGetValue(x, out indX)) indX = uint.MaxValue;
if (!_customOrderDict.TryGetValue(y, out indY)) indY = uint.MaxValue;
}
else
{
// (uint)-1 == uint.MaxValue
indX = (uint) _customOrder.IndexOf(x);
indY = (uint) _customOrder.IndexOf(y);
}
if (indX == uint.MaxValue && indY == uint.MaxValue)
return _fallbackComparer.Compare(x, y);
return indX.CompareTo(indY);
}
#endregion
}

In the example you gave (an array of numbers), there would be no point in re-ordering A, since you could just use B.
So, presumably these are arrays of objects which you want ordered by one of their properties.
Then, you will need a way to look up items in A based on the property in question (like a hashtable). Then you can iterate B (which is in the desired sequence), and operate on the corresponding element in A.

Both array's contain the same values (or nearly so) but I need to force them to be in the same order. For example, in array A the value "3045" is in index position 4 and in array B it is in index position 1. I want to reorder B so that the index positions of like values are the same as A.

If they are nearly the same then here is some pseudo code:
Make an ArrayList
Copy the contents of the smaller array to the arraylist
for each item I in the larger array
FInd I in the ArrayList
Append I to a new array
Remove I from the arraylist

Could the issue be resolved using a Dictionary so the elements have a relationship that isn't predicated on sort order at all?

Related

How do I get a value within a class stored within an array?

I'm making a C# script in Unity. My intention is to create a class Scenario, create classes representing different scenarios, which would then be stored in an array scenarioListAll.
A (simplified) version of the code is as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OverallManager2 : MonoBehaviour
{
public static object[] scenarioListAll = new object[40];
public class Scenario
{
    public string scenarioDesc;
    public bool surprise; // The 'surprise' bool I want to reference is defined here
public string surpriseType;
    public int[] leftOption;
    public int[] rightOption;
public int scenarioNumber;
public Scenario(string st, bool sp, int[] l, int[] r, int n)
{
scenarioDesc = st;
surprise = sp;
leftOption = l;
rightOption = r;
scenarioNumber = n;
}
// I haven't used this, but I'm not sure if this matters so I'm including this too
public Scenario(string st, bool sp, string spt, int[] l, int[] r, int n)
{
scenarioDesc = st;
surprise = sp;
surpriseType = spt;
leftOption = l;
rightOption = r;
scenarioNumber = n;
}
}
public static int[] getArray(int a, int b, int c, int d, int e, int f)
{
int[] arr = new int[6] {a, b, c, d, e, f};
return arr;
}
// Storing scenarios, am looking for the bool (2nd position)
public Scenario s1 = new Scenario("Test1", false, getArray(1, 1, 1, 1, 1, 1), getArray(1, 1, 1, 1, 1, 1), 1);
public Scenario s2 = new Scenario("Test2", true, getArray(1, 1, 1, 1, 1, 1), getArray(1, 1, 1, 1, 1, 1), 2);
public Scenario s3 = new Scenario("Test3", false, getArray(1, 1, 1, 1, 1, 1), getArray(1, 1, 1, 1, 1, 1), 3);
void Awake()
{
// Store scenarios in object array
scenarioListAll[0] = s1;
scenarioListAll[1] = s2;
scenarioListAll[2] = s3;
for(int i = 0; i < 40; i++)
{
object temp = scenarioListAll[i]; // Trying to extract the object stored in the array in a temp object
bool surpriseCheck = temp.surprise; // I am having problems with this line
if(surpriseCheck == true)
{
// Do something
}
}
}
// Ignoring start and update since they're irrelevant in this context
}
What I would like to do is to check whether the surprise element within a newly defined scenario (e.g. s1) is true. To do that, I was planning to extract the scenario stored in the array scenarioListAll, and then extract the surprise component from there. However, I'm couldn't figure out how to do this (e.g. in the code shown above, it returns Compiler Error CS1061).
I don't think I was able to find any documentation on this either, but I might not have understood something. I'm learning on my own so please bear with my poor knowledge/presentation.
Thank you for your time. Your help is much appreciated.
You are having a compilation issue due to the fact that the c# compiler doesn't know that temp is a Scenario since you declared it as "object". If you want to loop through the scenarios and check to see if they are a surprise you can use something like this:
foreach(Scenario temp in scenarioListAll)
{
bool surpriseCheck = temp.surprise;
if(surpriseCheck == true)
{
// Do something
}
}
Another way of accomplishing the same task with more control over the iteration would be:
for(int i = 0; i < scenarioListAll.Length; i++)
{
Scenario temp = scenarioListAll[i];
bool surpriseCheck = temp.surprise;
if(surpriseCheck == true)
{
// Do something
}
}
The benefit of the first version is that you don't have to worry about overrunning the bounds of the array. As Mike added below you could also use var to have the compiler fill in the type for you.
It's sometimes easiest to allow the compiler to determine the type of a variable for us.
In your case, you've specified that the variable temp will be of type object. Now, that's fine, but while a Scenario derives from object, and object is the lowest level class in the .Net environment, and is not a Scenario.
The var keyword doesn't mean that the declared type is of a "variable" type, instead it's telling the compiler just to "fill in" the correct type, based on the action you're taking. So, to put this in to action in your case, you could do this instead:
for( var i = 0; i < 40; i++ ) // notice the use of var here as well
{
var scenario = scenarioListAll[i]; // renamed temp to scenario
// var surpriseCheck = scenario .surprise; // var used but line not required
if( scenario.surprise )
{
// Do something
}
}
I went overboard there to demonstrate that the compiler is quite happy with the var keyword just about wherever you'd specify a data type as a type for a variable. Obviously not when you're trying to cast types, and there ARE sometimes where you'd want to specify the exact type you're trying to instantiate.
In your case, your next issue will be that you've defined the array as having 40 object elements, but you've then only instantiated 3 Scenario elements (which is valid, but probably not quite what you want overall). So your code, as it stands, is going to NullReference error out. You'll be able to avoid that, with a small modification, so your amended code could look like this, to include some type checks:
for( var i = 0; i < scenarioListAll.Length; i++ )
{
// First, check to see if the object in the array cell is a Scenario.
// If the item in the cell is a Scenario, check to see if surprise is set.
if ( scenarioListAll[i] is Scenario scenario && scenario.surprise )
{
// Do something
}
}
for more information on the is keyword, check the Microsoft Docs here.

How to get enum by index with different set values? [duplicate]

I need to get the numeric position of an enum in its definition.
Consider the following enum - it is used for bit fields but the status names
would be useful if they had the values on the right that I have commented.
[Flags]
public enum StatusFlags
{
None = 0, // 0 -- these commented indexes are the numbers I also would like
Untested = 1, // 1 to associate with the enum names.
Passed_Programming = 2, // 2
Failed_Programming = 4, // 3
// ... many more
}
I have created a static method as follows, which works for what I want.
public static int GetStatusID(this StatusFlags flag)
{
int i = 0;
foreach (StatusFlags val in Enum.GetValues(typeof(StatusFlags)))
{
if (flag == val) break;
i++;
}
return i;
}
It is used like this:
StatusFlags f = StatusFlags.Failed_Programming;
// I want the position i.e value of 3 not the value the enum is associated with i.e 4
int Index = f.GetStatusID();
Is there is a better way to do this?
How about using attributes on your enum? Something like this:
[Flags]
public enum StatusFlags
{
[Index=0]
None = 0,
[Index=1]
Untested = 1,
[Index=2]
Passed_Programming = 2,
[Index=3]
Failed_Programming = 4,
// ... many more
}
Then you can the index value of your enum like this:
var type = typeof(StatusFlags);
var statusFlag = type.GetMember(StatusFlags.Untested.ToString());
var attributes = statusFlag [0].GetCustomAttributes(typeof(IndexAttribute),false);
var index = int.Parse(((IndexAttribute)attributes[0]).Index); //if you need an int value
A deleted answer here suggested something that resembled
public static int GetStatusID(this StatusFlags flag)
{
return Array.IndexOf(Enum.GetValues(typeof(StatusFlags)), flag);
}
and was just missing the syntactical point that IndexOf is a static function in the Array class, not an extension method. I like it though for brevity.
You could do this:
public static int GetStatusID(this StatusFlags flag)
{
return
Enum
.GetValues(typeof(StatusFlags))
.Cast<StatusFlags>()
.Select((f, n) => new { f, n })
.Where(fn => fn.f == flag)
.Select(fn => fn.n)
.DefaultIfEmpty(0)
.First();
}
How about just using math? He says the flags go up in powers of 2
int GetStatusID(this StatusFlags flag)
{
if (((int)flag) == 0) return 0;
return (Math.Log((double)flag) / Math.Log(2D)) + 1;
}
If each flag has only 1 bit set like that then the index is just Math.Log2((int)flag) + 1. However Math.Log2 is a floating-point operation and is very slow so don't use it
If you're using .NET Core then there are BitOperations.Log2 and BitOperations.TrailingZeroCount which map directly to hardware instructions like TZCNT/BSF in x86 or CLZ in ARM, hence are much more efficient and the result is like this
public static int GetStatusID(this StatusFlags flag)
{
if ((int)flag == 0)
return 0;
return BitOperations.Log2((int)flag);
// or return BitOperations.TrailingZeroCount((int)flag) + 1;
}
If you're using an older .NET framework then calculate see the way to calculate integer log2 quickly in these questions
What's the quickest way to compute log2 of an integer in C#?
Fastest implementation of log2(int) and log2(float)
Fast way of finding most and least significant bit set in a 64-bit integer

c# initializing a list with another list/new list

I have a List of Lists.
To do some Opertations with each of those lists, i separate the Lists by a property and set a temp List with its value;
The list can be sometimes empty.
That is why i use this function for assignment.
EDIT:
My current solution is this simple method.
It should be easily adaptable.
private List<string> setList(List<string> a, int count)
{
List < string > retr;
if(a.Capacity == 0)
{
retr = new List<string>();
for(int counter = 0; counter < count; counter++)
{
retr.Add(string.empty);
}
}
else
{
retr = a;
}
return retr;
}
Is there a better way to either take a list as values or initialize a list with element count?
Or should I implement my own "List" class that has this behavior?
You could use Enumerable.Repeat<T> if you wanted to avoid the loop:
var list = Enumerable.Repeat<string>("", count).ToList();
But there are several things that are problematic with your code:
If Capacity is not 0, it doesn't mean it's equal to your desired count. Even if it is equal to the specified count, it doesn't mean that the actual List.Count is equal to count. A safer way would be to do:
static List<string> PreallocateList(List<string> a, int count)
{
// reuse the existing list?
if (a.Count >= count)
return a;
return Enumerable.Repeat("", count).ToList();
}
Preallocating a List<T> is unusual. It's usually common to use arrays when you have a fixed length known in advance.
// this would (perhaps) make more sense
var array = new string[count];
And keep in mind, as mentioned in 1., that list's Capacity is not the same as Count:
var list = new List<string>(10);
// this will print 10
Console.WriteLine("Capacity is {0}", list.Capacity);
// but this will throw an exception
list[0] = "";
Most likely, however, this method is unnecessary and there is a better way to accomplish what you're doing. If nothing else, I would play the safe card and simply instantiate a new list each time (presuming that you have an algorithm which depends on a preallocated list):
static List<string> PreallocateList(int count)
{
return Enumerable.Repeat("", count).ToList();
}
Or, if you are only interested in having the right capacity (not count), then just use the appropriate constructor:
static List<string> PreallocateList(int count)
{
// this will prevent internal array resizing, if that's your concern
return new List<string>(count);
}
Your method is meaningless but equivalent to
static List<string> setList(List<string> a, int count) =>
a.Capacity == 0 ? Enumerable.Repeat("", count).ToList() : a;
if you want Linq.

c# - Sorting Multiple String arrays with Quicksort

Sorry for the vague title but I'll try and describe what my problem as best as I can below.
Basically I have 5 string arrays that all hold data relevant to the same index in the other arrays. For example, element 5 in array 1 corresponds to element 5 in arrays 2, 3, 4 and 5.
What I have done is used the Quicksort algorthim to sort array 1 into alphabetical order. The problem is that when the array is sorted, no longer do elements in the other arrays correspond since the other arrays haven't been sorted.
What I need is some way to swap the same elements around in the other 4 arrays as has been down to array 1. For example, if element 2 in array 1 is swapped to element 55, then element 2 in the other 4 arrays need to be swapped to element 55 in their array and vice versa.
The end goal is to display all the data in a specific element across all 5 arrays.
Below I have added the quicksort algorithm I'm using and added 3 example arrays that need sorting:
string[] array1 = {"z","y","x","a"};
string[] array2 = {"26","25","24","1"};
string[] array3 = { "black","yellow","white","red" };
// The first 2 arrays should clarify my point further.
// I use Quicksort to sort array 1
public static void QuicksortSTRING(IComparable[] elements, int left, int right)
{        
int i = left, j = right;
IComparable pivot = elements[(left + right) / 2];
while (i <= j)
 {
while (elements[i].CompareTo(pivot) < 0)
{
i++;
}
while (elements[j].CompareTo(pivot) > 0)
{
j--;
}
if (i <= j)
{
// Swap
IComparable tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
i++;
j--;
}
}
// Recursive calls
if (left < j)
{
QuicksortSTRING(elements, left, j);
}
if (i < right)
{
QuicksortSTRING(elements, i, right);
}
}
If you need any other info just ask.
It’s better to put the three related strings into a single object:
sealed class RelatedInformation // or struct, you decide
{
public string First;
public string Second;
public string Third;
}
and then sort a list of those objects:
var myList = new List<RelatedInformation>();
// insert code that populates the list here
myList.Sort((a, b) => a.First.CompareTo(b.First));
or, if it needs to be an array:
var myArray = /* obtain the RelatedInformation[] here */;
Array.Sort(myList, (a, b) => a.First.CompareTo(b.First));
Additionally, there is no need for you to implement Quicksort yourself (unless this is homework? :)). You can just use Array.Sort or List<T>.Sort with a lambda expression that specifies your sort criterion.
You don’t even need to implement the IComparable<T> interface if you use the above code. However, if the RelatedInformation class (or struct) is used in many places that have something to do with their ordering, it may be wise to implement it anyway; then you can ditch the lambdas:
sealed class RelatedInformation : IComparable<RelatedInformation>
{
public string First;
public string Second;
public string Third;
public int CompareTo(RelatedInformation other)
{
return First.CompareTo(other.First);
}
}
// ...
var myList = new List<RelatedInformation>();
// insert code that populates the list
myList.Sort();
However, since you explicitly asked about the three-array situation, here is a solution that will work under that constraint. Instead of sorting any one of the arrays, the idea is to sort a list of the indexes. I’m going to use LINQ for this because it’s pretty succint and readable:
var sortedIndexes = Enumerable.Range(0, array1.Length)
.OrderBy(i => array1[i])
.ToArray();
var sortedArray1 = sortedIndexes.Select(i => array1[i]).ToArray();
var sortedArray2 = sortedIndexes.Select(i => array2[i]).ToArray();
var sortedArray3 = sortedIndexes.Select(i => array3[i]).ToArray();
Pretty short, huh? Of course, in the call to OrderBy, you can specify any other array to sort by.
Do be aware though that this code will throw an exception if any of the arrays is shorter than the first one, and it will silently discard items if any of the arrays is longer than the first one. One major benefit of the list-of-objects solution is that you do not need to worry about that.
As an added piece of information, the OrderBy from LINQ is a stable sort; this means that items where array1 has the same string stay in the same order. Array.Sort and List<T>.Sort do not have a stable sort.
You can even use this method to sort by multiple criteria; for example, let’s say you want to sort by the strings in array1, but whenever array1 has the same string for some items, you want those items to be sorted by whatever is in array2. You can do that using ThenBy:
var sortedIndexes = Enumerable.Range(0, array1.Length)
.OrderBy(i => array1[i])
.ThenBy(i => array2[i])
.ToArray();
You've got these three items of information to sort. Try creating a class to hold them. It can be an inner class inside one of your program classes if you want.
struct MyThing :IComparable {
char a;
int b;
string c;
}
Then make a List<MyThing>. Then populate it with your data.
You'll need to implement the IComparable interface (requiring your own CompareTo method) for your class, so it knows to sort on a, or whatever you want sorted.
Then use the built in List.Sort() function or your own quicksort method.
I think it would make more sense if you stored all your related information together in one array, e.g.:
var array = new[] { Tuple.Create("z", "26", "black"),
Tuple.Create("y", "25", "yellow"),
Tuple.Create("x", "24", "white"),
Tuple.Create("a", "1", "red") };
Then you can sort your array by any key you like and preserving other elements at corresponding positions.
You could approach this by putting all your related strings into a single class, rather than keeping them all in separate arrays.
For example:
public class Demo
{
public string Key;
public string S1;
public string S2;
public override string ToString()
{
return string.Format("Key: {0}, S1: {1}, S2: {2}", Key, S1, S2);
}
}
Then when you want to sort that, you need a way to determine which property or properties to use when comparing elements. There are several ways to do this; one is to make your type implement IComparable<T>.
However there is another more flexible approach. You can supply to your sort method an IComparer<T> object that it can use to compare elements.
Using this, you can "pick out" the member of a class that you want to use when comparing.
Here's a full example:
using System;
using System.Collections.Generic;
namespace Demo
{
public class Demo
{
public string Key;
public string S1;
public string S2;
public override string ToString()
{
return string.Format("Key: {0}, S1: {1}, S2: {2}", Key, S1, S2);
}
}
static class Program
{
static void Main()
{
var list = new List<Demo>
{
new Demo {Key = "Z", S1 = "Z1", S2 = "Z2"},
new Demo {Key = "Y", S1 = "Y1", S2 = "Y2"},
new Demo {Key = "X", S1 = "X1", S2 = "X2"},
new Demo {Key = "W", S1 = "W1", S2 = "W2"},
new Demo {Key = "V", S1 = "V1", S2 = "V2"}
};
// Rather than write your own IComparer<Demo> implementation, you can
// leverage a built-in .Net implementation by using
// Comparer<Demo>.Create() as follows:
var keyComparer = Comparer<Demo>.Create((x, y) => string.Compare(x.Key, y.Key, StringComparison.Ordinal));
QuicksortSTRING(list, 0, list.Count-1, keyComparer);
Console.WriteLine(string.Join("\n", list));
}
public static void QuicksortSTRING<T>(IList<T> elements, int left, int right, IComparer<T> comparer)
{
int i = left, j = right;
var pivot = elements[(left + right)/2];
while (i <= j)
{
while (comparer.Compare(elements[i], pivot) < 0)
{
i++;
}
while (comparer.Compare(elements[j], pivot) > 0)
{
j--;
}
if (i <= j)
{
// Swap
T tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
i++;
j--;
}
}
// Recursive calls
if (left < j)
{
QuicksortSTRING(elements, left, j, comparer);
}
if (i < right)
{
QuicksortSTRING(elements, i, right, comparer);
}
}
}
}

How can I use C# to sort values numerically?

I have a string that contains numbers separated by periods. When I sort it appears like this since it is a string: (ascii char order)
3.9.5.2.1.1
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4
etc.
I want it to sort like this: (in numeric order)
3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
...
3.9.5.2.1.9
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
I know that I can:
Use the Split function to get the individual numbers
Put the values into an object
Sort the object
I prefer to avoid all of that work if it is duplicating existing functionality. Is a method in the .net framework that does this already?
Here's my working solution that also takes care of strings that are not in the right format (e.g. contain text).
The idea is to get the first number within both strings and compare these numbers. If they match, continue with the next number. If they don't, we have a winner. If one if these numbers isn't a number at all, do a string comparison of the part, which wasn't already compared.
It would be easy to make the comparer fully compatible to natural sort order by changing the way to determine the next number.
Look at that.. just found this question.
The Comparer:
class StringNumberComparer : IComparer<string>
{
public int Compare(string x, string y)
{
int compareResult;
int xIndex = 0, yIndex = 0;
int xIndexLast = 0, yIndexLast = 0;
int xNumber, yNumber;
int xLength = x.Length;
int yLength = y.Length;
do
{
bool xHasNextNumber = TryGetNextNumber(x, ref xIndex, out xNumber);
bool yHasNextNumber = TryGetNextNumber(y, ref yIndex, out yNumber);
if (!(xHasNextNumber && yHasNextNumber))
{
// At least one the strings has either no more number or contains non-numeric chars
// In this case do a string comparison of that last part
return x.Substring(xIndexLast).CompareTo(y.Substring(yIndexLast));
}
xIndexLast = xIndex;
yIndexLast = yIndex;
compareResult = xNumber.CompareTo(yNumber);
}
while (compareResult == 0
&& xIndex < xLength
&& yIndex < yLength);
return compareResult;
}
private bool TryGetNextNumber(string text, ref int startIndex, out int number)
{
number = 0;
int pos = text.IndexOf('.', startIndex);
if (pos < 0) pos = text.Length;
if (!int.TryParse(text.Substring(startIndex, pos - startIndex), out number))
return false;
startIndex = pos + 1;
return true;
}
}
Usage:
public static void Main()
{
var comparer = new StringNumberComparer();
List<string> testStrings = new List<string>{
"3.9.5.2.1.1",
"3.9.5.2.1.10",
"3.9.5.2.1.11",
"3.9.test2",
"3.9.test",
"3.9.5.2.1.12",
"3.9.5.2.1.2",
"blabla",
"....",
"3.9.5.2.1.3",
"3.9.5.2.1.4"};
testStrings.Sort(comparer);
DumpArray(testStrings);
Console.Read();
}
private static void DumpArray(List<string> values)
{
foreach (string value in values)
{
Console.WriteLine(value);
}
}
Output:
....
3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.test
3.9.test2
blabla
No, I don't believe there's anything in the framework which does this automatically. You could write your own IComparer<string> implementation which doesn't do any splitting, but instead iterates over both strings, only comparing as much as is required (i.e. parsing just the first number of each, then continuing if necessary etc) but it would be quite fiddly I suspect. It would also need to make assumptions about how "1.2.3.4.5" compared with "1.3" for example (i.e. where the values contain different numbers of numbers).
Since the comparison you want to do on the strings is different from how strings are normally compared in .Net, you will have to use a custom string string comparer
class MyStringComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// your comparison logic
// split the string using '.' separator
// parse each string item in split array into an int
// compare parsed integers from left to right
}
}
Then you can use the comparer in methods like OrderBy and Sort
var sorted = lst.OrderBy(s => s, new MyStringComparer());
lst.Sort(new MyStringComparer());
This will give you the desired result. If not then just tweak the comparer.
What you are looking for is the natural sort order and Jeff Atwood bloged about it and has links to implementations in different languages. The .NET Framework does not contain an implementation.
Is it possible for you to pad your fields to the same length on the front with 0? If so, then you can just use straight lexicographic sorting on the strings. Otherwise, there is no such method built in to the framework that does this automatically. You'll have to implement your own IComparer<string> if padding is not an option.
Not really, though you may be able to use Regexes or Linq to avoid too much wheel-reinventing. Keep in mind it will cost you much the same computationally to use something built-in as to roll your own.
Try this:
List<string> myList = GetNumberStrings();
myList.Select(s=>s.Split('.')).ToArray().
.Sort((a,b)=>RecursiveCompare(a,b))
.Select(a=>a.Aggregate(new StringBuilder(),
(s,sb)=>sb.Append(s).Append(".")).Remove(sb.Length-1, 1).ToString())
.ToList();
...
public int RecursiveCompare(string[] a, string[] b)
{
return RecursiveCompare(a,b,0)
}
public int RecursiveCompare(string[] a, string[] b, int index)
{
return index == a.Length || index == b.Length
? 0
: a[index] < b[index]
? -1
: a[index] > b[index]
? 1
: RecursiveCompare(a,b, index++);
}
Not the most compact, but it should work and you could use a y-combinator to make the comparison a lambda.
Split each string by '.', iterate through the components and compare them numerically.
This code also assumes that the number of components is signficant (a string '1.1.1' will be greater than '2.1'. This can be adjusted by altering the first if statement in the Compare method below.
int Compare(string a, string b)
{
string[] aParts = a.Split('.');
string[] bParts = b.Split('.');
/// if A has more components than B, it must be larger.
if (aParts.Length != bParts.Length)
return (aParts.Length > bParts.Length) ? 1 : -1;
int result = 0;
/// iterate through each numerical component
for (int i = 0; i < aParts.Length; i++)
if ( (result = int.Parse(aParts[i]).CompareTo(int.Parse(bParts[i]))) !=0 )
return result;
/// all components are equal.
return 0;
}
public string[] sort()
{
/// initialize test data
string l = "3.9.5.2.1.1\n"
+ "3.9.5.2.1.10\n"
+ "3.9.5.2.1.11\n"
+ "3.9.5.2.1.12\n"
+ "3.9.5.2.1.2\n"
+ "3.9.5.2.1.3\n"
+ "3.9.5.2.1.4\n";
/// split the large string into lines
string[] arr = l.Split(new char[] { '\n' },StringSplitOptions.RemoveEmptyEntries);
/// create a list from the array
List<string> strings = new List<string>(arr);
/// sort using our custom sort routine
strings.Sort(Compare);
/// concatenate the list back to an array.
return strings.ToArray();
}
You can use the awesome AlphanumComparator Alphanum natural sort algorithm by David Koelle.
Code:
OrderBy(o => o.MyString, new AlphanumComparator())
If you're gonna use the C# version change it to:
AlphanumComparator : IComparer<string>
and
public int Compare(string x, string y)
In addition to implementing your own IComparer as Jon mentions, if you call ToList() on your array, you can call the .Sort() method and pass in a function parameter that compares two values, as shown here: http://msdn.microsoft.com/en-us/library/w56d4y5z.aspx

Categories