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
Related
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);
}
}
}
}
I am trying to find the differences in two lists. List, "y" should have 1 unique value when compared to list "x". However, Except, does not return the difference. The, "differences" list's count always equals 0.
List<EtaNotificationUser> etaNotifications = GetAllNotificationsByCompanyIDAndUserID(PrevSelectedCompany.cmp_ID);
IEnumerable<string> x = etaNotifications.OfType<string>();
IEnumerable<string> y = EmailList.OfType<string>();
IEnumerable<string> differences = x.Except(y, new StringLengthEqualityComparer()).ToList();
foreach(string diff in differences)
{
addDiffs.Add(diff);
}
After reading a few posts and articles on the post, I created a custom comparer. The comparer looks at string length (kept it simple for testing) and obtains the Hashcode, since these are two objects of a different type (even though I convert their types to string), I thought it may have been the issue.
class StringLengthEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Length == y.Length;
}
public int GetHashCode(string obj)
{
return obj.Length;
}
}
This is my first time using Except. Sounds like a great, optimized way of comparing two lists, but I can't get it to work.
Update
X - Should hold Email Addresses from the database.
GetAllNotificationsByCompanyIDAndUserID - brings back email values from the DB.
Y - Should hold all Email Addresses in the UI Grid.
What I am trying to do is detect if a new e-mail has been added to the grid. So at this point X will have the saved values from past entries. Y will have any new e-mail addresses add by the user and have not been saved yet.
I have verified this is all working correctly.
The problem is here:
IEnumerable<string> x = etaNotifications.OfType<string>();
but etaNotifications is a List<EtaNotificationUser>, none of which can be a string since string is sealed. OfType returns all instances that are of the given type - it does not "convert" each member to that type.
So x will always be empty.
Maybe you want:
IEnumerable<string> x = etaNotifications.Select(e => e.ToString());
if EtaNotificationUser has overridden ToString to give you the value you want to compare. If the value you want to compare is in a property you can use:
IEnumerable<string> x = etaNotifications.Select(e => e.EmailAddress);
or some other property.
You'll likely have to do something similar for y (unless EmailList is already a List<string> which I doubt).
Assuming you have verified that your two enumerables x and y actually contain the strings you expect them to, I believe your problem is with your string comparer. According to the docs, Enumerable.Except "Produces the set difference of two sequences. The set difference is the members of the first sequence that don't appear in the second sequence." But your equality comparer equates all strings with the same length. Thus, if a string in the first sequence happens to have the same length as a string in the second, it will not be found as different using your comparer.
Update: yup, I just tested it:
public class StringLengthEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Length == y.Length;
}
public int GetHashCode(string obj)
{
return obj.Length;
}
}
string [] array1 = new string [] { "foo", "bar", "yup" };
string[] array2 = new string[] { "dll" };
int diffCount;
diffCount = 0;
foreach (var diff in array1.Except(array2, new StringLengthEqualityComparer()))
{
diffCount++;
}
Debug.Assert(diffCount == 0); // No assert.
diffCount = 0;
foreach (var diff in array1.Except(array2))
{
diffCount++;
}
Debug.Assert(diffCount == 0); // Assert b/c diffCount == 3.
There is no assert with the custom comparer but there is with the standard.
I want to implement a custom string IComparer in C# and apply it to a ComboBox.
Actual Results
If I set the ComboBox's Sorted property to true, the output is :
A
AA
AAA
B
BB
BBB
Wanted Results
The wanted behavior of the sorting algorithm is the following (financial developers will understand why :) ) :
AAA
AA
A
BBB
BB
B
Question
Is it possible to do it ? Are sorting algorithms needed here ?
PS : I don't need a complete answer with code, i just need an idea of how it might be done ..
EDIT
This is about credit ratings. I've omitted something in my question. The ratings have to be sorted in this order :
XXX
XX+
XX
XX-
X+
X
X-
with X in ('A','B','C') and 'A' > 'B' > 'C'
Here's a mostly implemented version:
public class MyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
//todo null checks on input
var pairs = x.Zip(y, (a, b) => new { x = a, y = b });
foreach (var pair in pairs)
{
int value = pair.x.CompareTo(pair.y);
if (value != 0)
return value;
}
//if we got here then either they are the same,
//or one starts with the other
return y.Length.CompareTo(x.Length); //note x and y are reversed here
}
}
So this uses Zip to get the pairs of chars from each corresponding string until one ends, returning the appropriate value if they aren't equal. If it makes it past that then one string start with the other. For a traditional string comparison we'd just compare the lengths in the same order as the input parameters. Since we're essentially reversing the order based on length, note that the x and y are swapped on the last line. That reverses the comparison logic.
Assuming this is for credit ratings, normally this is done by having a "sort order" column on the CreditRating class that you could use to sort the list before assigning it as the data source of the drop-down.
But, a quick workaround (based on the limited possible values) would be to sort by the first letter ascending, then by the length of the string descending:
if(left[0] != right[0])
return left[0].CompareTo(right[0]);
else
return right.Length - left.Length;
Another workaround if you want more control over the order is to create a list of possible values in the "right" order and then use that to sort the list:
public class MyComparer : IComparer<string>
{
private static readonly string[] Ratings = new [] {
"CC","C","CCC-","CCC","CCC+",
"B-","B","B+","BB-","BB","BB+","BBB-","BBB","BBB+",
"A-","A","A+","AA-","AA","AA+","AAA"};
// reverse the order so that any strings not found will be put at the end.
public int Compare(string left, string right)
{
return Array.IndexOf(Ratings, right).CompareTo(Array.IndexOf(Ratings, left));
}
}
Write the IComparer so that it takes strings but compares per character,
if A[0] == B[0] go to the next character.
if B[1] == null or A[1] < B[1], return A < B.
if A[1] == null or B[1] < A[1], return B < A.
if equal...continue as needed
If I have two values eg/ABC001 and ABC100 or A0B0C1 and A1B0C0, is there a RegEx I can use to make sure the two values have the same pattern?
Well, here's my shot at it. This doesn't use regular expressions, and assumes s1 and s2 only contain numbers or digits:
public static bool SamePattern(string s1, string s2)
{
if (s1.Length == s2.Length)
{
char[] chars1 = s1.ToCharArray();
char[] chars2 = s2.ToCharArray();
for (int i = 0; i < chars1.Length; i++)
{
if (!Char.IsDigit(chars1[i]) && chars1[i] != chars2[i])
{
return false;
}
else if (Char.IsDigit(chars1[i]) != Char.IsDigit(chars2[i]))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
A description of the algorithm is as follows:
If the strings have different lengths, return false.
Otherwise, check the characters in the same position in both strings:
If they are both digits or both numbers, move on to the next iteration.
If they aren't digits but aren't the same, return false.
If one is a digit and one is a number, return false.
If all characters in both strings were checked successfully, return true.
If you don't know the pattern in advance, but are only going to encounter two groups of characters (alpha and digits), then you could do the following:
Write some C# that parsed the first pattern, looking at each char and determine if it's alpha, or digit, then generate a regex accordingly from that pattern.
You may find that there's no point writing code to generate a regex, as it could be just as simple to check the second string against the first.
Alternatively, without regex:
First check the strings are the same length.
Then loop through both strings at the same time, char by char. If char[x] from string 1 is alpha, and char[x] from string two is the same, you're patterns are matching.
Try this, it should cope if a string sneaks in some symbols. Edited to compare character values ... and use Char.IsLetter and Char.IsDigit
private bool matchPattern(string string1, string string2)
{
bool result = (string1.Length == string2.Length);
char[] chars1 = string1.ToCharArray();
char[] chars2 = string2.ToCharArray();
for (int i = 0; i < string1.Length; i++)
{
if (Char.IsLetter(chars1[i]) != Char.IsLetter(chars2[i]))
{
result = false;
}
if (Char.IsLetter(chars1[i]) && (chars1[i] != chars2[i]))
{
//Characters must be identical
result = false;
}
if (Char.IsDigit(chars1[i]) != Char.IsDigit(chars2[i]))
result = false;
}
return result;
}
Consider using Char.GetUnicodeCategory
You can write a helper class for this task:
public class Mask
{
public Mask(string originalString)
{
OriginalString = originalString;
CharCategories = originalString.Select(Char.GetUnicodeCategory).ToList();
}
public string OriginalString { get; private set; }
public IEnumerable<UnicodeCategory> CharCategories { get; private set; }
public bool HasSameCharCategories(Mask other)
{
//null checks
return CharCategories.SequenceEqual(other.CharCategories);
}
}
Use as
Mask mask1 = new Mask("ab12c3");
Mask mask2 = new Mask("ds124d");
MessageBox.Show(mask1.HasSameCharCategories(mask2).ToString());
I don't know C# syntax but here is a pseudo code:
split the strings on ''
sort the 2 arrays
join each arrays with ''
compare the 2 strings
A general-purpose solution with LINQ can be achieved quite easily. The idea is:
Sort the two strings (reordering the characters).
Compare each sorted string as a character sequence using SequenceEquals.
This scheme enables a short, graceful and configurable solution, for example:
// We will be using this in SequenceEquals
class MyComparer : IEqualityComparer<char>
{
public bool Equals(char x, char y)
{
return x.Equals(y);
}
public int GetHashCode(char obj)
{
return obj.GetHashCode();
}
}
// and then:
var s1 = "ABC0102";
var s2 = "AC201B0";
Func<char, double> orderFunction = char.GetNumericValue;
var comparer = new MyComparer();
var result = s1.OrderBy(orderFunction).SequenceEqual(s2.OrderBy(orderFunction), comparer);
Console.WriteLine("result = " + result);
As you can see, it's all in 3 lines of code (not counting the comparer class). It's also very very easily configurable.
The code as it stands checks if s1 is a permutation of s2.
Do you want to check if s1 has the same number and kind of characters with s2, but not necessarily the same characters (e.g. "ABC" to be equal to "ABB")? No problem, change MyComparer.Equals to return char.GetUnicodeCategory(x).Equals(char.GetUnicodeCategory(y));.
By changing the values of orderFunction and comparer you can configure a multitude of other comparison options.
And finally, since I don't find it very elegant to define a MyComparer class just to enable this scenario, you can also use the technique described in this question:
Wrap a delegate in an IEqualityComparer
to define your comparer as an inline lambda. This would result in a configurable solution contained in 2-3 lines of code.
What's the cleanest/best way in C# to convert something like 400AMP or 6M to an integer? I won't always know what the suffix is, and I just want whatever it is to go away and leave me with the number.
You could use a regular expression:
Regex reg = new Regex("[0-9]*");
int result = Convert.ToInt32(reg.Match(input));
Okay, here's a long-winded solution which should be reasonably fast. It's similar to Guffa's middle answer, but I've put the conditions inside the body of the loop as I think that's simpler (and allows us to fetch the character just once). It's a matter of personal taste really.
It deliberately doesn't limit the number of digits that it matches, because if the string is an integer which overflows Int32, I think I'd rather see an exception than just a large integer :)
Note that this also handles negative numbers, which I don't think any of the other solutions so far do...
using System;
class Test
{
static void Main()
{
Console.WriteLine(ParseLeadingInt32("-1234AMP"));
Console.WriteLine(ParseLeadingInt32("+1234AMP"));
Console.WriteLine(ParseLeadingInt32("1234AMP"));
Console.WriteLine(ParseLeadingInt32("-1234"));
Console.WriteLine(ParseLeadingInt32("+1234"));
Console.WriteLine(ParseLeadingInt32("1234"));
}
static int ParseLeadingInt32(string text)
{
// Declared before loop because we need the
// final value
int i;
for (i=0; i < text.Length; i++)
{
char c = text[i];
if (i==0 && (c=='-' || c=='+'))
{
continue;
}
if (char.IsDigit(c))
{
continue;
}
break;
}
return int.Parse(text.Substring(0, i));
}
}
It's possibly not the cleanest method, but it's reasonably simple (a one liner) and I would imagine faster than a regex (uncompiled, for sure).
var str = "400AMP";
var num = Convert.ToInt32(str.Substring(0, str.ToCharArray().TakeWhile(
c => char.IsDigit(c)).Count()));
Or as an extension method:
public static int GetInteger(this string value)
{
return Convert.ToInt32(str.Substring(0, str.ToCharArray().TakeWhile(
c => char.IsDigit(c)).Count()));
}
Equivalently, you could construct the numeric string from the result of the TakeWhile function, as such:
public static int GetInteger(this string value)
{
return new string(str.ToCharArray().TakeWhile(
c => char.IsNumber(c)).ToArray());
}
Haven't benchmarked them, so I wouldn't know which is quicker (though I'd very much suspect the first). If you wanted to get better performance, you would just convert the LINQ (extension method calls on enumerables) to a for loop.
Hope that helps.
There are several options...
Like using a regular expression:
int result = int.Parse(Regex.Match(input, #"^\d+").Groups[0].Value);
Among the fastest; simply looping to find digits:
int i = 0;
while (i < input.Length && Char.IsDigit(input, i)) i++;
int result = int.Parse(input.Substring(0, i));
Use LastIndexOfAny to find the last digit:
int i = input.LastIndexOfAny("0123456789".ToCharArray()) + 1;
int result = int.Parse(input.Substring(0, i));
(Note: breaks with strings that has digits after the suffix, like "123asdf123".)
Probably fastest; parse it yourself:
int i = 0;
int result = 0;
while (i < input.Length) {
char c = input[i];
if (!Char.IsDigit(c)) break;
result *= 10;
result += c - '0';
i++;
}
If all you want to do is remove an unknown postfix from what would otherwise be an int, here is how I would do it:
I like a utility static method I call IsInt(string possibleInt) which will, as the name implies, return True if the string will parse into an int. You could write this same static method into your utility class (if it's not there already) and try:
`string foo = "12345SomePostFix";
while (!Tools.ToolBox.IsInt(foo))
{
foo = foo.Remove(foo.Length - 1);
}
int fooInt = int.Parse(foo);`