How to convert Excel sheet column names into numbers? [duplicate] - c#

This question already has answers here:
What is the algorithm to convert an Excel Column Letter into its Number?
(11 answers)
Closed 9 years ago.
I was wondering what is the best way to convert excel sheet column names into numbers.
I'm working with Excel Package, a good library to handle .xlsx documents. This library unfortunately doesn't have this functionality included.
OBS: The first column, A, corresponds
to number 1 in this library.

This function should work for an arbitrary length column name.
public static int GetColumnNumber(string name)
{
int number = 0;
int pow = 1;
for (int i = name.Length - 1; i >= 0; i--)
{
number += (name[i] - 'A' + 1) * pow;
pow *= 26;
}
return number;
}

I had to deal with this a few months ago. The inverse - column index to column name - is fun, too, and becomes really messy if you try to solve it with a zero based index not recognizing that this complicates things. It could be so simple if it would be a normal polyadic numeral system ...
Here is a simplified version of my solution as a extension method without error handling and all that stuff.
public static Int32 ToOneBasedIndex(this String name)
{
return name.ToUpper().
Aggregate(0, (column, letter) => 26 * column + letter - 'A' + 1);
}

I've been working with this for a while now and found this to work really good for columns that go beyond A-Z, or even beyond AA-ZZ... It's accomplished by breaking apart each character in the string and recursively calling itself to derive the DEC value of the ASCII character (less 64), then multiplying it by 26^n. A return value of long was used to overcome a potential limitation when n > 4.
public long columnNumber(String columnName)
{
char[] chars = columnName.ToUpper().ToCharArray();
return (long)(Math.Pow(26, chars.Count() - 1)) *
(System.Convert.ToInt32(chars[0]) - 64) +
((chars.Count() > 2) ? columnNumber(columnName.Substring(1, columnName.Length - 1)) :
((chars.Count() == 2) ? (System.Convert.ToInt32(chars[chars.Count() - 1]) - 64) : 0));
}
Also, if you'd like to get the inverse (i.e. pass in the columnNumber and get the columnName, here's some code that works for that.
public String columnName(long columnNumber)
{
StringBuilder retVal = new StringBuilder();
int x = 0;
for (int n = (int)(Math.Log(25*(columnNumber + 1))/Math.Log(26)) - 1; n >= 0; n--)
{
x = (int)((Math.Pow(26,(n + 1)) - 1) / 25 - 1);
if (columnNumber > x)
retVal.Append(System.Convert.ToChar((int)(((columnNumber - x - 1) / Math.Pow(26, n)) % 26 + 65)));
}
return retVal.ToString();
}

Source code:
namespace XLS
{
/// <summary>
/// Represents a single cell in a excell sheet
/// </summary>
public struct Cell
{
private long row;
private long column;
private string columnAddress;
private string address;
private bool dataChange;
/// <summary>
/// Initializes a new instance of the XLS.Cell
/// class with the specified row and column of excel worksheet
/// </summary>
/// <param name="row">The row index of a cell</param>
/// <param name="column">The column index of a cell</param>
public Cell(long row, long column)
{
this.row = row;
this.column = column;
dataChange = true;
address = string.Empty;
columnAddress = string.Empty;
}
/// <summary>
/// Initializes a new instance of the XLS.Cell
/// class with the specified address of excel worksheet
/// </summary>
/// <param name="address">The adress of a cell</param>
public Cell(string address)
{
this.address = address;
dataChange = false;
row = GetRow(address);
columnAddress = GetColumnAddress(address);
column = GetColumn(columnAddress);
}
/// <summary>
/// Gets or sets the row of this XLS.Cell
/// </summary>
public long Row
{
get { return row <= 0 ? 1 : row; }
set { row = value; dataChange = true; }
}
/// <summary>
/// Gets or sets the column of this XLS.Cell
/// </summary>
public long Column
{
get { return column <= 0 ? 1 : column; }
set { column = value; dataChange = true; }
}
/// <summary>
/// Gets or sets the address of this XLS.Cell
/// </summary>
public string Address
{
get { return dataChange ? ToAddress() : address; }
set
{
address = value;
row = GetRow(address);
column = GetColumn(address);
}
}
/// <summary>
/// Gets the column address of this XLS.Cell
/// </summary>
public string ColumnAddress
{
get { return GetColumnAddress(Address); }
private set { columnAddress = value; }
}
#region Private Methods
private static long GetRow(string address)
{
return long.Parse(address.Substring(GetStartIndex(address)));
}
private static string GetColumnAddress(string address)
{
return address.Substring(0, GetStartIndex(address)).ToUpperInvariant();
}
private static long GetColumn(string columnAddress)
{
char[] characters = columnAddress.ToCharArray();
int sum = 0;
for (int i = 0; i < characters.Length; i++)
{
sum *= 26;
sum += (characters[i] - 'A' + 1);
}
return (long)sum;
}
private static int GetStartIndex(string address)
{
return address.IndexOfAny("123456789".ToCharArray());
}
private string ToAddress()
{
string indexToString = string.Empty;
if (Column > 26)
{
indexToString = ((char)(65 + (int)((Column - 1) / 26) - 1)).ToString();
}
indexToString += (char)(65 + ((Column - 1) % 26));
dataChange = false;
return indexToString + Row;
}
#endregion
}
}

O24 has a column number and you want a name:
=LEFT(RIGHT(ADDRESS(1,O24),LEN(ADDRESS(1,O24))-1),FIND("$",RIGHT((ADDRESS(1,O24)),LEN(ADDRESS(1,O24))-1))-1)
O37 has a column name and you want a number:
=COLUMN(INDIRECT(O37&1))

public static string GetColumnName(int index)
{
const string letters = "ZABCDEFGHIJKLMNOPQRSTUVWXY";
int NextPos = (index / 26);
int LastPos = (index % 26);
if (LastPos == 0) NextPos--;
if (index > 26)
return GetColumnName(NextPos) + letters[LastPos];
else
return letters[LastPos] + "";
}

Related

Test method in Console c#

I want to test a binary search in console app. I've populalated my array. I have no errors. Code runs... I'm not sure what lines of code I need to test this.
Here is my code..
internal class Program
{
static void Main(string[] args)
{
//Initialise array
int[] arr = { 800, 11, 50, 771, 649, 770, 240, 9 };
int key = 0;
Program.BinarySearchDisplay(arr, key);
}
//Binary Search Method
public static object BinarySearchDisplay(int[] arr, int key)
{
int minNum = 0;
int maxNum = arr.Length - 1;
while (minNum <= maxNum)
{
int mid = (minNum + maxNum) / 2;
if (key == arr[mid])
{
return ++mid;
}
else if (key < arr[mid])
{
maxNum = mid - 1;
}
else
{
minNum = mid + 1;
}
}
return "None";
}
}
}
A couple of things of note here. Although technically correct, it is not a good idea to return values of different types from a function. C# being statically typed provides the opportunity to expect certain return types that are used to check the code doesn't contain errors at compile type.
A function needs to return a value, and so you need to assign this value to a variable. This is an example of how to do this for a function called Fun()
int x = Fun(a,b,c);
Here is one way to achieve what you are trying to do. Have a function that returns a value if successful or flag when it does not succeed. Return a bool type indicating success, and include an out parameter that gets assigned a value before the function exits.
Notice that you store the results of the function in a variable found of type bool and then act accordingly using an if(found) statement
class Program
{
static void Main(string[] args)
{
//Initialise array
int[] arr = { 800, 11, 50, 771, 649, 770, 240, 9 };
int key = 50;
Console.WriteLine($"Array: {string.Join(",", arr)}");
Console.WriteLine($"Key: {key}");
// call the function an store the results in a variable
bool found = BinarySearchDisplay(arr, key, out int index);
if (found)
{
Console.WriteLine($"Found arr[{index}] = {arr[index]}");
}
else
{
Console.WriteLine("Not Found.");
}
}
/// <summary>
/// Binary Search Method.
/// </summary>
/// <param name="arr">The array of values.</param>
/// <param name="key">The key to search for.</param>
/// <param name="index">The array index if key was found, -1 otherwise.</param>
/// <returns>true if key found, false otherwise.</returns>
public static bool BinarySearchDisplay(int[] arr, int key, out int index)
{
int minIndex = 0;
int maxIndex = arr.Length - 1;
index = -1;
while (minIndex <= maxIndex)
{
index = (minIndex + maxIndex) / 2;
if (key == arr[index])
{
return true;
}
else if (key < arr[index])
{
maxIndex = index - 1;
}
else
{
minIndex = index + 1;
}
}
return false;
}
}
Note that Program.BinarySearchDisplay() is replaced by just BinarySearchDisplay(). This is because the function is within the same class Program and there is no need to fully qualify its name.
The result of the above code is
Array: 800,11,50,771,649,770,240,9
Key: 50
Found arr[2] = 50
I think there was an error in the op code in return ++mid; as the next index value is returned. Maybe this was done because indexing is 0-based, and the op wanted a 1-based result. But mixing the two styles is dangerous and will lead to bugs.
This is a similar pattern to bool int.TryParse(string, out int) to convert a string to a number if possible, or return false otherwise.
if(int.TryParse("1150", out int x))
{
// x holds the value 1150
}

How to take X amount of questions, that sum up Y amount of difficulty

I have a table
Questions -> Question(string), Difficulty (int, 1-10)
I need to create a method, that as the title mentions, takes X amount of questions, whose difficulty should sum up Y.
For example:
getQuestions(2,10) -> Question1 (diff: 4), Question2 (diff: 6)
getQuestions(3,15) -> Question3 (diff: 5), Question4 (diff: 5), Question5 (diff: 5)
How can I achieve something like this with LINQ?
Here's one way to do it, using a modified version of the recursive solution found here: Finding all possible combinations of numbers to reach a given sum
First, a public method that will do some quick validation and then call a recursive method to get the results:
/// <summary>
/// Gets lists of numQuestions length of all combinations
/// of questions whose difficulties add up to sumDifficulty
/// </summary>
/// <param name="questions">The list of questions to search</param>
/// <param name="numQuestions">The number of questions required</param>
/// <param name="sumDifficulty">The amount that the difficulties should sum to</param>
/// <returns></returns>
public static List<List<Question>> GetQuestions(List<Question> questions,
int numQuestions, int sumDifficulty)
{
if (questions == null) throw new ArgumentNullException("questions");
var results = new List<List<Question>>();
// Fail fast argument validation
if (numQuestions < 1 ||
numQuestions > questions.Count ||
sumDifficulty < numQuestions * Question.MinDifficulty ||
sumDifficulty > numQuestions * Question.MaxDifficulty)
{
return results;
}
// If we only need single questions, no need to do any recursion
if (numQuestions == 1)
{
results.AddRange(questions.Where(q => q.Difficulty == sumDifficulty)
.Select(q => new List<Question> {q}));
return results;
}
// We can remove any questions who have a difficulty that's higher
// than the sumDifficulty minus the number of questions plus one
var candidateQuestions =
questions.Where(q => q.Difficulty <= sumDifficulty - numQuestions + 1)
.ToList();
if (!candidateQuestions.Any())
{
return results;
}
GetSumsRecursively(candidateQuestions, sumDifficulty, new List<Question>(),
numQuestions, results);
return results;
}
And then the recursive method that does the heavy lifting:
private static void GetSumsRecursively(IReadOnlyList<Question> questions,
int sumDifficulty, List<Question> candidates, int numQuestions,
ICollection<List<Question>> results)
{
int candidateSum = candidates.Sum(x => x.Difficulty);
if (candidateSum == sumDifficulty && candidates.Count == numQuestions)
{
results.Add(candidates);
}
if (candidateSum >= sumDifficulty)
return;
for (int i = 0; i < questions.Count; i++)
{
var remaining = new List<Question>();
for (int j = i + 1; j < questions.Count; j++)
{
remaining.Add(questions[j]);
}
var filteredCandidates = new List<Question>(candidates) {questions[i]};
GetSumsRecursively(remaining, sumDifficulty, filteredCandidates,
numQuestions, results);
}
}
Here's an example usage:
public static void Main()
{
const int numberOfQuestions = 3;
const int sumOfDifficulty = 15;
// Since I don't have your table, I'm using a list of objects to fake it
var questions = new List<Question>();
for (int i = 1; i < 11; i++)
{
questions.Add(new Question {Difficulty = i % 10 + 1,
QuestionString = "Question #" + i});
}
var results = GetQuestions(questions, numberOfQuestions, sumOfDifficulty);
// Write output to console to verify results
foreach (var result in results)
{
Console.WriteLine("{0} = {1}", string.Join(" + ",
result.Select(r => r.Difficulty)), sumOfDifficulty);
}
}
And just so you have everything to make this work, here's my Question class used to fake your table:
internal class Question
{
public const int MinDifficulty = 1;
public const int MaxDifficulty = 10;
private int _difficulty;
public int Difficulty
{
get { return _difficulty; }
set
{
if (value < MinDifficulty) _difficulty = MinDifficulty;
else if (value > MaxDifficulty) _difficulty = MaxDifficulty;
else _difficulty = value;
}
}
public string QuestionString { get; set; }
}

Detecting Filename Patterns for Creating RegEx

When using a string to define a RegEx, I'd like to know if there is a way to get my code to recognize a pattern in the files contained within a directory.
The goal is to rename these files using our naming conventions, so I'm writing something to try to create the expression to use in RegEx.
I've started something here, but I don't think it is the best, and I'm not sure how to fill in the "{0}" portion of my RegEx expression.
private Regex m_regex;
public string DirPattern(string path, string[] extensions) {
string result = null;
int endPos = 0;
int resLen = 0;
int startLen = 0;
var dir = new DirectoryInfo(path);
foreach (var file in dir.GetFiles()) {
if (extensions.Contains(file.Extension)) {
if (!String.IsNullOrEmpty(result)) {
int sL = 0;
int fileLen = file.Name.Length;
string one = null;
for (int i = 0; i < resLen && i < fileLen; i++) {
if (result[i] == file.Name[i]) {
sL = i + 1;
if (String.IsNullOrEmpty(one)) {
one = file.Name;
} else {
break;
}
}
}
if (!String.IsNullOrEmpty(one)) {
int eP = 0;
int oneLen = one.Length;
for (int i = fileLen - 1; -1 < i; i--) {
if (result[i] == file.Name[i]) {
eP = i - 1;
} else {
break;
}
}
if ((0 < endPos) && (eP == endPos)) {
if ((0 < startLen) && (sL == startLen)) {
result = one.Substring(0, startLen) + "{0}" + one.Substring(endPos);
} else if (0 < sL) {
startLen = sL;
}
} else if (0 < sL) {
startLen = sL;
}
}
} else {
result = file.Name;
resLen = result.Length;
}
}
}
return result;
}
public bool GenerateRexEx(string path, string[] extensions) {
var pattern = DirPattern(path, extensions);
if (!String.IsNullOrEmpty(pattern)) {
m_regex = new Regex(pattern);
return true;
}
return false;
}
Here is an example of a list of files that would be most like our company files (which I am not allowed to post):
UPDATE:
The goal is to take files with names like this:
FOLDER_PATTERN_1 + MixedContent + FOLDER_PATTERN_2
and rename them using our format:
OUR_PATTERN_1 + MixedContent + OUR_PATTERN_2
That way, our software will be able to search the files more efficiently.
I think that in your case you need just to find count of characters in the prefix pattern and postfix pattern. Then you can simply replace some count of characters with your pattern. I wrote a simple code which I tested and works. You can inspire yourself and use the same method I think. Anyway there are areas to make this better, but I hope it is enough to answer your question.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
static class Program
{
static void Main()
{
var inputFilenames = new string[]
{
"mtn_flint501-muxed",
"mtn_flint502-muxed",
"mtn_flint503-muxed",
"mtn_flint504-muxed",
"mtn_flint505-muxed",
"mtn_flint506-muxed",
"mtn_flint507-muxed",
"mtn_flint508-muxed",
"mtn_flint509-muxed",
"mtn_flint510-muxed",
"mtn_flint511-muxed",
"mtn_flint512-muxed",
};
var replacedFilenames = ReplaceFileNames(inputFilenames);
for (int i = 0; i < inputFilenames.Length; i++)
{
Console.WriteLine("{0} >> {1}", inputFilenames[i], replacedFilenames[i]);
}
Console.ReadKey();
}
private const string OurPrefixPattern = "Prefix_";
private const string OurPostfixPattern = "_Postfix";
/// <summary>
/// Method which will find the filename's pattern and replace it with our pattern
/// </summary>
/// <param name="fileNames"></param>
/// <returns></returns>
public static string[] ReplaceFileNames(params string[] fileNames)
{
//At first, we will find count of characters, which are same for
//all filenames as prefix and store it to prefixCount variable and
//we will find count of characters which are same for all filenames
//as postfix and store it to postfixCount variable
var prefixCount = int.MaxValue;
var postfixCount = int.MaxValue;
//We will use first filename as the reference one (we will be comparing)
//all filenames with this one
var referenceFilename = fileNames[0];
var reversedReferenceFilename = referenceFilename.ReverseString();
//Lets find the prefixCount and postfixCount
foreach (var filename in fileNames)
{
if (filename == referenceFilename)
{
continue;
}
//Check for prefix count
var firstDifferenceIndex = referenceFilename.GetFirstDifferentIndexWith(filename);
if (firstDifferenceIndex < prefixCount)
{
prefixCount = firstDifferenceIndex;
}
//For postfix count we will do the same, but with reversed strings
firstDifferenceIndex = reversedReferenceFilename.GetFirstDifferentIndexWith(filename.ReverseString());
if (firstDifferenceIndex < postfixCount)
{
postfixCount = firstDifferenceIndex;
}
}
//So now replace given filnames with our prefix and post fix.
//Our regex determines only how many characters should be replaced
var prefixRegexToReplace = string.Format("^.{{{0}}}", prefixCount);
var postfixRegexToReplace = string.Format(".{{{0}}}$", postfixCount);
var result = new string[fileNames.Length];
for (int i = 0; i < fileNames.Length; i++)
{
//Replace the prefix
result[i] = Regex.Replace(fileNames[i], prefixRegexToReplace, OurPrefixPattern);
//Replace the postfix
result[i] = Regex.Replace(result[i], postfixRegexToReplace, OurPostfixPattern);
}
return result;
}
/// <summary>
/// Gets the first index in which the strings has different character
/// </summary>
/// <param name="value"></param>
/// <param name="stringToCompare"></param>
/// <returns></returns>
private static int GetFirstDifferentIndexWith(this string value, string stringToCompare)
{
return value.Zip(stringToCompare, (c1, c2) => c1 == c2).TakeWhile(b => b).Count();
}
/// <summary>
/// Revers given string
/// </summary>
/// <param name="value">String which should be reversed</param>
/// <returns>Reversed string</returns>
private static string ReverseString(this string value)
{
char[] charArray = value.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
}
The console output looks like this
mtn_flint501-muxed >> Prefix_01_Postfix
mtn_flint502-muxed >> Prefix_02_Postfix
mtn_flint503-muxed >> Prefix_03_Postfix
mtn_flint504-muxed >> Prefix_04_Postfix
mtn_flint505-muxed >> Prefix_05_Postfix
mtn_flint506-muxed >> Prefix_06_Postfix
mtn_flint507-muxed >> Prefix_07_Postfix
mtn_flint508-muxed >> Prefix_08_Postfix
mtn_flint509-muxed >> Prefix_09_Postfix
mtn_flint510-muxed >> Prefix_10_Postfix
mtn_flint511-muxed >> Prefix_11_Postfix
mtn_flint512-muxed >> Prefix_12_Postfix

Implementing the De Boor splining algorithm in C#

I know it's a slightly "Please do my homework" question, but I'm trying to implement the De Boor algorithm as explained here: http://en.wikipedia.org/wiki/De_Boor's_algorithm
I have got a generic class which takes any array of any type and two functions which yield an x and y for each point.
I am getting very inaccurate interpolations when I supply to GetPoint(x) an x which is very near to the lowest x value in _d.
Is anyone able to see what I'm doing wrong? Many thanks in advance.
The data I'm using are:
x y
0.002739726 0.999988548
0.019178082 0.999917365
0.038356164 0.999835128
0.057534247 0.999753381
0.082191781 0.999648832
0.167123288 0.999286638
0.249315068 0.998939303
0.334246575 0.998583164
0.419178082 0.998222171
0.495890411 0.997899634
0.747945205 0.99680615
1 0.99559971
1.249315068 0.994187653
1.495890411 0.9926708
1.747945205 0.99066351
2 0.988439344
2.249315068 0.985377424
2.498630137 0.981972695
2.750684932 0.978188863
3.002739726 0.974069647
4.002739726 0.952415995
5.002739726 0.926226504
6.002739726 0.897271422
7.005479452 0.866241091
8.005479452 0.834348616
9.005479452 0.802369725
The code
public class Spline<T>
{
/// <summary>
/// Coordinate
/// </summary>
private struct TwoDPoint
{
public double x;
public double y;
};
/// <summary>
/// Unused apart from debugging.
/// </summary>
private T [] _originalDataPoints;
/// <summary>
/// Stores the points we will perform the algorithm on.
/// </summary>
private TwoDPoint [] _d;
private int _n; // Value for the "order" used in the De Boor algorithm
public Spline(T [] dataPoints, Func<T, double> xFunc, Func<T, double> yFunc, int n)
{
SortAndAssignPoints(dataPoints, xFunc, yFunc);
_n = n;
}
/// <summary>
/// Populates points in d sorted ascending by their x value
/// </summary>
private void SortAndAssignPoints(T [] dataPoints, Func<T, double> xFunc, Func<T, double> yFunc)
{
_originalDataPoints = dataPoints;
_d = dataPoints
.Select(point => new TwoDPoint()
{
x = xFunc(point),
y = yFunc(point)
})
.OrderBy(point => point.x).ToArray();
}
/// <summary>
/// Function which gets an interpolated value
/// </summary>
public double GetPoint(double x)
{
double sum = 0;
for (int i = 0; i < _d.Length; i++)
{
sum += _d[i].y * N_General(_n, i, x);
}
return sum;
}
/// <summary>
/// General N function as given in the algorithm
/// </summary>
private double N_General(int n, int i, double x)
{
if (n == 0)
{
return N_Base(i, x);
}
double firstCoefficient;
if ((i + n) > (_d.Length - 1))
{
firstCoefficient = 0D;
}
else
{
firstCoefficient =
(x - _d[i].x) / (_d[i + n].x - _d[i].x);
}
double secondCoefficient;
if ((i + n + 1) > (_d.Length - 1))
{
secondCoefficient = 0D;
}
else
{
secondCoefficient =
(_d[i + n + 1].x - x) / (_d[i + n + 1].x - _d[i + 1].x);
}
return firstCoefficient * N_General(n - 1, i, x) +
secondCoefficient * N_General(n - 1, i + 1, x);
}
/// <summary>
/// N_i^0 as given in the algorithm
/// </summary>
private double N_Base(int i, double x)
{
//Bound check
if (
(i >= (_d.Length - 1)) ||
(i < 0)
)
{
return 0;
}
if ((x >= _d[i].x) &&
(x < _d[i+1].x))
{
return 1;
}
else
{
return 0;
}
}
}
From your codes, it looks like you are using y values from _d as the control points and x values from _d as the knot values. Please note that for B-spline curves the control points in general do not lie on the curve itself. So, you should not expect that passing an x value from your data array to GetPoint(x) function will get back the corresponding y value in the data array.
Also, for B-spline curves, the rule of "number of knots = number of control points + order" should always be followed. You really cannot use_d[i].x as knots and _d[i].y as control points as you will have the same number of knots and control points.
Finally, I would recommend using this as your reference instead of the Wikipedia link as it offers better description of De Boor-Cox algorithm.

Comparison of multi-part alphanumeric strings

I receive a string which contains the sw version currently running in the system.
I want to do some operations only if the system is running on a certain sw version or
later.
e.g. If system is running sw version 2.D or later (2.E, ..) I do some operations. If system is running lower sw version (2.C, ..), then I don't do it.
How to do this comparison for strings?
There are many ways of doing this, but if you know that the string is in the form x.y or even x.y.z... then I would suggest using a custom StringComparer:
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace StingComparisons
{
[TestClass]
public class StringComparison
{
[TestMethod]
public void TestMethod1()
{
var a = "2.E";
var b = "2.F";
var c = "2.C";
var d = "1.F";
var e = "3.A";
StringComparer comp = new MyStringComparer();
Assert.IsTrue(b.IsSameOrAfter(a, comp));
Assert.IsFalse(c.IsSameOrAfter(a, comp));
Assert.IsFalse(d.IsSameOrAfter(a, comp));
Assert.IsTrue(e.IsSameOrAfter(a, comp));
Assert.IsTrue(a.IsSameOrAfter(a, comp));
}
[TestMethod]
public void TestMethod2()
{
var a = "2.E.1";
var b = "2.E";
var c = "2.E.2";
var d = "2.F";
var e = "2.D.3";
var f = "3.A";
StringComparer comp = new MyStringComparer();
Assert.IsFalse(b.DotDelimitedIsSameOrAfter(a));
Assert.IsTrue(c.DotDelimitedIsSameOrAfter(a));
Assert.IsTrue(d.DotDelimitedIsSameOrAfter(a));
Assert.IsFalse(e.DotDelimitedIsSameOrAfter(a));
Assert.IsTrue(f.DotDelimitedIsSameOrAfter(a));
Assert.IsTrue(a.DotDelimitedIsSameOrAfter(a));
}
}
public static class stringExtensions
{
public static bool DotDelimitedIsSameOrAfter(this string a, string b)
{
return a.IsSameOrAfter(b, new MyStringComparer());
}
public static bool IsSameOrAfter(this string a, string b, StringComparer comp)
{
return comp.Compare(a, b) <= 0;
}
}
public class MyStringComparer : StringComparer
{
public override int Compare(string x, string y)
{
var partsX = x.Split('.');
var partsY = y.Split('.');
for (int i = 0; i < partsY.Length; i++)
{
if (partsX.Length <= i)
return 1;
var partComp = partsY[i].CompareTo(partsX[i]);
if (partComp != 0)
return partComp;
}
return 0;
}
public override bool Equals(string x, string y)
{
return x.Equals(y);
}
public override int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
}
I have wrapped the calls in an extension method purely to make it a bit more readable.
Since I was needing just that (and a bit more), I've written the following comparer which compares two alphanumeric version strings:
/// <summary>
/// Compares two alphanumeric version numbers.
/// </summary>
public class AlphanumericVersionComparer : IComparer<string>
{
/// <summary>
/// Compares two alphanumeric version numbers and returns a value
/// indicating whether one is less than, equal to, or greater than the other.
/// </summary>
/// <param name="x">The first alphanumeric version number to compare.</param>
/// <param name="y">The second alphanumeric version number to compare.</param>
/// <returns>A signed integer that indicates the relative values of x and y.</returns>
public int Compare(string x, string y)
{
// Validate parameters
if (x == null) throw new ArgumentNullException("x");
if (y == null) throw new ArgumentNullException("y");
// Test for equality
if (x == y)
return 0;
// Split the different parts of the number
string[] xParts = x.Split('.');
string[] yParts = y.Split('.');
// Compare each parts
AlphanumericComparer alphaNumComparer = new AlphanumericComparer();
for (int i = 0, n = Math.Max(xParts.Length, yParts.Length); i < n; i++)
{
// If the part at index i is not in y => x is greater
if (i >= yParts.Length)
return 1;
// If the part at index i is not in x => y is greater
if (i >= xParts.Length)
return -1;
// Compare the two alphanumerical numbers
int result = alphaNumComparer.Compare(xParts[i], yParts[i]);
if (result != 0)
{
return result;
}
}
// The two numbers are equal (really??? I thought we tested for equality already!)
System.Diagnostics.Debug.Fail("Not supposed to reach this code...");
return 0;
}
}
/// <summary>
/// Compares two alphanumeric strings.
/// </summary>
/// <remarks>See http://snipd.net/alphanumericnatural-sorting-in-c-using-icomparer </remarks>
public class AlphanumericComparer : IComparer<string>
{
/// <summary>
/// Compares two alphanumerics and returns a value
/// indicating whether one is less than, equal to, or greater than the other.
/// </summary>
/// <param name="x">The first alphanumeric to compare.</param>
/// <param name="y">The second alphanumeric to compare.</param>
/// <returns>A signed integer that indicates the relative values of x and y.</returns>
public int Compare(string x, string y)
{
int len1 = x.Length;
int len2 = y.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = x[marker1];
char ch2 = y[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = x[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = y[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
You can play with the fiddle here: https://dotnetfiddle.net/28iius.
All credits to http://snipd.net/alphanumericnatural-sorting-in-c-using-icomparer for the alphanumeric comparison.

Categories

Resources