Related
I have a string[] in which every elements ends with some numeric value.
string[] partNumbers = new string[]
{
"ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11"
};
I am trying to sort the above array as follows using LINQ but I am not getting the expected result.
var result = partNumbers.OrderBy(x => x);
Actual Result:
AB1
Ab11
AB2
ABC1
ABC10
ABC10
ABC11
ABC2
Expected Result
AB1
AB2
AB11
..
That is because the default ordering for string is standard alpha numeric dictionary (lexicographic) ordering, and ABC11 will come before ABC2 because ordering always proceeds from left to right.
To get what you want, you need to pad the numeric portion in your order by clause, something like:
var result = partNumbers.OrderBy(x => PadNumbers(x));
where PadNumbers could be defined as:
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}
This pads zeros for any number (or numbers) that appear in the input string so that OrderBy sees:
ABC0000000010
ABC0000000001
...
AB0000000011
The padding only happens on the key used for comparison. The original strings (without padding) are preserved in the result.
Note that this approach assumes a maximum number of digits for numbers in the input.
If you want to sort a list of objects by a specific property using LINQ and a custom comparer like the one by Dave Koelle you would do something like this:
...
items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList();
...
You also have to alter Dave's class to inherit from System.Collections.Generic.IComparer<object> instead of the basic IComparer so the class signature becomes:
...
public class AlphanumComparator : System.Collections.Generic.IComparer<object>
{
...
Personally, I prefer the implementation by James McCormack because it implements IDisposable, though my benchmarking shows that it is slightly slower.
You can use PInvoke to get fast and good result:
class AlphanumericComparer : IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern int StrCmpLogicalW(string s1, string s2);
public int Compare(string x, string y) => StrCmpLogicalW(x, y);
}
You can use it like AlphanumComparatorFast from the answer above.
You can PInvoke to StrCmpLogicalW (the windows function) to do this. See here: Natural Sort Order in C#
public class AlphanumComparatorFast : IComparer
{
List<string> GetList(string s1)
{
List<string> SB1 = new List<string>();
string st1, st2, st3;
st1 = "";
bool flag = char.IsDigit(s1[0]);
foreach (char c in s1)
{
if (flag != char.IsDigit(c) || c=='\'')
{
if(st1!="")
SB1.Add(st1);
st1 = "";
flag = char.IsDigit(c);
}
if (char.IsDigit(c))
{
st1 += c;
}
if (char.IsLetter(c))
{
st1 += c;
}
}
SB1.Add(st1);
return SB1;
}
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;
}
if (s1 == s2)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
List<string> str1 = GetList(s1);
List<string> str2 = GetList(s2);
while (str1.Count != str2.Count)
{
if (str1.Count < str2.Count)
{
str1.Add("");
}
else
{
str2.Add("");
}
}
int x1 = 0; int res = 0; int x2 = 0; string y2 = "";
bool status = false;
string y1 = ""; bool s1Status = false; bool s2Status = false;
//s1status ==false then string ele int;
//s2status ==false then string ele int;
int result = 0;
for (int i = 0; i < str1.Count && i < str2.Count; i++)
{
status = int.TryParse(str1[i].ToString(), out res);
if (res == 0)
{
y1 = str1[i].ToString();
s1Status = false;
}
else
{
x1 = Convert.ToInt32(str1[i].ToString());
s1Status = true;
}
status = int.TryParse(str2[i].ToString(), out res);
if (res == 0)
{
y2 = str2[i].ToString();
s2Status = false;
}
else
{
x2 = Convert.ToInt32(str2[i].ToString());
s2Status = true;
}
//checking --the data comparision
if(!s2Status && !s1Status ) //both are strings
{
result = str1[i].CompareTo(str2[i]);
}
else if (s2Status && s1Status) //both are intergers
{
if (x1 == x2)
{
if (str1[i].ToString().Length < str2[i].ToString().Length)
{
result = 1;
}
else if (str1[i].ToString().Length > str2[i].ToString().Length)
result = -1;
else
result = 0;
}
else
{
int st1ZeroCount=str1[i].ToString().Trim().Length- str1[i].ToString().TrimStart(new char[]{'0'}).Length;
int st2ZeroCount = str2[i].ToString().Trim().Length - str2[i].ToString().TrimStart(new char[] { '0' }).Length;
if (st1ZeroCount > st2ZeroCount)
result = -1;
else if (st1ZeroCount < st2ZeroCount)
result = 1;
else
result = x1.CompareTo(x2);
}
}
else
{
result = str1[i].CompareTo(str2[i]);
}
if (result == 0)
{
continue;
}
else
break;
}
return result;
}
}
USAGE of this Class:
List<string> marks = new List<string>();
marks.Add("M'00Z1");
marks.Add("M'0A27");
marks.Add("M'00Z0");
marks.Add("0000A27");
marks.Add("100Z0");
string[] Markings = marks.ToArray();
Array.Sort(Markings, new AlphanumComparatorFast());
For those who likes a generic approach, adjust the AlphanumComparator to Dave Koelle : AlphanumComparator slightly.
Step one (I rename the class to non-abbreviated and taking a TCompareType generic type argument):
public class AlphanumericComparator<TCompareType> : IComparer<TCompareType>
The next adjustments is to import the following namespace:
using System.Collections.Generic;
And we change the signature of the Compare method from object to TCompareType:
public int Compare(TCompareType x, TCompareType y)
{ .... no further modifications
Now we can specify the right type for the AlphanumericComparator.
(It should actually be called AlphanumericComparer I think), when we use it.
Example usage from my code:
if (result.SearchResults.Any()) {
result.SearchResults = result.SearchResults.OrderBy(item => item.Code, new AlphanumericComparator<string>()).ToList();
}
Now you have an alphanumeric comparator (comparer) that accepts generic arguments and can be used on different types.
And here is an extension method for using the comparator:
/// <summary>
/// Returns an ordered collection by key selector (property expression) using alpha numeric comparer
/// </summary>
/// <typeparam name="T">The item type in the ienumerable</typeparam>
/// <typeparam name="TKey">The type of the key selector (property to order by)</typeparam>
/// <param name="coll">The source ienumerable</param>
/// <param name="keySelector">The key selector, use a member expression in lambda expression</param>
/// <returns></returns>
public static IEnumerable<T> OrderByMember<T, TKey>(this IEnumerable<T> coll, Func<T, TKey> keySelector)
{
var result = coll.OrderBy(keySelector, new AlphanumericComparer<TKey>());
return result;
}
Well looks like its doing a Lexicographical Ordering irrespective to small or capital chars.
You can try using some custom expression in that lambda to do that.
There's no natural way to do this in .NET, but have a look at this blog post on natural sorting
You could put this into an extension method and use that instead of OrderBy
Looks like Dave Koelle's code link is dead. I got the last version from WebArchive.
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Based on the Java implementation of Dave Koelle's Alphanum algorithm.
* Contributed by Jonathan Ruckwood <jonathan.ruckwood#gmail.com>
*
* Adapted by Dominik Hurnaus <dominik.hurnaus#gmail.com> to
* - correctly sort words where one word starts with another word
* - have slightly better performance
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
using System;
using System.Collections;
using System.Text;
/*
* Please compare against the latest Java version at http://www.DaveKoelle.com
* to see the most recent modifications
*/
namespace AlphanumComparator
{
public class AlphanumComparator : IComparer
{
private enum ChunkType {Alphanumeric, Numeric};
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
}
Since the number of characters at the beginning is variable, a regular expression would help:
var re = new Regex(#"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => int.Parse(re.Match(x).Value));
If there were a fixed number of prefix characters, then you could use the Substring method to extract starting from the relevant characters:
// parses the string as a number starting from the 5th character
var result = partNumbers.OrderBy(x => int.Parse(x.Substring(4)));
If the numbers might contain a decimal separator or thousands separator, then the regular expression needs to allow those characters as well:
var re = new Regex(#"[\d,]*\.?\d+$");
var result = partNumbers.OrderBy(x => double.Parse(x.Substring(4)));
If the string returned by the regular expression or Substring might be unparseable by int.Parse / double.Parse, then use the relevant TryParse variant:
var re = new Regex(#"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => {
int? parsed = null;
if (int.TryParse(re.Match(x).Value, out var temp)) {
parsed = temp;
}
return parsed;
});
Just extending #Nathan's answer here.
var maxStringLength = partNumbers.Max(x => x).Count();
var result = partNumbers.OrderBy(x => PadNumbers(x, maxStringLength));
Then pass the param to the PadNumbers function will be dynamic.
public static string PadNumbers(string input, int maxStringLength)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(maxStringLength, '0'));
}
I don´t know how to do that in LINQ, but maybe you like this way to:
Array.Sort(partNumbers, new AlphanumComparatorFast());
// Display the results
foreach (string h in partNumbers )
{
Console.WriteLine(h);
}
I have a file which contains lots of numbers which I want to reduce to construct a new file. First, I extract all the text using File.ReadAllText, then I split and extract numbers from each line that contains a number which are separated by commas or spaces. After scan, I replace all occurrences of each found number with the new reduced number but the problem is that this method is error prone since some numbers get replaced more than once
Here's the code I'm using:
List<float> oPaths = new List<float>();
List<float> nPaths = new List<float>();
var far = File.ReadAllText("paths.js");
foreach(var s in far.Split('\n'))
{
//if it starts with this that means there are some numbers
if (s.StartsWith("\t\tpath:"))
{
var paths = s.Substring(10).Split(new[]{',', ' '});
foreach(var n in paths)
{
float di;
if(float.TryParse(n, out di))
{
if(oPaths.Contains(di)) break;
oPaths.Add(di);
nPaths.Add(di * 3/4);
}
}
}
}
//second iteration to replace old numbers with new ones
var ns = far;
for (int i = 0; i < oPaths.Count; i++)
{
var od = oPaths[i].ToString();
var nd = nPaths[i].ToString();
ns = ns.Replace(od, nd);
}
File.WriteAllText("npaths.js", ns);
As you can see, the above method is redundant as it does not replace the strings on real time. Maybe my head is full, but I'm just lost on how to go about this. Any ideas?
Thanks.
I think a regex can help here
string text = File.ReadAllText(file);
string newtext = Regex.Replace(text, #"\b(([0-9]+)?\.)?[0-9]+\b", m =>
{
float f;
if (float.TryParse(m.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out f)) f *= 3.0f / 4;
return f.ToString();
});
File.WriteAllText(file, newtext);
Just after typing the question I realized the answer was to iterate character by character and replace accordingly. Here's the code I used to get this to work:
string nfar = "";
var far = File.ReadAllText("paths.js");
bool neg = false;
string ccc = "";
for(int i = 0; i < far.Length; i++)
{
char c = far[i];
if (Char.IsDigit(c) || c == '.')
{
ccc += c;
if (far[i + 1] == ' ' || far[i + 1] == ',')
{
ccc = neg ? "-" + ccc : ccc;
float di;
if (float.TryParse(ccc, out di))
{
nfar += (di*0.75f).ToString();
ccc = "";
neg = false;
}
}
}
else if (c == '-')
{
neg = true;
}
else
{
nfar += c;
}
}
File.WriteAllText("nfile.js", nfar);
Comments and/or optimization suggestions are welcome.
I write an rpn, with a struktogram.
Newest Problem: It is'nt work correctly now.
If input string is "5 + ((1 + 2) * 4) - 3"
My output is:
5 1 2 + 4 * 3 - +
I have to got this result:
5 1 2 + 4 * + 3 -
Edited the source
*That was the original problem, but helped me, and now the original mistakes fixed: *,
At the debug when the loop or
int i = 12, the c value is 0\0 or something else
and this value is added to output (name: formula) string as a '(' bracket. And I don't know why.
And the last '-' operation symbol, don't added to (or not look) at the end of output string (formula)
I misgave this problem cause by the '('.
I tried the program with other string input value, but always put an '(' to my string, and I don't know why... I saw that It was independt about the numbers of bracket. Always only one '(' add to my string...*)
Yes, in english LengyelFormula = rpn (it is hungarian)*
static void Main(string[] args)
{
String str = "5 + ( ( 1 + 2 ) * 4 ) −3";
String result=LengyelFormaKonvertalas(str);
Console.WriteLine(result.ToString());
Console.ReadLine();
}
static String LengyelFormaKonvertalas(String input) // this is the rpn method
{
Stack stack = new Stack();
String str = input.Replace(" ",string.Empty);
StringBuilder formula = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
char x=str[i];
if (x == '(')
stack.Push(x);
else if (IsOperandus(x)) // is it operand
{
formula.Append(x);
}
else if (IsOperator(x)) // is it operation
{
if (stack.Count>0 && (char)stack.Peek()!='(' && Prior(x)<=Prior((char)stack.Peek()) )
{
char y = (char)stack.Pop();
formula.Append(y);
}
if (stack.Count > 0 && (char)stack.Peek() != '(' && Prior(x) < Prior((char)stack.Peek()))
{
char y = (char)stack.Pop();
formula.Append(y);
}
stack.Push(x);
}
else
{
char y=(char)stack.Pop();
if (y!='(')
{
formula.Append(y);
}
}
}
while (stack.Count>0)
{
char c = (char)stack.Pop();
formula.Append(c);
}
return formula.ToString();
}
static bool IsOperator(char c)
{
return (c=='-'|| c=='+' || c=='*' || c=='/');
}
static bool IsOperandus(char c)
{
return (c>='0' && c<='9' || c=='.');
}
static int Prior(char c)
{
switch (c)
{
case '=':
return 1;
case '+':
return 2;
case '-':
return 2;
case '*':
return 3;
case '/':
return 3;
case '^':
return 4;
default:
throw new ArgumentException("Rossz paraméter");
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
class Sample {
static void Main(string[] args){
String str = "5 + ( ( 1 + 2 ) * 4 ) -3";
String result=LengyelFormaKonvertalas(str);
Console.WriteLine(result);
Console.ReadLine();
}
static String LengyelFormaKonvertalas(String input){
Stack<char> stack = new Stack<char>();
String str = input.Replace(" ", string.Empty);
StringBuilder formula = new StringBuilder();
for (int i = 0; i < str.Length; i++){
char x=str[i];
if (x == '(')
stack.Push(x);
else if (x == ')'){
while(stack.Count>0 && stack.Peek() != '(')
formula.Append(stack.Pop());
stack.Pop();
} else if (IsOperandus(x)){
formula.Append(x);
} else if (IsOperator(x)) {
while(stack.Count>0 && stack.Peek() != '(' && Prior(x)<=Prior(stack.Peek()) )
formula.Append(stack.Pop());
stack.Push(x);
}
else {
char y= stack.Pop();
if (y!='(')
formula.Append(y);
}
}
while (stack.Count>0) {
formula.Append(stack.Pop());
}
return formula.ToString();
}
static bool IsOperator(char c){
return (c=='-'|| c=='+' || c=='*' || c=='/');
}
static bool IsOperandus(char c){
return (c>='0' && c<='9' || c=='.');
}
static int Prior(char c){
switch (c){
case '=':
return 1;
case '+':
return 2;
case '-':
return 2;
case '*':
return 3;
case '/':
return 3;
case '^':
return 4;
default:
throw new ArgumentException("Rossz parameter");
}
}
}
In IsOperator, you check c == '-'.
But in the string, you write −3.
− isn't the same character than -
I don't know about Polish stuff so maybe I'm missing something, but that's why no '-' operator is printed, it fails the IsOperator check and goes into the else clause, which doesn't add it to formula.
When you get a ), you should pop all operators and add them to your formula until you reach a (, and pop that '(' as well.
When you get an operator, you should only pop the stack and add this operator to the formula if its priority is greater than or equal to that of x. Your second check is redundant because it is already covered by the first.
As a general rule: try your program with some simple inputs like 1+2+3, 1+2-3, 1*2+3 and 1+2*3 and see if you get the right result. Testing systematically like that should help you find errors faster.
I have a string[] in which every elements ends with some numeric value.
string[] partNumbers = new string[]
{
"ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11"
};
I am trying to sort the above array as follows using LINQ but I am not getting the expected result.
var result = partNumbers.OrderBy(x => x);
Actual Result:
AB1
Ab11
AB2
ABC1
ABC10
ABC10
ABC11
ABC2
Expected Result
AB1
AB2
AB11
..
That is because the default ordering for string is standard alpha numeric dictionary (lexicographic) ordering, and ABC11 will come before ABC2 because ordering always proceeds from left to right.
To get what you want, you need to pad the numeric portion in your order by clause, something like:
var result = partNumbers.OrderBy(x => PadNumbers(x));
where PadNumbers could be defined as:
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}
This pads zeros for any number (or numbers) that appear in the input string so that OrderBy sees:
ABC0000000010
ABC0000000001
...
AB0000000011
The padding only happens on the key used for comparison. The original strings (without padding) are preserved in the result.
Note that this approach assumes a maximum number of digits for numbers in the input.
If you want to sort a list of objects by a specific property using LINQ and a custom comparer like the one by Dave Koelle you would do something like this:
...
items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList();
...
You also have to alter Dave's class to inherit from System.Collections.Generic.IComparer<object> instead of the basic IComparer so the class signature becomes:
...
public class AlphanumComparator : System.Collections.Generic.IComparer<object>
{
...
Personally, I prefer the implementation by James McCormack because it implements IDisposable, though my benchmarking shows that it is slightly slower.
You can use PInvoke to get fast and good result:
class AlphanumericComparer : IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern int StrCmpLogicalW(string s1, string s2);
public int Compare(string x, string y) => StrCmpLogicalW(x, y);
}
You can use it like AlphanumComparatorFast from the answer above.
You can PInvoke to StrCmpLogicalW (the windows function) to do this. See here: Natural Sort Order in C#
public class AlphanumComparatorFast : IComparer
{
List<string> GetList(string s1)
{
List<string> SB1 = new List<string>();
string st1, st2, st3;
st1 = "";
bool flag = char.IsDigit(s1[0]);
foreach (char c in s1)
{
if (flag != char.IsDigit(c) || c=='\'')
{
if(st1!="")
SB1.Add(st1);
st1 = "";
flag = char.IsDigit(c);
}
if (char.IsDigit(c))
{
st1 += c;
}
if (char.IsLetter(c))
{
st1 += c;
}
}
SB1.Add(st1);
return SB1;
}
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;
}
if (s1 == s2)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
List<string> str1 = GetList(s1);
List<string> str2 = GetList(s2);
while (str1.Count != str2.Count)
{
if (str1.Count < str2.Count)
{
str1.Add("");
}
else
{
str2.Add("");
}
}
int x1 = 0; int res = 0; int x2 = 0; string y2 = "";
bool status = false;
string y1 = ""; bool s1Status = false; bool s2Status = false;
//s1status ==false then string ele int;
//s2status ==false then string ele int;
int result = 0;
for (int i = 0; i < str1.Count && i < str2.Count; i++)
{
status = int.TryParse(str1[i].ToString(), out res);
if (res == 0)
{
y1 = str1[i].ToString();
s1Status = false;
}
else
{
x1 = Convert.ToInt32(str1[i].ToString());
s1Status = true;
}
status = int.TryParse(str2[i].ToString(), out res);
if (res == 0)
{
y2 = str2[i].ToString();
s2Status = false;
}
else
{
x2 = Convert.ToInt32(str2[i].ToString());
s2Status = true;
}
//checking --the data comparision
if(!s2Status && !s1Status ) //both are strings
{
result = str1[i].CompareTo(str2[i]);
}
else if (s2Status && s1Status) //both are intergers
{
if (x1 == x2)
{
if (str1[i].ToString().Length < str2[i].ToString().Length)
{
result = 1;
}
else if (str1[i].ToString().Length > str2[i].ToString().Length)
result = -1;
else
result = 0;
}
else
{
int st1ZeroCount=str1[i].ToString().Trim().Length- str1[i].ToString().TrimStart(new char[]{'0'}).Length;
int st2ZeroCount = str2[i].ToString().Trim().Length - str2[i].ToString().TrimStart(new char[] { '0' }).Length;
if (st1ZeroCount > st2ZeroCount)
result = -1;
else if (st1ZeroCount < st2ZeroCount)
result = 1;
else
result = x1.CompareTo(x2);
}
}
else
{
result = str1[i].CompareTo(str2[i]);
}
if (result == 0)
{
continue;
}
else
break;
}
return result;
}
}
USAGE of this Class:
List<string> marks = new List<string>();
marks.Add("M'00Z1");
marks.Add("M'0A27");
marks.Add("M'00Z0");
marks.Add("0000A27");
marks.Add("100Z0");
string[] Markings = marks.ToArray();
Array.Sort(Markings, new AlphanumComparatorFast());
For those who likes a generic approach, adjust the AlphanumComparator to Dave Koelle : AlphanumComparator slightly.
Step one (I rename the class to non-abbreviated and taking a TCompareType generic type argument):
public class AlphanumericComparator<TCompareType> : IComparer<TCompareType>
The next adjustments is to import the following namespace:
using System.Collections.Generic;
And we change the signature of the Compare method from object to TCompareType:
public int Compare(TCompareType x, TCompareType y)
{ .... no further modifications
Now we can specify the right type for the AlphanumericComparator.
(It should actually be called AlphanumericComparer I think), when we use it.
Example usage from my code:
if (result.SearchResults.Any()) {
result.SearchResults = result.SearchResults.OrderBy(item => item.Code, new AlphanumericComparator<string>()).ToList();
}
Now you have an alphanumeric comparator (comparer) that accepts generic arguments and can be used on different types.
And here is an extension method for using the comparator:
/// <summary>
/// Returns an ordered collection by key selector (property expression) using alpha numeric comparer
/// </summary>
/// <typeparam name="T">The item type in the ienumerable</typeparam>
/// <typeparam name="TKey">The type of the key selector (property to order by)</typeparam>
/// <param name="coll">The source ienumerable</param>
/// <param name="keySelector">The key selector, use a member expression in lambda expression</param>
/// <returns></returns>
public static IEnumerable<T> OrderByMember<T, TKey>(this IEnumerable<T> coll, Func<T, TKey> keySelector)
{
var result = coll.OrderBy(keySelector, new AlphanumericComparer<TKey>());
return result;
}
Well looks like its doing a Lexicographical Ordering irrespective to small or capital chars.
You can try using some custom expression in that lambda to do that.
There's no natural way to do this in .NET, but have a look at this blog post on natural sorting
You could put this into an extension method and use that instead of OrderBy
Looks like Dave Koelle's code link is dead. I got the last version from WebArchive.
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Based on the Java implementation of Dave Koelle's Alphanum algorithm.
* Contributed by Jonathan Ruckwood <jonathan.ruckwood#gmail.com>
*
* Adapted by Dominik Hurnaus <dominik.hurnaus#gmail.com> to
* - correctly sort words where one word starts with another word
* - have slightly better performance
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
using System;
using System.Collections;
using System.Text;
/*
* Please compare against the latest Java version at http://www.DaveKoelle.com
* to see the most recent modifications
*/
namespace AlphanumComparator
{
public class AlphanumComparator : IComparer
{
private enum ChunkType {Alphanumeric, Numeric};
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
}
Since the number of characters at the beginning is variable, a regular expression would help:
var re = new Regex(#"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => int.Parse(re.Match(x).Value));
If there were a fixed number of prefix characters, then you could use the Substring method to extract starting from the relevant characters:
// parses the string as a number starting from the 5th character
var result = partNumbers.OrderBy(x => int.Parse(x.Substring(4)));
If the numbers might contain a decimal separator or thousands separator, then the regular expression needs to allow those characters as well:
var re = new Regex(#"[\d,]*\.?\d+$");
var result = partNumbers.OrderBy(x => double.Parse(x.Substring(4)));
If the string returned by the regular expression or Substring might be unparseable by int.Parse / double.Parse, then use the relevant TryParse variant:
var re = new Regex(#"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => {
int? parsed = null;
if (int.TryParse(re.Match(x).Value, out var temp)) {
parsed = temp;
}
return parsed;
});
Just extending #Nathan's answer here.
var maxStringLength = partNumbers.Max(x => x).Count();
var result = partNumbers.OrderBy(x => PadNumbers(x, maxStringLength));
Then pass the param to the PadNumbers function will be dynamic.
public static string PadNumbers(string input, int maxStringLength)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(maxStringLength, '0'));
}
I don´t know how to do that in LINQ, but maybe you like this way to:
Array.Sort(partNumbers, new AlphanumComparatorFast());
// Display the results
foreach (string h in partNumbers )
{
Console.WriteLine(h);
}
The question is complicated but I will explain it in details.
The goal is to make a function which will return next "step" of the given string.
For example
String.Step("a"); // = "b"
String.Step("b"); // = "c"
String.Step("g"); // = "h"
String.Step("z"); // = "A"
String.Step("A"); // = "B"
String.Step("B"); // = "C"
String.Step("G"); // = "H"
Until here its quite easy, But taking in mind that input IS string it can contain more than 1 characters and the function must behave like this.
String.Step("Z"); // = "aa";
String.Step("aa"); // = "ab";
String.Step("ag"); // = "ah";
String.Step("az"); // = "aA";
String.Step("aA"); // = "aB";
String.Step("aZ"); // = "ba";
String.Step("ZZ"); // = "aaa";
and so on...
This doesn't exactly need to extend the base String class.
I tried to work it out by each characters ASCII values but got stuck with strings containing 2 characters.
I would really appreciate if someone can provide full code of the function.
Thanks in advance.
EDIT
*I'm sorry I forgot to mention earlier that the function "reparse" the self generated string when its length reaches n.
continuation of this function will be smth like this. for example n = 3
String.Step("aaa"); // = "aab";
String.Step("aaZ"); // = "aba";
String.Step("aba"); // = "abb";
String.Step("abb"); // = "abc";
String.Step("abZ"); // = "aca";
.....
String.Step("zzZ"); // = "zAa";
String.Step("zAa"); // = "zAb";
........
I'm sorry I didn't mention it earlier, after reading some answers I realised that the problem was in question.
Without this the function will always produce character "a" n times after the end of the step.
NOTE: This answer is incorrect, as "aa" should follow after "Z"... (see comments below)
Here is an algorithm that might work:
each "string" represents a number to a given base (here: twice the count of letters in the alphabet).
The next step can thus be computed by parsing the "number"-string back into a int, adding 1 and then formatting it back to the base.
Example:
"a" == 1 -> step("a") == step(1) == 1 + 1 == 2 == "b"
Now your problem is reduced to parsing the string as a number to a given base and reformatting it. A quick googling suggests this page: http://everything2.com/title/convert+any+number+to+decimal
How to implement this?
a lookup table for letters to their corresponding number: a=1, b=2, c=3, ... Y = ?, Z = 0
to parse a string to number, read the characters in reverse order, looking up the numbers and adding them up:
"ab" -> 2*BASE^0 + 1*BASE^1
with BASE being the number of "digits" (2 count of letters in alphabet, is that 48?)
EDIT: This link looks even more promising: http://www.citidel.org/bitstream/10117/20/12/convexp.html
Quite collection of approaches, here is mine:-
The Function:
private static string IncrementString(string s)
{
byte[] vals = System.Text.Encoding.ASCII.GetBytes(s);
for (var i = vals.Length - 1; i >= 0; i--)
{
if (vals[i] < 90)
{
vals[i] += 1;
break;
}
if (vals[i] == 90)
{
if (i != 0)
{
vals[i] = 97;
continue;
}
else
{
return new String('a', vals.Length + 1);
}
}
if (vals[i] < 122)
{
vals[i] += 1;
break;
}
vals[i] = 65;
break;
}
return System.Text.Encoding.ASCII.GetString(vals);
}
The Tests
Console.WriteLine(IncrementString("a") == "b");
Console.WriteLine(IncrementString("z") == "A");
Console.WriteLine(IncrementString("Z") == "aa");
Console.WriteLine(IncrementString("aa") == "ab");
Console.WriteLine(IncrementString("az") == "aA");
Console.WriteLine(IncrementString("aZ") == "ba");
Console.WriteLine(IncrementString("zZ") == "Aa");
Console.WriteLine(IncrementString("Za") == "Zb");
Console.WriteLine(IncrementString("ZZ") == "aaa");
public static class StringStep
{
public static string Next(string str)
{
string result = String.Empty;
int index = str.Length - 1;
bool carry;
do
{
result = Increment(str[index--], out carry) + result;
}
while (carry && index >= 0);
if (index >= 0) result = str.Substring(0, index+1) + result;
if (carry) result = "a" + result;
return result;
}
private static char Increment(char value, out bool carry)
{
carry = false;
if (value >= 'a' && value < 'z' || value >= 'A' && value < 'Z')
{
return (char)((int)value + 1);
}
if (value == 'z') return 'A';
if (value == 'Z')
{
carry = true;
return 'a';
}
throw new Exception(String.Format("Invalid character value: {0}", value));
}
}
Split the input string into columns and process each, right-to-left, like you would if it was basic arithmetic. Apply whatever code you've got that works with a single column to each column. When you get a Z, you 'increment' the next-left column using the same algorithm. If there's no next-left column, stick in an 'a'.
I'm sorry the question is stated partly.
I edited the question so that it meets the requirements, without the edit the function would end up with a n times by step by step increasing each word from lowercase a to uppercase z without "re-parsing" it.
Please consider re-reading the question, including the edited part
This is what I came up with. I'm not relying on ASCII int conversion, and am rather using an array of characters. This should do precisely what you're looking for.
public static string Step(this string s)
{
char[] stepChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
char[] str = s.ToCharArray();
int idx = s.Length - 1;
char lastChar = str[idx];
for (int i=0; i<stepChars.Length; i++)
{
if (stepChars[i] == lastChar)
{
if (i == stepChars.Length - 1)
{
str[idx] = stepChars[0];
if (str.Length > 1)
{
string tmp = Step(new string(str.Take(str.Length - 1).ToArray()));
str = (tmp + str[idx]).ToCharArray();
}
else
str = new char[] { stepChars[0], str[idx] };
}
else
str[idx] = stepChars[i + 1];
break;
}
}
return new string(str);
}
This is a special case of a numeral system. It has the base of 52. If you write some parser and output logic you can do any kind of arithmetics an obviously the +1 (++) here.
The digits are "a"-"z" and "A" to "Z" where "a" is zero and "Z" is 51
So you have to write a parser who takes the string and builds an int or long from it. This function is called StringToInt() and is implemented straight forward (transform char to number (0..51) multiply with 52 and take the next char)
And you need the reverse function IntToString which is also implementet straight forward (modulo the int with 52 and transform result to digit, divide the int by 52 and repeat this until int is null)
With this functions you can do stuff like this:
IntToString( StringToInt("ZZ") +1 ) // Will be "aaa"
You need to account for A) the fact that capital letters have a lower decimal value in the Ascii table than lower case ones. B) The table is not continuous A-Z-a-z - there are characters inbetween Z and a.
public static string stepChar(string str)
{
return stepChar(str, str.Length - 1);
}
public static string stepChar(string str, int charPos)
{
return stepChar(Encoding.ASCII.GetBytes(str), charPos);
}
public static string stepChar(byte[] strBytes, int charPos)
{
//Escape case
if (charPos < 0)
{
//just prepend with a and return
return "a" + Encoding.ASCII.GetString(strBytes);
}
else
{
strBytes[charPos]++;
if (strBytes[charPos] == 91)
{
//Z -> a plus increment previous char
strBytes[charPos] = 97;
return stepChar(strBytes, charPos - 1); }
else
{
if (strBytes[charPos] == 123)
{
//z -> A
strBytes[charPos] = 65;
}
return Encoding.ASCII.GetString(strBytes);
}
}
}
You'll probably want some checking in place to ensure that the input string only contains chars A-Za-z
Edit Tidied up code and added new overload to remove redundant byte[] -> string -> byte[] conversion
Proof http://geekcubed.org/random/strIncr.png
This is a lot like how Excel columns would work if they were unbounded. You could change 52 to reference chars.Length for easier modification.
static class AlphaInt {
private static string chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string StepNext(string input) {
return IntToAlpha(AlphaToInt(input) + 1);
}
public static string IntToAlpha(int num) {
if(num-- <= 0) return "a";
if(num % 52 == num) return chars.Substring(num, 1);
return IntToAlpha(num / 52) + IntToAlpha(num % 52 + 1);
}
public static int AlphaToInt(string str) {
int num = 0;
for(int i = 0; i < str.Length; i++) {
num += (chars.IndexOf(str.Substring(i, 1)) + 1)
* (int)Math.Pow(52, str.Length - i - 1);
}
return num;
}
}
LetterToNum should be be a Function that maps "a" to 0 and "Z" to 51.
NumToLetter the inverse.
long x = "aazeiZa".Aggregate((x,y) => (x*52) + LetterToNum(y)) + 1;
string s = "";
do { // assertion: x > 0
var c = x % 52;
s = NumToLetter() + s;
x = (x - c) / 52;
} while (x > 0)
// s now should contain the result