system.version more than 3 decimal points c# - c#

I am currently receiving the following error -
"Version string portion was too short or too long"
When using this statement -
records = records.OrderBy(r => new Version(r.RefNo)).ToList();
To order a list of string's called RefNo. It fails on 25.1.2.1.2 so i assume it is because it has four decimal points? as it works ok with 3....
Is the max four deciaml points for system.version?
Thanks

A Version can only have 4 parts:
major, minor, build, and revision, in that order, and all separated by
periods.
That's why your approach fails. You could create an extension-method which handles this case, f.e.:
public static Version TryParseVersion(this string versionString)
{
if (string.IsNullOrEmpty(versionString))
return null;
String[] tokens = versionString.Split('.');
if (tokens.Length < 2 || !tokens.All(t => t.All(char.IsDigit)))
return null;
if (tokens.Length > 4)
{
int maxVersionLength = tokens.Skip(4).Max(t => t.Length);
string normalizedRest = string.Concat(tokens.Skip(4).Select(t => t.PadLeft(maxVersionLength, '0')));
tokens[3] = $"{tokens[3].PadLeft(maxVersionLength, '0')}{normalizedRest}";
Array.Resize(ref tokens, 4);
}
versionString = string.Join(".", tokens);
bool valid = Version.TryParse(versionString, out Version v);
return valid ? v : null;
}
Now you can use it in this way:
records = records
.OrderBy(r => r.RefNo.TryParseVersion())
.ToList();
With your sample this new version string will be parsed(successfully): 25.1.2.12

See MSDN
Costructor public Version(string version)
A string containing the major, minor, build, and revision numbers,
where each number is delimited with a period character ('.').
Makes a total of 4 numbers.
Means the string is limited to 4 numbers, 5 lead to an error.
Also the costructors with int's as parameters only support 1 to 4 parameters.

sorry for the late reply but here is the finished extension method i used with a couple of alterations -
public static Version ParseRefNo(this string refNoString)
{
if (string.IsNullOrEmpty(refNoString))
return null;
String[] tokens = refNoString.Split('.');
if (tokens.Length < 2 || !tokens.All(t => t.All(char.IsDigit)))
return null;
if (tokens.Length > 4)
{
int maxVersionLength = tokens.Skip(4).Max(t => t.Length);
string normalizedRest = string.Concat(tokens.Skip(4).Select(t => t.PadLeft(maxVersionLength, '0')));
tokens[3] = $"{tokens[3].PadLeft(maxVersionLength, '0')}{normalizedRest}";
Array.Resize(ref tokens, 4);
}
refNoString = string.Join(".", tokens);
Version v = null;
bool valid = Version.TryParse(refNoString, out v);
return valid ? v : null;
}

Related

How to validate or compare string by omitting certain part of it

I have a string as below
"a1/type/xyz/parts"
The part where 'xyz' exists is dynamic and varies accordingly at any size. I want to compare just the two strings are equal discarding the 'xyz' portion exactly.
For example I have string as below
"a1/type/abcd/parts"
Then my comparison has to be successful
I tried with regular expression as below. Though my knowledge on regular expressions is limited and it did not work. Probably something wrong in the way I used.
var regex = #"^[a-zA-Z]{2}/\[a-zA-Z]{16}/\[0-9a-zA-Z]/\[a-z]{5}/$";
var result = Regex.Match("mystring", regex).Success;
Another idea is to get substring of first and last part omitting the unwanted portion and comparing it.
The comparison should be successful by discarding certain portion of the string with effective code.
Comparison successful cases
string1: "a1/type/21412ghh/parts"
string2: "a1/type/eeeee122ghh/parts"
Comparison failure cases:
string1: "a1/type/21412ghh/parts"
string2: "a2/type/eeeee122ghh/parts/mm"
In short "a1/type/abcd/parts" in this part of string the non-bold part is static always.
Honestly, you could do this using regex, and pull apart the string. But you have a specified delimiter, just use String.Split:
bool AreEqualAccordingToMyRules(string input1, string input2)
{
var split1 = input1.Split('/');
var split2 = input2.Split('/');
return split1.Length == split2.Length // strings must have equal number of sections
&& split1[0] == split2[0] // section 1 must match
&& split1[1] == split2[1] // section 2 must match
&& split1[3] == split2[3] // section 4 must match
}
You can try Split (to get parts) and Linq (to exclude 3d one)
using System.Linq;
...
string string1 = "a1/type/xyz/parts";
string string2 = "a1/type/abcd/parts";
bool result = string1
.Split('/') // string1 parts
.Where((v, i) => i != 2) // all except 3d one
.SequenceEqual(string2 // must be equal to
.Split('/') // string2 parts
.Where((v, i) => i != 2)); // except 3d one
Here's a small programm using string functions to compare the parts before and after the middle part:
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(CutOutMiddle("a1/type/21412ghh/parts"));
Console.WriteLine("True: " + CompareNoMiddle("a1/type/21412ghh/parts", "a1/type/21412ghasdasdh/parts"));
Console.WriteLine("False: " + CompareNoMiddle("a1/type/21412ghh/parts", "a2/type/21412ghh/parts/someval"));
Console.WriteLine("False: " + CompareNoMiddle("a1/type/21412ghh/parts", "a1/type/21412ghasdasdh/parts/someappendix"));
}
private static bool CompareNoMiddle(string s1, string s2)
{
var s1CutOut = CutOutMiddle(s1);
var s2CutOut = CutOutMiddle(s2);
return s1CutOut == s2CutOut;
}
private static string CutOutMiddle(string val)
{
var fistSlash = val.IndexOf('/', 0);
var secondSlash = val.IndexOf('/', fistSlash+1);
var thirdSlash = val.IndexOf('/', secondSlash+1);
var firstPart = val.Substring(0, secondSlash);
var secondPart = val.Substring(thirdSlash, val.Length - thirdSlash);
return firstPart + secondPart;
}
}
returns
a1/type/parts
True: True
False: False
False: False
This solution should cover your case, as said by others, if you have a delimiter use it. In the function below you could change int skip for string ignore or something similar and within the comparison loop if(arrayStringOne[i] == ignore) continue;.
public bool Compare(string valueOne, string valueTwo, int skip) {
var delimiterOccuranceOne = valueOne.Count(f => f == '/');
var delimiterOccuranceTwo = valueTwo.Count(f => f == '/');
if(delimiterOccuranceOne == delimiterOccuranceTwo) {
var arrayStringOne = valueOne.Split('/');
var arrayStringTwo = valueTwo.Split('/');
for(int i=0; i < arrayStringOne.Length; ++i) {
if(i == skip) continue; // or instead of an index you could use a string
if(arrayStringOne[i] != arrayStringTwo[i]) {
return false;
}
}
return true;
}
return false;
}
Compare("a1/type/abcd/parts", "a1/type/xyz/parts", 2);

Split exponential number string representation into power and exponent

I have strings which come from resources in exponential form like the following: 2⁴. I was wondering if there is a way to split this into:
var base = 2; //or even "2", this is also helpful since it can be parsed
and
var exponent = 4;
I have searched the internet and msdn Standard Numeric Format Strings also, but I was unable to find the solve for this case.
You can add mapping between digits to superscript digits, then select all digits from source (this will be the base) and all the others - the exponent
const string superscriptDigits = "⁰¹²³⁴⁵⁶⁷⁸⁹";
var digitToSuperscriptMapping = superscriptDigits.Select((c, i) => new { c, i })
.ToDictionary(item => item.c, item => item.i.ToString());
const string source = "23⁴⁴";
var baseString = new string(source.TakeWhile(char.IsDigit).ToArray());
var exponentString = string.Concat(source.SkipWhile(char.IsDigit).Select(c => digitToSuperscriptMapping[c]));
Now you can convert base and exponent to int.
Also you'll need to validate input before executing conversion code.
Or even without mapping:
var baseString = new string(source.TakeWhile(char.IsDigit).ToArray());
var exponentString = string.Concat(source.SkipWhile(char.IsDigit).Select(c => char.GetNumericValue(c).ToString()));
You can use a regular expression together with String.Normalize:
var value = "42⁴³";
var match = Regex.Match(value, #"(?<base>\d+)(?<exponent>[⁰¹²³⁴-⁹]+)");
var #base = int.Parse(match.Groups["base"].Value);
var exponent = int.Parse(match.Groups["exponent"].Value.Normalize(NormalizationForm.FormKD));
Console.WriteLine($"base: {#base}, exponent: {exponent}");
The way your exponent is formatted is called superscript in English.
You can find many question related to this if you search with that keyword.
Digits in superscript are mapped in Unicode as:
0 -> \u2070
1 -> \u00b9
2 -> \u00b2
3 -> \u00b3
4 -> \u2074
5 -> \u2075
6 -> \u2076
7 -> \u2077
8 -> \u2078
9 -> \u2079
You can search for that values in your string:
Lis<char> superscriptDigits = new List<char>(){
'\u2070', \u00b9', \u00b2', \u00b3', \u2074',
\u2075', \u2076', \u2077', \u2078', \u2079"};
//the rest of the string is the expontent. Join remaining chars.
str.SkipWhile( ch => !superscriptDigits.Contains(ch) );
You get the idea
You can use a simple regex (if your source is quite clean):
string value = "2⁴⁴";
string regex = #"(?<base>\d+)(?<exp>.*)";
var matches = Regex.Matches(value, regex);
int b;
int exp = 0;
int.TryParse(matches[0].Groups["base"].Value, out b);
foreach (char c in matches[0].Groups["exp"].Value)
{
exp = exp * 10 + expToInt(c.ToString());
}
Console.Out.WriteLine("base is : {0}, exponent is {1}", b, exp);
And expToInt (based on Unicode subscripts and superscripts):
public static int expToInt(string c)
{
switch (c)
{
case "\u2070":
return 0;
case "\u00b9":
return 1;
case "\u00b2":
return 2;
case "\u00b3":
return 3;
case "\u2074":
return 4;
case "\u2075":
return 5;
case "\u2076":
return 6;
case "\u2077":
return 7;
case "\u2078":
return 8;
case "\u2079":
return 9;
default:
throw new ArgumentOutOfRangeException();
}
}
This will output:
base is 2, exp is 44

How to perform word search using LINQ?

I have a list which contains the name of suppliers. Say
SuppId Supplier Name
----------------------------------
1 Aardema & Whitelaw
2 Aafedt Forde Gray
3 Whitelaw & Sears-Ewald
using following LINQ query
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey));
I can return records correctly in the following conditions,
1) If i am using search string as "Whitelaw & Sears-Ewald" it will return 3rd record.
2) If i am using "Whitelaw" or "Sears-Ewald" it will return 3rd record.
But how can i return 3rd record if i am giving search string as "Whitelaw Sears-Ewald". It always returns 0 records.
Can i use ALL to get this result, but i dont know how to use it for this particular need.
What I usually do in this situation is split the words into a collection, then perform the following:
var searchopts = SearchKey.Split(' ').ToList();
supplierListQuery = supplierListQuery
.Where(x => searchopts.Any(y=> x.SupplierName.Contains(y)));
This works for me:
IEnumerable<string> keyWords = SearchKey.Split('');
supplierListQuery = supplierListQuery
.AsParallel()
.Where
(
x => keyWords.All
(
keyword => x.SupplierName.ContainsIgnoreCase(keyword)
)
);
Thank you all for your quick responses. But the one which worked or a easy fix to handle this was timothyclifford's note on this. Like he said i alterd my answer to this
string[] filters = SearchKey.ToLower().Split(new[] { ' ' });
objSuppliersList = (from x in objSuppliersList
where filters.All(f => x.SupplierName.ToLower().Contains(f))
select x).ToList();
Now it returns the result for all my serach conditions.
Because "Whitelaw" appears in both you will get both records. Otherwise there is no dynamic way to determine you only want the last one. If you know you only have these 3 then append .Last() to get the final record.
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey.Split(' ')[0]));
You need to use some sort of string comparer to create your own simple Search Engine and then you can find strings that are most likely to be included in your result :
public static class SearchEngine
{
public static double CompareStrings(string val1, string val2)
{
if ((val1.Length == 0) || (val2.Length == 0)) return 0;
if (val1 == val2) return 100;
double maxLength = Math.Max(val1.Length, val2.Length);
double minLength = Math.Min(val1.Length, val2.Length);
int charIndex = 0;
for (int i = 0; i < minLength; i++) { if (val1.Contains(val2[i])) charIndex++; }
return Math.Round(charIndex / maxLength * 100);
}
public static List<string> Search(this string[] values, string searchKey, double threshold)
{
List<string> result = new List<string>();
for (int i = 0; i < values.Length; i++) if (CompareStrings(values[i], searchKey) > threshold) result.Add(values[i]);
return result;
}
}
Example of usage :
string[] array = { "Aardema & Whitelaw", "Aafedt Forde Gray", "Whitelaw & Sears-Ewald" };
var result = array.Search("WhitelawSears-Ewald", 80);
// Results that matches this string with 80% or more
foreach (var item in result)
{
Console.WriteLine(item);
}
Output: Whitelaw & Sears-Ewald
If you want an easy (not very handy) solution,
var result = supplierListQuery
.Select(x => normalize(x.SupplierName))
.Where(x => x.Contains(normalize(SearchKey)));
string normalize(string inputStr)
{
string retVal = inputStr.Replace("&", "");
while (retVal.IndexOf(" ") >= 0)
{
retVal = retVal.Replace(" ", " ");
}
return retVal;
}

Find a fixed length string with specific string part in C#

I want to find a string of fixed length with specific substring. But I need to do it like we can do in SQL queries.
Example:
I have strings like -
AB012345
AB12345
AB123456
AB1234567
AB98765
AB987654
I want to select strings that have AB at first and 6 characters afterwards. Which can be done in SQL by SELECT * FROM [table_name] WHERE [column_name] LIKE 'AB______' (6 underscores after AB).
So the result will be:
AB012345
AB123456
AB987654
I need to know if there is any way to select strings in such way with C#, by using AB______.
You can use Regular Expressions to filter the result:
List<string> sList = new List<string>(){"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
var qry = sList.Where(s=>Regex.Match(s, #"^AB\d{6}$").Success);
Considering you have an string array:
string[] str = new string[3]{"AB012345", "A12345", "AB98765"};
var result = str.Where(x => x.StartsWith("AB") && x.Length == 8).ToList();
The logic is if it starts with AB, and its length is 8. It is your best match.
this should do it
List<string> sList = new List<string>(){
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
List<string> sREsult = sList.Where(x => x.Length == 8 && x.StartsWith("AB")).ToList();
first x.Length == 8 determines the length and x.StartsWith("AB") determines the required characters at the start of the string
This can be achieved by using string.Startwith and string.Length function like this:
public bool CheckStringValid (String input)
{
if (input.StartWith ("AB") && input.Length == 8)
{
return true;
}
else
{
return false;
}
}
This will return true if string matches your criteria.
Hope this helps.
var strlist = new List<string>()
{
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"
};
var result = strlist.Where(
s => (s.StartsWith("AB") &&(s.Length == 8))
);
foreach(var v in result)
{
Console.WriteLine(v.ToString());
}

C# code to convert string to Double

I am trying to convert some particular types of strings into double in c#. Normally, Convert.ToDouble() works all great but this one does not always return healthy strings. Which means that input does not always come in format of "4.2". SOmetimes it also comes in form of 4.4.2. Now, i can also not rely on positioning and truncating since it can be 10.11.445 tomorrow?
Any easy and short string manipulation function that I can apply on this scenario?
struct CalifornicatedNumber
{
private string value;
public CalifornicatedNumber(string value)
{
this.value = value;
}
static public implicit operator CalifornicatedNumber(string value)
{
return new CalifornicatedNumber(value);
}
static public implicit operator CalifornicatedNumber(double value)
{
return new CalifornicatedNumber(value.ToString());
}
static public implicit operator double(CalifornicatedNumber calif)
{
return double.Parse(MakeItMakeSense(calif.value));
}
static private string MakeItMakeSense(string calif)
{
if (calif.Count(x => x == '.') > 1)
calif = calif.Substring(0, calif.IndexOf('.', calif.IndexOf('.') + 1));
return calif;
}
}
then...
CalifornicatedNumber califnum;
califnum = "10.11.145";
Console.WriteLine(califnum);
if (califnum > 10) { Console.WriteLine("huzzah");}
califnum = 13.42;
Console.WriteLine(califnum);
if (califnum > 10) { Console.WriteLine("huzzahZah"); }
...this is the most foolish piece of code I have ever written.
After the posted comments I am assuming you would like to take the string 4.4.2 and convert it to double dropping everything after the second . (if found).
A method such as.
public static double ConvertStringToDouble(string inputString)
{
if (inputString.Count(x => x == '.') > 1)
inputString = inputString.Substring(0, inputString.IndexOf('.', inputString.IndexOf('.') + 1));
return double.Parse(inputString);
}
I would do an approach of creating a bunch of strategies for parsing the input text and then iterating through the strategies until a result is found.
First I'd define a tryParseDouble function:
Func<string, double?> tryParseDouble = t =>
{
double value;
if (double.TryParse(t, out value))
{
return value;
}
return null;
};
Then I'd create my list of strategies:
var strategies = new Func<string, double?>[]
{
t =>
{
var parts = t.Split('.');
return parts.Length > 1
? tryParseDouble(String.Join(".", parts.Take(2)))
: null;
},
tryParseDouble,
t => null,
};
And finally I'd get the result:
var text = "4.4.2";
var result =
strategies
.Select(p => p(text))
.Where(v => v != null)
.FirstOrDefault();
The result variable is a double? with the value parsed or a null if none of the strategies work. The final strategy, t => null, is there to be explicit but is not necessary in getting the final null result.
As new strategies are needed to parse different types of input text they can just be added to the list as needed.
I think this will do what you want according to your comments by parsing either the whole string (if no decimal points) or just the first two parts of the sting if there are multiple decimals.
String[] parts = stringVal.Split('.');
double doubleVal;
if (parts.length > 1)
{
doubleVal = Convert.ToDouble(parts[0] + "." + parts[1]);
}
else
{
doubleVale = Convert.ToDouble(stingVal);
}

Categories