How to parse a Ternary Statement in C# - c#

I'm accepting an input string that I want to be a ternary statement that works on strings. So my method signature would look like this:
public string Parse(string value, string ternaryStatement)
and there parameters would give these results:
Parse(null, "=?It's Null:It's Not Null") == "It's Null" // Empty string would
Parse("", "=?It's Null:It's Not Null") == "It's Null" // be same as null
This example is fairly simple, Split the string first by '?' then by ':'
But of course I need a method to handle escape characters, "\", "\?" and ":", where "\" is valid anywhere, "\?" would only be valid before the first unescaped "?" and ":" would only be valid after that same "?".
Parse(#"\?\", #"=\\\?\\?\:Match\::\:No Match\:") == ":Match:"
Parse(#"\?\", #"!=\\\?\\?\:No Match\::\:Match\:") == ":Match:"
But this is really complicated. I believe I can perform it using regular expressions, but that just creates another problem since this is well beyond my limited understanding of regular expressions. What's the best way to tackle this problem?
Edit 1
Some of the background: I'm storing a format for a URL in a database config table (It's actually Dynamics 365 for Customer Engagement, but that doesn't matter at this point). The format is stored as strings, and the parameters that are required are defined in code. So generally it looks like this:
Format: "https://something.com?Foo={0}&Bar={1}"
Description: "0 - Foo, 1 - Bar"
where the description is used both for the person that is formatting the url, and the developer that needs to know how to structure the format statement.
The problem I'm running into right now is that I have a url that requires at least one of two different parameters. If one of the values is null or empty, it will error if included in the url. So I need a way of saying, if Foo is null or Bar is null, don't include the name or &. Ideally I'd like to implement this like this:
"https://something.com?{0:=?:Foo={{0}}}&}{1:=?:Bar={{1}}}}"
So if Foo is null and Bar is "Bar" the output would be
"https://something.com?Bar=Bar"
I could also see this being used if we need to switch between a 0/1 for a boolean to true/false without having to change code:
"https://something.com?{0:=0?false:true}"

The two regexes should be:
Regex rx = new Regex(#"(?<=(?:^|[^\\])(?:\\\\)*)\?");
Regex rx2 = new Regex(#"(?<=(?:^|[^\\])(?:\\\\)*):");
Use them like:
var m = rx.Match(str);
if (m.Success)
{
int ix = m.Index;
}
The main point of the two rx is that the searched string (\? or :) must be preceded by
(?<=(?:^|[^\\])(?:\\\\)*)
that is the beginning of the string ^ or not a \ ([^\\]) plus zero or an even number of \\ that is (?:\\\\)*.
A all-in-one regex is:
Regex rx = new Regex(#"^(?<operator>=|!=|<=|>=|<|>)(?<cmp>(?:(?:\\.)|[^?:])*)\?(?<true>(?:(?:\\.)|[^?:])*):(?<false>(?:(?:\\.)|[^?:])*)$");
if (m.Success)
{
string op = m.Groups["operator"].Value;
string cmp = m.Groups["cmp"].Value;
string true1 = m.Groups["true"].Value;
string false1 = m.Groups["false"].Value;
}
In op you'll get the comparison operator used, in cmp the comparand, in true1 and false1 the true and false strings. If !m.Success then the string isn't correctly formatted. Comprehending the regex is left as a simple exercise for the reader (unless you comprehend a regex, you shouldn't ever use it, because before or later you'll have to modify it/fix it/debug it)

Solution to returning different values based on input string
Why do you need to pass in a ternary statement / wouldn't this make more sense?
string Parse(string value, string returnIfNull, string returnIfNotNull)
{
return string.IsNullOrEmpty(value) ? returnIfNull: returnIfNotNull;
}
Console.WriteLine(Parse("", "treat as null", "not expected"));
Console.WriteLine(Parse("hello", "not expected", "this value's not null"));
Parsing a ternary string for values
However, if you really need to do this, you could use something like the below:
private static readonly Regex TernaryParserRegex = new Regex(
#"^=\?(?<ifNull>(?:\\(\\\\)*:|[^:])*)(?<!\\):(?<ifNotNull>(?:\\(\\\\)*:|[^:])*)$"
/* , RegexOptions.Compiled //include this line for performance if appropriate */
);
private string[] ParseTernaryString (string ternaryStatement)
{
var results = TernaryParserRegex.Match(ternaryStatement);
if (results.Success)
{
string[] returnVal = {
results.Groups["ifNull"].Value
,
results.Groups["ifNotNull"].Value
};
return returnVal;
}
else
{
throw new Exception("Invalid Ternary Statement"); //use an appropriate exception type here; or have the function return `{null,null}` / some other default as appropriate
}
}
public string Parse(string value, string ternaryStatement)
{
var returnValues = ParseTernaryString(ternaryStatement);
return string.IsNullOrEmpty(value) ? returnValues[0]: returnValues[1];
}
//Example Usage:
Console.WriteLine(Parse("", "=?treat as null:not expected"));
Console.WriteLine(Parse("hello", "=?not expected:this value's not null"));
An explanation of the regex & additional examples are available here:
https://regex101.com/r/YJ9qd3/1
Appending non-null/blank values to a URL's Query String
public void Main()
{
var url = "https://example.com?something=keepMe&foo=FooWasHere&bar=swapMeQuick";
var dict = new System.Collections.Generic.Dictionary<string, string>();
dict.Add("foo", null);
dict.Add("bar", "hello");
dict.Add("boo", "new");
Console.WriteLine(CreateUri(url, dict).ToString());
}
Uri CreateUri(string uri, IDictionary<string, string> parameters)
{
return CreateUri(new Uri(uri), parameters);
}
Uri CreateUri(Uri uri, IDictionary<string, string> parameters)
{
var query = HttpUtility.ParseQueryString(uri.Query); //https://msdn.microsoft.com/en-us/library/ms150046(v=vs.110).aspx; though returns HttpValueCollection
foreach (string key in parameters.Keys)
{
if (string.IsNullOrEmpty(parameters[key]))
{ //parameter is null or empty; if such a parameter already exists on our URL, remove it
query.Remove(key); //if this parameter does not already exist, has no effect (but no exception is thrown)
}
else
{ //parameter has a value; add or update the query string with this value
query.Set(key, parameters[key]);
}
}
var builder = new UriBuilder(uri);
builder.Query = query.ToString();
return builder.Uri;
}

Related

Trying to make substrings from a string in C#

I have a string aaaaabbbbbccccc I have a dictionary that has certain rules
Dictionary<string, string> rules = new Dictionary<string, string>();
rules.Add("abc", "aab");
rules.Add("ac", "ba");
rules.Add("cb", "cc");
This means if string has abc it will be replaced with aab but string doesn't have any rules that match. So, I am creating a new string out of the old string based on these rules. For instance, if I rearrange the old string to abcabcabcabcabc then rule can be applied. But I am stuck at rearranging. I tried using IndexOf and Remove functions but I didn't get the positive output. This is my code
string s;
s = "aaaaabbbbbccccc";
string newString = "";
int ia, ib, ic;
//Formulating rule 1
if (s.Contains("a") && s.Contains("b") && s.Contains("c"))
{
ia = s.IndexOf("a");
ib = s.IndexOf("b");
ic = s.IndexOf("c");
if (ia < ib && ib < ic)
{
newString += "abc";
s.Remove(ia, 1);
s.Remove(ib, 1);
s.Remove(ic, 1);
}
}
Console.WriteLine("New String " + newString);
Console.WriteLine("Old String " + s);
I am getting
New String abc
Old String aaaaabbbbbccccc //Which is wrong.
Can anyone help what I am doing wrong or is there any better way?
Your explanation does not match your code.
In your explanation, you say that aaaaabbbbbccccc does not match any rule (e.g. the "abc" rule). However, in your code, you are not checking if it contains "abc", but rather that it contain "a" and "b" and "c", not necessarily as a single chunk:
if (s.Contains("a") && s.Contains("b") && s.Contains("c"))
This is an important difference:
aaaaabbbbbccccc does not contain abc (rule does not apply)
aaaaabbbbbccccc does contain a, b and c (rule does apply)
You're contradicting yourself. Which is correct here, the code or your explanation? Does the rule check for the exact string, or does it check for all characters separately?
Awkward string manipulation.
Based on your code; I infer that you're not experienced with some very common string operations (no offense intended). Unless you contradict me, I'm going to assume that your explanation is correct and your code is not.
Checking if a string contains a substring:
As I explained previously, there is an important difference between checking is a string contains a substring:
s.Contains("abc") //1
and checking if a string contains each individual character of a substring:
s.Contains("a") && s.Contains("b") && s.Contains("c") //2
As a more practical example; does my username (Flater) contain the substring "Fertla"?
If you use the logic in //1, the answer is no.
If you use the logic in //2, the answer is yes.
Based on your explanation, you should be using //1
Replacing a string:
This means if string has abc it will be replaced with aab
There is a very simple method for this:
s.Replace("abc", "aab");
Some examples:
abcdef becomes aabdef
abcabcabc becomes aabaabaab (it replaces all occurrences)
uvwxyz becomes uvwxyz (if it doesn't occur, nothing gets replaced)
Take note of the second and third bullet point.
String.Replace() will replace all occurrences, in case the substring occurs more than once. Based on your explanation, I assume this is what you want (if it's possible for a substring to occur more than once, to begin with).
If the substring is not found, String.Replace() will give you the same output as its input. Nothing will be changed. This means that you can execute your rule on your string without needing to check if the substring exists:
If it does exist, then your value will be replaced; just like you want it to happen.
If it does not exist, then nothing will happen; which is also what you want to happen.
You can dramatically simplify your code!
Create your dictionary;
Dictionary<string, string> rules = new Dictionary<string, string>();
rules.Add("abc", "aab");
rules.Add("ac", "ba");
rules.Add("cb", "cc");
Define your string:
string s = "aaaaaaabbbbbbccccccc";
And the rest is easy enough:
foreach(var rule in rules)
{
s = s.Replace(rule.Key, rule.Value);
}
This will try to perform a replace for every rule that you've defined. If it finds rules that are applicable; then it will replace the values.
Note
This code assumes that your replace values do not collide. If you do want to avoid collisions, you will have to check if substrings of all defined rules exist (Contains()) before actually replacing a value.
I have a really hard time understanding you requierment, but here is a solution may you tell me if this is even close to what you want?
private static string WeirdArrangement (string input)
{
//string input = "aabbcc[aczç_eyvur]";
string validChars = "abc";
string pattern = "abc"; // Must be a composition of all valid char
var invalidChars = input.Where(c => !validChars.Contains(c));
var validOccurences = input.Where(c => validChars.Contains(c))
.GroupBy(c => c)
.Select(c => new { Char = c.Key, Count = c.Count() });
var minPattern = validOccurences.Min(o => o.Count);
// Build time
StringBuilder builder = new StringBuilder();
//new StringBuilder(pattern.Length * minPattern + invalidChars.Count() + leftoverCount);
// X time partern
for (int i = 0; i < minPattern; i++) builder.Append(pattern);
//Rest of the validOccurences
foreach (var charOccurency in validOccurences)
{
for (int i = minPattern; i < charOccurency.Count; i++) builder.Append(charOccurency.Char);
}
//Invalid char
foreach (var invalidChar in invalidChars)
{
builder.Append(invalidChar);
};
return builder.ToString();
}

Evaluating inline C# code from post content

I have been around in circles with this one and need some help. I have a method that evaluates code, so if I pass this Eval("DateTime.Now.Year - 1986") it returns 29, its working great and this means I can have inline code in my posts that dynamically evaluate at runtime (this might present some security concerns, but that for some other time), here's the example string I am trying to work with: string inStr = "this year is [EVAL]DateTime.Now.Year[/EVAL] and it has been [EVAL]DateTime.Now.Year - 1986[/EVAL] years since 1986"; I need a regex that will replace all [EVAL] instances and return the full text with the evaluated results. Anyone?
You want a Regex, you can have a regex...
string inStr = "this year is [EVAL]DateTime.Now.Year[/EVAL] and it has been [EVAL]DateTime.Now.Year - 1986[/EVAL] years since 1986";
var rx = new Regex(#"(\[EVAL\])(.*?)(\[/EVAL])");
string outStr = rx.Replace(inStr, RegexReplacer);
with
public static string RegexReplacer(Match match)
{
return Eval(match.Groups[2].Value);
}
or depending on the return type of Eval:
public static string RegexReplacer(Match match)
{
object obj = Eval(match.Groups[2].Value);
return obj != null ? obj.ToString() : string.Empty;
}
The capture group #2 is the (.*?). Note the use of the lazy quantifier .*?, because otherwise the capture would be [EVAL]DateTime.Now.Year[/EVAL] and it has been [EVAL]DateTime.Now.Year - 1986[/EVAL]

Insert spaces into string using string.format

I've been using C# String.Format for formatting numbers before like this (in this example I simply want to insert a space):
String.Format("{0:### ###}", 123456);
output:
"123 456"
In this particular case, the number is a string. My first thought was to simply parse it to a number, but it makes no sense in the context, and there must be a prettier way.
Following does not work, as ## looks for numbers
String.Format("{0:### ###}", "123456");
output:
"123456"
What is the string equivalent to # when formatting? The awesomeness of String.Format is still fairly new to me.
You have to parse the string to a number first.
int number = int.Parse("123456");
String.Format("{0:### ###}", number);
of course you could also use string methods but that's not as reliable and less safe:
string strNumber = "123456";
String.Format("{0} {1}", strNumber.Remove(3), strNumber.Substring(3));
As Heinzi pointed out, you can not have format specifier for string arguments.
So, instead of String.Format, you may use following:
string myNum="123456";
myNum=myNum.Insert(3," ");
Not very beautiful, and the extra work might outweigh the gains, but if the input is a string on that format, you could do:
var str = "123456";
var result = String.Format("{0} {1}", str.Substring(0,3), str.Substring(3));
string is not a IFormattable
Console.WriteLine("123456" is IFormattable); // False
Console.WriteLine(21321 is IFormattable); // True
No point to supply a format if the argument is not IFormattable only way is to convert your string to int or long
We're doing string manipulation, so we could always use a regex.
Adapted slightly from here:
class MyClass
{
static void Main(string[] args)
{
string sInput, sRegex;
// The string to search.
sInput = "123456789";
// The regular expression.
sRegex = "[0-9][0-9][0-9]";
Regex r = new Regex(sRegex);
MyClass c = new MyClass();
// Assign the replace method to the MatchEvaluator delegate.
MatchEvaluator myEvaluator = new MatchEvaluator(c.ReplaceNums);
// Replace matched characters using the delegate method.
sInput = r.Replace(sInput, myEvaluator);
// Write out the modified string.
Console.WriteLine(sInput);
}
public string ReplaceNums(Match m)
// Replace each Regex match with match + " "
{
return m.ToString()+" ";
}
}
How's that?
It's been ages since I used C# and I can't test, but this may work as a one-liner which may be "neater" if you only need it once:
sInput = Regex("[0-9][0-9][0-9]").Replace(sInput,MatchEvaluator(Match m => m.ToString()+" "));
There is no way to do what you want unless you parse the string first.
Based on your comments, you only really need a simple formatting so you are better off just implementing a small helper method and thats it. (IMHO it's not really a good idea to parse the string if it isn't logically a number; you can't really be sure that in the future the input string might not be a number at all.
I'd go for something similar to:
public static string Group(this string s, int groupSize = 3, char groupSeparator = ' ')
{
var formattedIdentifierBuilder = new StringBuilder();
for (int i = 0; i < s.Length; i++)
{
if (i != 0 && (s.Length - i) % groupSize == 0)
{
formattedIdentifierBuilder.Append(groupSeparator);
}
formattedIdentifierBuilder.Append(s[i]);
}
return formattedIdentifierBuilder.ToString();
}
EDIT: Generalized to generic grouping size and group separator.
The problem is that # is a Digit placeholder and it is specific to numeric formatting only. Hence, you can't use this on strings.
Either parse the string to a numeric, so the formatting rules apply, or use other methods to split the string in two.
string.Format("{0:### ###}", int.Parse("123456"));

Replacing characters in a string with another string

So what I am trying to do is as follows :
example of a string is A4PC
I am trying to replace for example any occurance of "A" with "[A4]" so I would get and similar any occurance of "4" with "[A4]"
"[A4][A4]PC"
I tried doing a normal Replace on the string but found out I got
"[A[A4]]PC"
string badWordAllVariants =
restriction.Value.Replace("A", "[A4]").Replace("4", "[A4]")
since I have two A's in a row causing an issue.
So I was thinking it would be better rather than the replace on the string I need to do it on a character per character basis and then build up a string again.
Is there anyway in Linq or so to do something like this ?
You don't need any LINQ here - String.Replace works just fine:
string input = "AAPC";
string result = input.Replace("A", "[A4]"); // "[A4][A4]PC"
UPDATE: For your updated requirements I suggest to use regular expression replace
string input = "A4PC";
var result = Regex.Replace(input, "A|4", "[A4]"); // "[A4][A4]PC"
This works well for me:
string x = "AAPC";
string replace = x.Replace("A", "[A4]");
EDIT:
Based on the updated question, the issue is the second replacement. In order to replace multiple strings you will want to do this sequentially:
var original = "AAPC";
// add arbitrary room to allow for more new characters
StringBuilder resultString = new StringBuilder(original.Length + 10);
foreach (char currentChar in original.ToCharArray())
{
if (currentChar == 'A') resultString.Append("[A4]");
else if (currentChar == '4') resultString.Append("[A4]");
else resultString.Append(currentChar);
}
string result = resultString.ToString();
You can run this routine with any replacements you want to make (in this case the letters 'A' and '4' and it should work. If you would want to replace strings the code would be similar in structure but you would need to "look ahead" and probably use a for loop. Hopefully this helps!
By the way - you want to use a string builder here and not strings because strings are static which means space gets allocated every time you loop. (Not good!)
I think this should do the trick
string str = "AA4PC";
string result = Regex.Replace(str, #"(?<Before>[^A4]?)(?<Value>A|4)(?<After>[^A4]?)", (m) =>
{
string before = m.Groups["Before"].Value;
string after = m.Groups["After"].Value;
string value = m.Groups["Value"].Value;
if (before != "[" || after != "]")
{
return "[A4]";
}
return m.ToString();
});
It is going to replace A and 4 that hasn't been replaced yet for [A4].

RegEx - Please help in forming this RegEx

Can you please help me to write regular expression for this.
Name = "Windows Product for .Net"
Type = "Software Programming"
Quantity = "Pack of 3"
I want to do a match like this in c# for which I need RegEx.
If Name.contains(".Net") && (Type.Contains("Programming") || Type.Contains("Hardware")
{
// output will be a Match.
}
else
{
// no match.
}
The approach I want to take here is , specify regular expression for each condition and then apply logical operand && , logical grouping paranthesis and then logical operand ||.
I have come up with all regular expressions for these. How can I provide logical operands for each of them to execute in appropriate order?
string Name = "Windows Product for .Net";
string Type = "Software Programming";
string patternForName = ".*Net";
Regex rgxName = new Regex(patternForName);
Match matchName = rgx.Match(Name);
string patternForType = ".*Programming";
Regex rgxType = new Regex(patternForType);
Match matchType = rgx.Match(Type);
string patternForType1 = ".*Hardware";
Regex rgxType1 = new Regex(patternForType1);
Match matchType1 = rgx.Match(Type);
Please note - We are making it dynamic, in the sense the patterns , operands and regEx are coming from xml file. So that's why I do not want to write one big regEx for above.
First of all you don't need a leading .* in your expression unless you want the whole match (i.e. when working with matches). Just for a simple "is it there" you won't need it as the pattern might match any position.
Just use one regular expression for each field (i.e. one for Name, one for Type, one for Quantity:
string patternForName = "\\.Net"; // escaping the dot so it will match real dots only
string patternForType = "Programming|Hardware"; // | will result in "left side or right side"
string patternForQuantity = ".?"; // will match any string, even empty ones
To check everything:
bool match = rgxName.IsMatch(Name) && rgxType.IsMatch(Type) && rgx.IsMatch(Quantity);
You can make them dynamic without using regex. Using regex won't really save you any time or effort, since the code's going to be about the same size either way. Following your pattern above, you can do something like this:
var names = new[] { "Net", "Programming" };
var types = new[] { "Hardware" };
bool matchFound = true;
foreach (string n in names)
matchFound &= Name.Contains(n);
foreach (string t in types)
matchFound |= Type.Contains(t);
The above code assumes you want to match all of "names" and any of "types", but you can substitute any logic you want.
The real crux of your problem is these boolean combinations; regex won't help you with the logic for those, so you're better off using string.Contains unless the patterns you're looking for become much more variable. Regex is distracting you from your real goal here, in my opinion.
It sounds like you're asking how you should handle the logical part of the problem. If you're pulling it from an xml file, you could structure your file in the way you want to structure your logic.
for example, have And and Or groups:
<And>
<Name Match=".Net"/>
<Or>
<Type Match="Programming"/>
<Type Match="Hardware"/>
</Or>
</And>
Create classes for each of these types. For brevity, I didnt define the classes with properties or create constructors, but you can fill them out however you want:
class YourType
{
string Name;
string Type;
string Quantity;
}
abstract class Test
{
public abstract bool RunTest(YourType o);
}
class AndTest : Test
{
public List<Test> Children;
public bool RunTest(YourType o)
{
foreach (var test in Children)
{
if (!test.RunTest(o)) return false;
}
return true;
}
}
class OrTest : Test
{
public List<Test> Children;
public bool RunTest(YourType o)
{
foreach (var test in Children)
{
if (test.RunTest(o)) return true;
}
return false;
}
}
class NameTest : Test
{
public string Match;
public bool RunTest(YourType o)
{
return o.Name.Contains(Match);
}
}
class TypeTest : Test
{
public string Match;
public bool RunTest(YourType o)
{
return o.Type.Contains(Match);
}
}
Build the class structure from the xml file and just call RunTest from the top level Test. This way you can do any type of logic youd like. I just used Contains instead of a Regex for ease of the example, but you can easily replace the string match with a regex match.
if (rgxName.IsMatch(Name) && (rgxType.IsMatch(Type) || rgxType1.IsMatch(Type))
{
...
}
In .NET, Regex.Match matches anywhere in the string, so you don't need the any-characters (.*) prefix on your pattern. So, to check for ".NET", it would simply be:
Regex regexName = new Regex(#"\.NET", RegexOptions.IgnoreCase);
// IsMatch returns true/false, Match returns a Match object
bool nameMatches = regexName.IsMatch(name);
Your patterns for Programming and Hardware would just be
new Regex(#"Programming", RegexOptions.IgnoreCase) // Or leave out IgnoreCase if you're case-sensitive
new Regex(#"Hardware")
If you have a list of Name patterns and a list of type patterns, you could do something similar to this:
bool nameIsMatch = false;
bool typeIsMatch = false;
foreach (string namePattern in namePatterns)
{
nameIsMatch = nameIsMatch || Regex.IsMatch(nameString, namePattern);
}
foreach (string typePattern in typePatterns)
{
typeIsMatch = typeIsMatch || Regex.IsMatch(typeString, typePattern);
}
if (nameIsMatch && typeIsMatch)
{
// Whatever you want to do
}
patternForName = ".Net"
patternForType = "Programming"
patternForType1 = "Hardware"
You might find The Regex Coach to be useful.

Categories

Resources