named String.Format, is it possible? - c#

Instead of using {0} {1}, etc. I want to use {title} instead. Then fill that data in somehow (below I used a Dictionary). This code is invalid and throws an exception. I wanted to know if i can do something similar to what i want. Using {0 .. N} is not a problem. I was just curious.
Dictionary<string, string> d = new Dictionary<string, string>();
d["a"] = "he";
d["ba"] = "llo";
d["lol"] = "world";
string a = string.Format("{a}{ba}{lol}", d);

No, but this extension method will do it
static string FormatFromDictionary(this string formatString, Dictionary<string, string> valueDict)
{
int i = 0;
StringBuilder newFormatString = new StringBuilder(formatString);
Dictionary<string, int> keyToInt = new Dictionary<string,int>();
foreach (var tuple in valueDict)
{
newFormatString = newFormatString.Replace("{" + tuple.Key + "}", "{" + i.ToString() + "}");
keyToInt.Add(tuple.Key, i);
i++;
}
return String.Format(newFormatString.ToString(), valueDict.OrderBy(x => keyToInt[x.Key]).Select(x => x.Value).ToArray());
}

Check this one, it supports formating:
public static string StringFormat(string format, IDictionary<string, object> values)
{
var matches = Regex.Matches(format, #"\{(.+?)\}");
List<string> words = (from Match matche in matches select matche.Groups[1].Value).ToList();
return words.Aggregate(
format,
(current, key) =>
{
int colonIndex = key.IndexOf(':');
return current.Replace(
"{" + key + "}",
colonIndex > 0
? string.Format("{0:" + key.Substring(colonIndex + 1) + "}", values[key.Substring(0, colonIndex)])
: values[key] == null ? string.Empty : values[key].ToString());
});
}
How to use:
string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var dictionary = new Dictionary<string, object>
{
{ "foo", 123 },
{ "bar", true },
{ "baz", "this is a test" },
{ "qux", 123.45 },
{ "fizzle", DateTime.Now }
};
StringFormat(format, dictionary)

You can implement your own:
public static string StringFormat(string format, IDictionary<string, string> values)
{
foreach(var p in values)
format = format.Replace("{" + p.Key + "}", p.Value);
return format;
}

Phil Haack discussed several methods of doing this on his blog a while back: http://haacked.com/archive/2009/01/14/named-formats-redux.aspx. I've used the "Hanselformat" version on two projects with no complaints.

It's possible now
With Interpolated Strings of C# 6.0 you can do this:
string name = "John";
string message = $"Hi {name}!";
//"Hi John!"

static public class StringFormat
{
static private char[] separator = new char[] { ':' };
static private Regex findParameters = new Regex(
"\\{(?<param>.*?)\\}",
RegexOptions.Compiled | RegexOptions.Singleline);
static string FormatNamed(
this string format,
Dictionary<string, object> args)
{
return findParameters.Replace(
format,
delegate(Match match)
{
string[] param = match.Groups["param"].Value.Split(separator, 2);
object value;
if (!args.TryGetValue(param[0], out value))
value = match.Value;
if ((param.Length == 2) && (param[1].Length != 0))
return string.Format(
CultureInfo.CurrentCulture,
"{0:" + param[1] + "}",
value);
else
return value.ToString();
});
}
}
A little more involved than the other extension method, but this should also allow non-string values and formatting patterns used on them, so in your original example:
Dictionary<string, object> d = new Dictionary<string, object>();
d["a"] = DateTime.Now;
string a = string.FormatNamed("{a:yyyyMMdd-HHmmss}", d);
Will also work...

Since C# 6 released you are able to use String Interpolation feature
Code that solves your question:
string a = $"{d["a"]}{d["ba"]}{d["lol"]}";

Why a Dictionary? It's unnecessary and overly complicated. A simple 2 dimensional array of name/value pairs would work just as well:
public static string Format(this string formatString, string[,] nameValuePairs)
{
if (nameValuePairs.GetLength(1) != 2)
{
throw new ArgumentException("Name value pairs array must be [N,2]", nameof(nameValuePairs));
}
StringBuilder newFormat = new StringBuilder(formatString);
int count = nameValuePairs.GetLength(0);
object[] values = new object[count];
for (var index = 0; index < count; index++)
{
newFormat = newFormat.Replace(string.Concat("{", nameValuePairs[index,0], "}"), string.Concat("{", index.ToString(), "}"));
values[index] = nameValuePairs[index,1];
}
return string.Format(newFormat.ToString(), values);
}
Call the above with:
string format = "{foo} = {bar} (really, it's {bar})";
string formatted = format.Format(new[,] { { "foo", "Dictionary" }, { "bar", "unnecessary" } });
Results in: "Dictionary = unnecessary (really, it's unnecessary)"

public static string StringFormat(this string format, IDictionary<string, object> values)
{
return Regex.Matches(format, #"\{(?!\{)(.+?)\}")
.Select(m => m.Groups[1].Value)
.Aggregate(format, (current, key) =>
{
string[] splits = key.Split(":");
string replacement = splits.Length > 1
? string.Format($"{{0:{splits[1]}}}", values[splits[0]])
: values[key].ToString();
return Regex.Replace(current, "(.|^)("+ Regex.Escape($"{{{key}}}")+")(.|$)",
m => m.Groups[1].ToString() == "{" && m.Groups[3].ToString() == "}"
? m.Groups[2].ToString()
: m.Groups[1] + replacement + m.Groups[3]
);
});
}
This is similar to another answer, but it considers escaping with {{text}}.

I took #LPCRoy's answer and refactored for generic dictionary types, added parameter validation, it only replaces fields it can find, and attempts to solve the "double curly brace" issue by escaping them first, then replacing them at the end with single braces in the same way that string.Format() does.
public static string FormatFromDictionary<K, T>(this string formatString, IDictionary<K, T> valueDict)
{
if (string.IsNullOrWhiteSpace(formatString)) return formatString;
if (valueDict == null || !valueDict.Any()) return formatString;
bool escapedDoubleCurlyBraces = false;
if (formatString.Contains("{{") || formatString.Contains("}}"))
{
formatString = formatString.Replace("{{", "\\{\\{").Replace("}}", "\\}\\}");
escapedDoubleCurlyBraces = true;
}
int i = 0;
StringBuilder newFormatString = new StringBuilder(formatString);
Dictionary<K, int> keyToInt = new Dictionary<K, int>();
string field;
//StringBuilder.Replace() is faster than string.Replace().
foreach (var kvp in valueDict)
{
field = "{" + kvp.Key.ToString() + "}";
if (formatString.Contains(field))
{
newFormatString = newFormatString.Replace(field, "{" + i.ToString() + "}");
keyToInt.Add(kvp.Key, i);
i++;
}
}
//Any replacements to make?
if (keyToInt.Any())
{
formatString = string.Format(
newFormatString.ToString(),
keyToInt.OrderBy(kvp => kvp.Value).Select(kvp => (object)valueDict[kvp.Key]).ToArray()
);
}
if (escapedDoubleCurlyBraces)
{
//Converts "{{" and "}}" to single "{" and "}" for the final result just like string.format().
formatString = formatString.Replace("\\{\\{", "{").Replace("\\}\\}", "}");
}
return formatString;
}

Here is a nice solution that is very useful when formatting emails: http://www.c-sharpcorner.com/UploadFile/e4ff85/string-replacement-with-named-string-placeholders/
Edited:
public static class StringExtension
{
public static string Format( this string str, params Expression<Func<string,object>>[] args)
{
var parameters = args.ToDictionary( e=>string.Format("{{{0}}}",e.Parameters[0].Name), e=>e.Compile()(e.Parameters[0].Name));
var sb = new StringBuilder(str);
foreach(var kv in parameters)
{
sb.Replace( kv.Key, kv.Value != null ? kv.Value.ToString() : "");
}
return sb.ToString();
}
}
Example usage:
public string PopulateString(string emailBody)
{
User person = _db.GetCurrentUser();
string firstName = person.FirstName; // Peter
string lastName = person.LastName; // Pan
return StringExtension.Format(emailBody.Format(
firstname => firstName,
lastname => lastName
));
}

(your Dictionary + foreach + string.Replace) wrapped in a sub-routine or extension method?
Obviously unoptimized, but...

Related

Convert String To camelCase from TitleCase C#

I have a string that I converted to a TextInfo.ToTitleCase and removed the underscores and joined the string together. Now I need to change the first and only the first character in the string to lower case and for some reason, I can not figure out how to accomplish it.
class Program
{
static void Main(string[] args)
{
string functionName = "zebulans_nightmare";
TextInfo txtInfo = new CultureInfo("en-us", false).TextInfo;
functionName = txtInfo.ToTitleCase(functionName).Replace('_', ' ').Replace(" ", String.Empty);
Console.Out.WriteLine(functionName);
Console.ReadLine();
}
}
Results: ZebulansNightmare
Desired Results: zebulansNightmare
UPDATE:
class Program
{
static void Main(string[] args)
{
string functionName = "zebulans_nightmare";
TextInfo txtInfo = new CultureInfo("en-us", false).TextInfo;
functionName = txtInfo.ToTitleCase(functionName).Replace("_", string.Empty).Replace(" ", string.Empty);
functionName = $"{functionName.First().ToString().ToLowerInvariant()}{functionName.Substring(1)}";
Console.Out.WriteLine(functionName);
Console.ReadLine();
}
}
Produces the desired output.
You just need to lower the first char in the array. See this answer
Char.ToLowerInvariant(name[0]) + name.Substring(1)
As a side note, seeing as you are removing spaces you can replace the underscore with an empty string.
.Replace("_", string.Empty)
If you're using .NET Core 3 or .NET 5, you can call:
System.Text.Json.JsonNamingPolicy.CamelCase.ConvertName(someString)
Then you'll definitely get the same results as ASP.NET's own JSON serializer.
Implemented Bronumski's answer in an extension method (without replacing underscores).
public static class StringExtension
{
public static string ToCamelCase(this string str)
{
if(!string.IsNullOrEmpty(str) && str.Length > 1)
{
return char.ToLowerInvariant(str[0]) + str.Substring(1);
}
return str.ToLowerInvariant();
}
}
//Or
public static class StringExtension
{
public static string ToCamelCase(this string str) =>
string.IsNullOrEmpty(str) || str.Length < 2
? str.ToLowerInvariant()
: char.ToLowerInvariant(str[0]) + str.Substring(1);
}
and to use it:
string input = "ZebulansNightmare";
string output = input.ToCamelCase();
Here is my code, in case it is useful to anyone
// This converts to camel case
// Location_ID => locationId, and testLEFTSide => testLeftSide
static string CamelCase(string s)
{
var x = s.Replace("_", "");
if (x.Length == 0) return "null";
x = Regex.Replace(x, "([A-Z])([A-Z]+)($|[A-Z])",
m => m.Groups[1].Value + m.Groups[2].Value.ToLower() + m.Groups[3].Value);
return char.ToLower(x[0]) + x.Substring(1);
}
If you prefer Pascal-case use:
static string PascalCase(string s)
{
var x = CamelCase(s);
return char.ToUpper(x[0]) + x.Substring(1);
}
The following code works with acronyms as well. If it is the first word it converts the acronym to lower case (e.g., VATReturn to vatReturn), and otherwise leaves it as it is (e.g., ExcludedVAT to excludedVAT).
name = Regex.Replace(name, #"([A-Z])([A-Z]+|[a-z0-9_]+)($|[A-Z]\w*)",
m =>
{
return m.Groups[1].Value.ToLower() + m.Groups[2].Value.ToLower() + m.Groups[3].Value;
});
Example 01
public static string ToCamelCase(this string text)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text);
}
Example 02
public static string ToCamelCase(this string text)
{
return string.Join(" ", text
.Split()
.Select(i => char.ToUpper(i[0]) + i.Substring(1)));
}
Example 03
public static string ToCamelCase(this string text)
{
char[] a = text.ToLower().ToCharArray();
for (int i = 0; i < a.Count(); i++)
{
a[i] = i == 0 || a[i - 1] == ' ' ? char.ToUpper(a[i]) : a[i];
}
return new string(a);
}
Adapted from Leonardo's answer:
static string PascalCase(string str) {
TextInfo cultInfo = new CultureInfo("en-US", false).TextInfo;
str = Regex.Replace(str, "([A-Z]+)", " $1");
str = cultInfo.ToTitleCase(str);
str = str.Replace(" ", "");
return str;
}
Converts to PascalCase by first adding a space before any group of capitals, and then converting to title case before removing all the spaces.
Here's my code, includes lowering all upper prefixes:
public static class StringExtensions
{
public static string ToCamelCase(this string str)
{
bool hasValue = !string.IsNullOrEmpty(str);
// doesn't have a value or already a camelCased word
if (!hasValue || (hasValue && Char.IsLower(str[0])))
{
return str;
}
string finalStr = "";
int len = str.Length;
int idx = 0;
char nextChar = str[idx];
while (Char.IsUpper(nextChar))
{
finalStr += char.ToLowerInvariant(nextChar);
if (len - 1 == idx)
{
// end of string
break;
}
nextChar = str[++idx];
}
// if not end of string
if (idx != len - 1)
{
finalStr += str.Substring(idx);
}
return finalStr;
}
}
Use it like this:
string camelCasedDob = "DOB".ToCamelCase();
If you are Ok with the Newtonsoft.JSON dependency, the following string extension method will help. The advantage of this approach is the serialization will work on par with standard WebAPI model binding serialization with high accuracy.
public static class StringExtensions
{
private class CamelCasingHelper : CamelCaseNamingStrategy
{
private CamelCasingHelper(){}
private static CamelCasingHelper helper =new CamelCasingHelper();
public static string ToCamelCase(string stringToBeConverted)
{
return helper.ResolvePropertyName(stringToBeConverted);
}
}
public static string ToCamelCase(this string str)
{
return CamelCasingHelper.ToCamelCase(str);
}
}
Here is the working fiddle
https://dotnetfiddle.net/pug8pP
In .Net 6 and above
public static class CamelCaseExtension
{
public static string ToCamelCase(this string str) =>
char.ToLowerInvariant(str[0]) + str[1..];
}
public static string CamelCase(this string str)
{
TextInfo cultInfo = new CultureInfo("en-US", false).TextInfo;
str = cultInfo.ToTitleCase(str);
str = str.Replace(" ", "");
return str;
}
This should work using System.Globalization
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
JsonConvert.SerializeObject(object, camelCaseFormatter));
Strings are immutable, but we can use unsafe code to make it mutable though.
The string.Copy insured that the original string stays as is.
In order for these codes to run you have to allow unsafe code in your project.
public static unsafe string ToCamelCase(this string value)
{
if (value == null || value.Length == 0)
{
return value;
}
string result = string.Copy(value);
fixed (char* chr = result)
{
char valueChar = *chr;
*chr = char.ToLowerInvariant(valueChar);
}
return result;
}
This version modifies the original string, instead of returning a modified copy. This will be annoying though and totally uncommon. So make sure the XML comments are warning users about that.
public static unsafe void ToCamelCase(this string value)
{
if (value == null || value.Length == 0)
{
return value;
}
fixed (char* chr = value)
{
char valueChar = *chr;
*chr = char.ToLowerInvariant(valueChar);
}
return value;
}
Why use unsafe code though? Short answer... It's super fast.
Here's my code which is pretty simple. My major objective was to ensure that camel-casing was compatible with what ASP.NET serializes objects to, which the above examples don't guarantee.
public static class StringExtensions
{
public static string ToCamelCase(this string name)
{
var sb = new StringBuilder();
var i = 0;
// While we encounter upper case characters (except for the last), convert to lowercase.
while (i < name.Length - 1 && char.IsUpper(name[i + 1]))
{
sb.Append(char.ToLowerInvariant(name[i]));
i++;
}
// Copy the rest of the characters as is, except if we're still on the first character - which is always lowercase.
while (i < name.Length)
{
sb.Append(i == 0 ? char.ToLowerInvariant(name[i]) : name[i]);
i++;
}
return sb.ToString();
}
}
/// <summary>
/// Gets the camel case from snake case.
/// </summary>
/// <param name="snakeCase">The snake case.</param>
/// <returns></returns>
private string GetCamelCaseFromSnakeCase(string snakeCase)
{
string camelCase = string.Empty;
if(!string.IsNullOrEmpty(snakeCase))
{
string[] words = snakeCase.Split('_');
foreach (var word in words)
{
camelCase = string.Concat(camelCase, Char.ToUpperInvariant(word[0]) + word.Substring(1));
}
// making first character small case
camelCase = Char.ToLowerInvariant(camelCase[0]) + camelCase.Substring(1);
}
return camelCase;
}
I use This method to convert the string with separated by "_" to Camel Case
public static string ToCamelCase(string? s)
{
var nameArr = s?.ToLower().Split("_");
var str = "";
foreach (var name in nameArr.Select((value, i) => new { value, i }))
{
if(name.i >= 1)
{
str += string.Concat(name.value[0].ToString().ToUpper(), name.value.AsSpan(1));
}
else
{
str += name.value ;
}
}
return str;
}
u can change the separated by "_" with any other you want.
Simple and easy in build c#
using System;
using System.Globalization;
public class SamplesTextInfo {
public static void Main() {
// Defines the string with mixed casing.
string myString = "wAr aNd pEaCe";
// Creates a TextInfo based on the "en-US" culture.
TextInfo myTI = new CultureInfo("en-US",false).TextInfo;
// Changes a string to lowercase.
Console.WriteLine( "\"{0}\" to lowercase: {1}", myString, myTI.ToLower( myString ) );
// Changes a string to uppercase.
Console.WriteLine( "\"{0}\" to uppercase: {1}", myString, myTI.ToUpper( myString ) );
// Changes a string to titlecase.
Console.WriteLine( "\"{0}\" to titlecase: {1}", myString, myTI.ToTitleCase( myString ) );
}
}
/*
This code produces the following output.
"wAr aNd pEaCe" to lowercase: war and peace
"wAr aNd pEaCe" to uppercase: WAR AND PEACE
"wAr aNd pEaCe" to titlecase: War And Peace
*/
public static class StringExtension
{
public static string ToCamelCase(this string str)
{
return string.Join(" ", str
.Split()
.Select(i => char.ToUpper(i[0]) + i.Substring(1).ToLower()));
}
}
I had the same issue with titleCase so I just created one, hope this helps this is an extension method.
public static string ToCamelCase(this string text)
{
if (string.IsNullOrEmpty(text))
return text;
var separators = new[] { '_', ' ' };
var arr = text
.Split(separators)
.Where(word => !string.IsNullOrWhiteSpace(word));
var camelCaseArr = arr
.Select((word, i) =>
{
if (i == 0)
return word.ToLower();
var characterArr = word.ToCharArray()
.Select((character, characterIndex) => characterIndex == 0
? character.ToString().ToUpper()
: character.ToString().ToLower());
return string.Join("", characterArr);
});
return string.Join("", camelCaseArr);
}

Cleanup String: Replace consecutive nonalphanum Character with single seperator

I have a string, that need to be formatted:
keep alphanumeric letters
replace one or more non-aplhanum characters with a single separator
I came up with this:
string Format( string str , string separator )
{
if( string.IsNullOrEmpty( str ) )
return string.Empty;
var words = new List<string>();
var sb = new StringBuilder();
foreach( var c in str.ToCharArray() )
{
if( char.IsLetterOrDigit( c ) )
{
sb.Append( c );
}
else if( sb.Length > 0 )
{
words.Add( sb.ToString() );
sb.Clear();
}
}
if( sb.Any() )
words.Add( sb.ToString() );
return string.Join( seperator , words );
}
Is there a better/more-linq-like/shorter/more performant solution (without using regex) than this?
You could go to the "low level" and use the fact that a string is an IEnumerable<char> to use it's GetEnumerator
string Format(string str, string separator)
{
var builder = new StringBuilder (str.Length);
using (var e = str.GetEnumerator ())
{
while (e.MoveNext ())
{
bool hasMoved = true;
if (!char.IsLetterOrDigit (e.Current))
{
while ((hasMoved = e.MoveNext ()) && !char.IsLetterOrDigit (e.Current))
;
builder.Append (separator);
}
if (hasMoved)
builder.Append (e.Current);
}
}
return builder.ToString ();
}
Just in case here is a regex version too
private static readonly Regex rgx = new Regex(#"[^\w-[_]]+", RegexOptions.Compiled);
string Format (string str, string separator)
{
return rgx.Replace (str, separator);
}
Addendum regarding OP's comment about linq one-liner :
That's possible but hardly "easy to understand"
Using anonymous type
string Format (string str, string separator)
{
return str.Aggregate (new { builder = new StringBuilder (str.Length), prevDiscarded = false }, (state, ch) => char.IsLetterOrDigit (ch) ? new { builder = (state.prevDiscarded ? state.builder.Append (separator) : state.builder).Append (ch), prevDiscarded = false } : new { state.builder, prevDiscarded = true }, state => (state.prevDiscarded ? state.builder.Append (separator) : state.builder).ToString ());
}
Same thing using a Tuple instead
string Format (string str, string separator)
{
return str.Aggregate (Tuple.Create (new StringBuilder (str.Length), false), (state, ch) => char.IsLetterOrDigit (ch) ? Tuple.Create ((state.Item2 ? state.Item1.Append (separator) : state.Item1).Append (ch), false) : Tuple.Create (state.Item1, true), state => (state.Item2 ? state.Item1.Append (separator) : state.Item1).ToString ());
}
And with Tuple we can make some helpers to "ease" (so to speak) readability [although it's technically not a one-liner anymore]
//top of file
using State = System.Tuple<System.Text.StringBuilder, bool>;
string Format (string str, string separator)
{
var initialState = Tuple.Create (new StringBuilder (str.Length), false);
Func<State, StringBuilder> addSeparatorIfPrevDiscarded = state => state.Item2 ? state.Item1.Append (separator) : state.Item1;
Func<State, char, State> aggregator = (state, ch) => char.IsLetterOrDigit (ch) ? Tuple.Create (addSeparatorIfPrevDiscarded (state).Append (ch), false) : Tuple.Create (state.Item1, true);
Func<State, string> resultSelector = state => addSeparatorIfPrevDiscarded (state).ToString ();
return str.Aggregate (initialState, aggregator, resultSelector);
}
What makes it complex is that (IMO) Linq* is not really well suited when "item output" depend on previous (or next) items in the same collection
* Well Linq doesn't have a problem with that but it's quickly a lot of noise with Func and anonymous types/tuple syntax (maybe C#7.0 will change that a bit)
In the same flavor, one could also embrace side-effects which allow to have only the bool as state
string Format (string str, string separator)
{
var builder = new StringBuilder (str.Length);
Action<bool> addSeparatorIfPrevDiscarded = prevDiscarded => { if (prevDiscarded) builder.Append (separator); };
Func<bool, char, bool> aggregator = (prevDiscarded, ch) => {
if (char.IsLetterOrDigit (ch)) {
addSeparatorIfPrevDiscarded (prevDiscarded);
builder.Append (ch);
return false;
}
return true;
};
addSeparatorIfPrevDiscarded (str.Aggregate (false, aggregator));
return builder.ToString ();
}
Something like this to avoid the need for a List<string> and the use of string.Join. Also it will compile.
string Format(string str, char seperator)
{
if (string.IsNullOrEmpty(str))
return string.Empty;
var sb = new StringBuilder();
bool previousWasNonAlphaNum = false;
foreach (var c in str)
{
if (char.IsLetterOrDigit(c))
{
if (previousWasNonAlphaNum && sb.Count > 0)
sb.Append(seperator);
sb.Append(c);
}
previousWasNonAlphaNum = !char.IsLetterOrDigit(c);
}
return sb.ToString();
}
Try this, it will work
string Format(string str, string separator)
{
var delimiter = char.Parse(separator);
var replaced = false;
var cArray = str.Select(c =>
{
if (!char.IsLetterOrDigit(c) & !replaced)
{
replaced = true;
return delimiter;
}
else if (char.IsLetterOrDigit(c))
{
replaced = false;
}
else
{
return ' ';
}
return c;
}).ToArray();
return new string(cArray).Replace(" ","");
}
or you can try this one below
string Format(string str, string separator)
{
var delimiter = char.Parse(separator);
var cArray = str.Select(c => !char.IsLetterOrDigit(c) ? delimiter : c).ToArray();
var wlist = new string(cArray).Split(new string[]{separator}, StringSplitOptions.RemoveEmptyEntries);
return string.Join(separator, wlist);
}

C# simple way to build params list for variable number of params

I have a method that takes this parameter
params string[] additionalParameters
I am building it like this:
qsParams = new string[] {"k=" + k, "l=" + l, "o=" + o, "s=" + s, "t=" + t, "h=" + h, "exp=" + exp };
These are url params. The problem is I only want to add parameters where the variables are not null or empty.
I can do this kind of thing:
if (string.IsNullOrEmpty(k))
{
qsParams = new string[] {"l=" + l, "o=" + o, "s=" + s, "t=" + t, "h=" + h, "exp=" + exp };
}
But that's going to get complicated and ugly trying to handle all the different permutations of empty variables.
Can anyone suggest a simple way to add the params if there are not null? Perhaps a list that I can convert to a params []string?
public static void Main(string[] args)
{
List<string> parameters = new List<string>();
string k = "a";
string l = null;
AddParam("k", k, parameters);
AddParam("l", l, parameters);
string[] result = parameters.ToArray();
}
public static void AddParam(string paramName, string paramValue, List<string> parameters)
{
if (string.IsNullOrEmpty(paramValue))
return;
parameters.Add(paramName + "=" + paramValue);
}
You can try something like this.
You can write a method that returns null if your variable has no value:
private string GetListValue(string prefix, string value) {
if (String.IsNullOrEmpty(value)) {
return null;
}
else {
return prefix + value;
}
}
You can define your raw list with this method (only using 2 values):
string[] rawList = { GetListValue("k=", k), GetListValue("l=", l) };
Then clean the list with LINQ:
string[] cleanValues = rawValues.Where(v => v != null).ToArray();
If your params are always in right order:
List<string> qsParams = new List<string>();
string[] paramNames = new string[] { "k", "l", "o", "s", "t", "h", "exp" };
for (int i = 0; i < additionalParameters.Length; i++)
{
if (!string.IsNullOrEmpty(additionalParameters[i]))
{
qsParams.Add(string.Format("{0}={1}", paramNames[i], additionalParameters[i]));
}
}
Just create a dictionary with the key being the param and the value being well... the value.
Dictionary<string, string> Parameters = new Dictionary<string, string>();
Parameters.Add("k", "myKValue");
Parameters.Add("o", "myOValue");
string paramaterList = string.Join("&", Parameters.Select(x => $"{x.Key}={x.Value}"));
Only add values to the dictionary when they aren't null.

How can I speed this loop up? Is there a class for replacing multiple terms at at time?

The loop:
var pattern = _dict[key];
string before;
do
{
before = pattern;
foreach (var pair in _dict)
if (key != pair.Key)
pattern = pattern.Replace(string.Concat("{", pair.Key, "}"), string.Concat("(", pair.Value, ")"));
} while (pattern != before);
return pattern;
It just does a repeated find-and-replace on a bunch of keys. The dictionary is just <string,string>.
I can see 2 improvements to this.
Every time we do pattern.Replace it searches from the beginning of the string again. It would be better if when it hit the first {, it would just look through the list of keys for a match (perhaps using a binary search), and then replace the appropriate one.
The pattern != before bit is how I check if anything was replaced during that iteration. If the pattern.Replace function returned how many or if any replaces actually occured, I wouldn't need this.
However... I don't really want to write a big nasty thing class to do all that. This must be a fairly common scenario? Are there any existng solutions?
Full Class
Thanks to Elian Ebbing and ChrisWue.
class FlexDict : IEnumerable<KeyValuePair<string,string>>
{
private Dictionary<string, string> _dict = new Dictionary<string, string>();
private static readonly Regex _re = new Regex(#"{([_a-z][_a-z0-9-]*)}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public void Add(string key, string pattern)
{
_dict[key] = pattern;
}
public string Expand(string pattern)
{
pattern = _re.Replace(pattern, match =>
{
string key = match.Groups[1].Value;
if (_dict.ContainsKey(key))
return "(" + Expand(_dict[key]) + ")";
return match.Value;
});
return pattern;
}
public string this[string key]
{
get { return Expand(_dict[key]); }
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
foreach (var p in _dict)
yield return new KeyValuePair<string,string>(p.Key, this[p.Key]);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Example Usage
class Program
{
static void Main(string[] args)
{
var flex = new FlexDict
{
{"h", #"[0-9a-f]"},
{"nonascii", #"[\200-\377]"},
{"unicode", #"\\{h}{1,6}(\r\n|[ \t\r\n\f])?"},
{"escape", #"{unicode}|\\[^\r\n\f0-9a-f]"},
{"nmstart", #"[_a-z]|{nonascii}|{escape}"},
{"nmchar", #"[_a-z0-9-]|{nonascii}|{escape}"},
{"string1", #"""([^\n\r\f\\""]|\\{nl}|{escape})*"""},
{"string2", #"'([^\n\r\f\\']|\\{nl}|{escape})*'"},
{"badstring1", #"""([^\n\r\f\\""]|\\{nl}|{escape})*\\?"},
{"badstring2", #"'([^\n\r\f\\']|\\{nl}|{escape})*\\?"},
{"badcomment1", #"/\*[^*]*\*+([^/*][^*]*\*+)*"},
{"badcomment2", #"/\*[^*]*(\*+[^/*][^*]*)*"},
{"baduri1", #"url\({w}([!#$%&*-\[\]-~]|{nonascii}|{escape})*{w}"},
{"baduri2", #"url\({w}{string}{w}"},
{"baduri3", #"url\({w}{badstring}"},
{"comment", #"/\*[^*]*\*+([^/*][^*]*\*+)*/"},
{"ident", #"-?{nmstart}{nmchar}*"},
{"name", #"{nmchar}+"},
{"num", #"[0-9]+|[0-9]*\.[0-9]+"},
{"string", #"{string1}|{string2}"},
{"badstring", #"{badstring1}|{badstring2}"},
{"badcomment", #"{badcomment1}|{badcomment2}"},
{"baduri", #"{baduri1}|{baduri2}|{baduri3}"},
{"url", #"([!#$%&*-~]|{nonascii}|{escape})*"},
{"s", #"[ \t\r\n\f]+"},
{"w", #"{s}?"},
{"nl", #"\n|\r\n|\r|\f"},
{"A", #"a|\\0{0,4}(41|61)(\r\n|[ \t\r\n\f])?"},
{"C", #"c|\\0{0,4}(43|63)(\r\n|[ \t\r\n\f])?"},
{"D", #"d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?"},
{"E", #"e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?"},
{"G", #"g|\\0{0,4}(47|67)(\r\n|[ \t\r\n\f])?|\\g"},
{"H", #"h|\\0{0,4}(48|68)(\r\n|[ \t\r\n\f])?|\\h"},
{"I", #"i|\\0{0,4}(49|69)(\r\n|[ \t\r\n\f])?|\\i"},
{"K", #"k|\\0{0,4}(4b|6b)(\r\n|[ \t\r\n\f])?|\\k"},
{"L", #"l|\\0{0,4}(4c|6c)(\r\n|[ \t\r\n\f])?|\\l"},
{"M", #"m|\\0{0,4}(4d|6d)(\r\n|[ \t\r\n\f])?|\\m"},
{"N", #"n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n"},
{"O", #"o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o"},
{"P", #"p|\\0{0,4}(50|70)(\r\n|[ \t\r\n\f])?|\\p"},
{"R", #"r|\\0{0,4}(52|72)(\r\n|[ \t\r\n\f])?|\\r"},
{"S", #"s|\\0{0,4}(53|73)(\r\n|[ \t\r\n\f])?|\\s"},
{"T", #"t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t"},
{"U", #"u|\\0{0,4}(55|75)(\r\n|[ \t\r\n\f])?|\\u"},
{"X", #"x|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\x"},
{"Z", #"z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z"},
{"Z", #"z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z"},
{"CDO", #"<!--"},
{"CDC", #"-->"},
{"INCLUDES", #"~="},
{"DASHMATCH", #"\|="},
{"STRING", #"{string}"},
{"BAD_STRING", #"{badstring}"},
{"IDENT", #"{ident}"},
{"HASH", #"#{name}"},
{"IMPORT_SYM", #"#{I}{M}{P}{O}{R}{T}"},
{"PAGE_SYM", #"#{P}{A}{G}{E}"},
{"MEDIA_SYM", #"#{M}{E}{D}{I}{A}"},
{"CHARSET_SYM", #"#charset\b"},
{"IMPORTANT_SYM", #"!({w}|{comment})*{I}{M}{P}{O}{R}{T}{A}{N}{T}"},
{"EMS", #"{num}{E}{M}"},
{"EXS", #"{num}{E}{X}"},
{"LENGTH", #"{num}({P}{X}|{C}{M}|{M}{M}|{I}{N}|{P}{T}|{P}{C})"},
{"ANGLE", #"{num}({D}{E}{G}|{R}{A}{D}|{G}{R}{A}{D})"},
{"TIME", #"{num}({M}{S}|{S})"},
{"PERCENTAGE", #"{num}%"},
{"NUMBER", #"{num}"},
{"URI", #"{U}{R}{L}\({w}{string}{w}\)|{U}{R}{L}\({w}{url}{w}\)"},
{"BAD_URI", #"{baduri}"},
{"FUNCTION", #"{ident}\("},
};
var testStrings = new[] { #"""str""", #"'str'", "5", "5.", "5.0", "a", "alpha", "url(hello)",
"url(\"hello\")", "url(\"blah)", #"\g", #"/*comment*/", #"/**/", #"<!--", #"-->", #"~=",
"|=", #"#hash", "#import", "#page", "#media", "#charset", "!/*iehack*/important"};
foreach (var pair in flex)
{
Console.WriteLine("{0}\n\t{1}\n", pair.Key, pair.Value);
}
var sw = Stopwatch.StartNew();
foreach (var str in testStrings)
{
Console.WriteLine("{0} matches: ", str);
foreach (var pair in flex)
{
if (Regex.IsMatch(str, "^(" + pair.Value + ")$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
Console.WriteLine(" {0}", pair.Key);
}
}
Console.WriteLine("\nRan in {0} ms", sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
Purpose
For building complex regular expressions that may extend eachother. Namely, I'm trying to implement the css spec.
I think it would be faster if you look for any occurrences of {foo} using a regular expression, and then use a MatchEvaluator that replaces the {foo} if foo happens to be a key in the dictionary.
I have currently no visual studio here, but I guess this is functionally equivalent with your code example:
var pattern = _dict[key];
bool isChanged = false;
do
{
isChanged = false;
pattern = Regex.Replace(pattern, "{([^}]+)}", match => {
string matchKey = match.Groups[1].Value;
if (matchKey != key && _dict.ContainsKey(matchKey))
{
isChanged = true;
return "(" + _dict[matchKey] + ")";
}
return match.Value;
});
} while (isChanged);
Can I ask you why you need the do/while loop? Can the value of a key in the dictionary again contain {placeholders} that have to be replaced? Can you be sure you don't get stuck in an infinite loop where key "A" contains "Blahblah {B}" and key "B" contains "Blahblah {A}"?
Edit: further improvements would be:
Using a precompiled Regex.
Using recursion instead of a loop (see ChrisWue's comment).
Using _dict.TryGetValue(), as in Guffa's code.
You will end up with an O(n) algorithm where n is the size of the output, so you can't do much better than this.
You should be able to use a regular expression to find the matches. Then you can also make use of the fast lookup of the dictionary and not just use it as a list.
var pattern = _dict[key];
bool replaced = false;
do {
pattern = Regex.Replace(pattern, #"\{([^\}]+)\}", m => {
string k = m.Groups[1].Value;
string value;
if (k != key && _dict.TryGetValue(k, out value) {
replaced = true;
return "(" + value + ")";
} else {
return "{" + k + "}";
}
});
} while (replaced);
return pattern;
You can implement the following algorithm:
Search for { in source string
Copy everything upto { to StringBuilder
Find matching } (the search is done from last fond position)
Compare value between { and } to keys in your dictionary
If it matches copy to String builder ( + Value + )
Else copy from source string
If source string end is not reached go to step 1
Could you use PLINQ at all?
Something along the lines of:
var keys = dict.KeyCollection.Where(k => k != key);
bool replacementMade = keys.Any();
foreach(var k in keys.AsParallel(), () => {replacement code})

How to word by word iterate in string in C#?

I want to iterate over string as word by word.
If I have a string "incidentno and fintype or unitno", I would like to read every word one by one as "incidentno", "and", "fintype", "or", and "unitno".
foreach (string word in "incidentno and fintype or unitno".Split(' ')) {
...
}
var regex = new Regex(#"\b[\s,\.-:;]*");
var phrase = "incidentno and fintype or unitno";
var words = regex.Split(phrase).Where(x => !string.IsNullOrEmpty(x));
This works even if you have ".,; tabs and new lines" between your words.
Slightly twisted I know, but you could define an iterator block as an extension method on strings. e.g.
/// <summary>
/// Sweep over text
/// </summary>
/// <param name="Text"></param>
/// <returns></returns>
public static IEnumerable<string> WordList(this string Text)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(' ', cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
foreach (string word in "incidentno and fintype or unitno".WordList())
System.Console.WriteLine("'" + word + "'");
Which has the advantage of not creating a big array for long strings.
Use the Split method of the string class
string[] words = "incidentno and fintype or unitno".Split(" ");
This will split on spaces, so "words" will have [incidentno,and,fintype,or,unitno].
Assuming the words are always separated by a blank, you could use String.Split() to get an Array of your words.
There are multiple ways to accomplish this. Two of the most convenient methods (in my opinion) are:
Using string.Split() to create an array. I would probably use this method, because it is the most self-explanatory.
example:
string startingSentence = "incidentno and fintype or unitno";
string[] seperatedWords = startingSentence.Split(' ');
Alternatively, you could use (this is what I would use):
string[] seperatedWords = startingSentence.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
StringSplitOptions.RemoveEmptyEntries will remove any empty entries from your array that may occur due to extra whitespace and other minor problems.
Next - to process the words, you would use:
foreach (string word in seperatedWords)
{
//Do something
}
Or, you can use regular expressions to solve this problem, as Darin demonstrated (a copy is below).
example:
var regex = new Regex(#"\b[\s,\.-:;]*");
var phrase = "incidentno and fintype or unitno";
var words = regex.Split(phrase).Where(x => !string.IsNullOrEmpty(x));
For processing, you can use similar code to the first option.
foreach (string word in words)
{
//Do something
}
Of course, there are many ways to solve this problem, but I think that these two would be the simplest to implement and maintain. I would go with the first option (using string.Split()) just because regex can sometimes become quite confusing, while a split will function correctly most of the time.
When using split, what about checking for empty entries?
string sentence = "incidentno and fintype or unitno"
string[] words = sentence.Split(new char[] { ' ', ',' ,';','\t','\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
foreach (string word in words)
{
// Process
}
EDIT:
I can't comment so I'm posting here but this (posted above) works:
foreach (string word in "incidentno and fintype or unitno".Split(' '))
{
...
}
My understanding of foreach is that it first does a GetEnumerator() and the calles .MoveNext until false is returned. So the .Split won't be re-evaluated on each iteration
public static string[] MyTest(string inword, string regstr)
{
var regex = new Regex(regstr);
var phrase = "incidentno and fintype or unitno";
var words = regex.Split(phrase);
return words;
}
? MyTest("incidentno, and .fintype- or; :unitno",#"[^\w+]")
[0]: "incidentno"
[1]: "and"
[2]: "fintype"
[3]: "or"
[4]: "unitno"
I'd like to add some information to JDunkerley's awnser.
You can easily make this method more reliable if you give a string or char parameter to search for.
public static IEnumerable<string> WordList(this string Text,string Word)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(Word, cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
public static IEnumerable<string> WordList(this string Text, char c)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(c, cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
I write a string processor class.You can use it.
Example:
metaKeywords = bodyText.Process(prepositions).OrderByDescending().TakeTop().GetWords().AsString();
Class:
public static class StringProcessor
{
private static List<String> PrepositionList;
public static string ToNormalString(this string strText)
{
if (String.IsNullOrEmpty(strText)) return String.Empty;
char chNormalKaf = (char)1603;
char chNormalYah = (char)1610;
char chNonNormalKaf = (char)1705;
char chNonNormalYah = (char)1740;
string result = strText.Replace(chNonNormalKaf, chNormalKaf);
result = result.Replace(chNonNormalYah, chNormalYah);
return result;
}
public static List<KeyValuePair<String, Int32>> Process(this String bodyText,
List<String> blackListWords = null,
int minimumWordLength = 3,
char splitor = ' ',
bool perWordIsLowerCase = true)
{
string[] btArray = bodyText.ToNormalString().Split(splitor);
long numberOfWords = btArray.LongLength;
Dictionary<String, Int32> wordsDic = new Dictionary<String, Int32>(1);
foreach (string word in btArray)
{
if (word != null)
{
string lowerWord = word;
if (perWordIsLowerCase)
lowerWord = word.ToLower();
var normalWord = lowerWord.Replace(".", "").Replace("(", "").Replace(")", "")
.Replace("?", "").Replace("!", "").Replace(",", "")
.Replace("<br>", "").Replace(":", "").Replace(";", "")
.Replace("،", "").Replace("-", "").Replace("\n", "").Trim();
if ((normalWord.Length > minimumWordLength && !normalWord.IsMemberOfBlackListWords(blackListWords)))
{
if (wordsDic.ContainsKey(normalWord))
{
var cnt = wordsDic[normalWord];
wordsDic[normalWord] = ++cnt;
}
else
{
wordsDic.Add(normalWord, 1);
}
}
}
}
List<KeyValuePair<String, Int32>> keywords = wordsDic.ToList();
return keywords;
}
public static List<KeyValuePair<String, Int32>> OrderByDescending(this List<KeyValuePair<String, Int32>> list, bool isBasedOnFrequency = true)
{
List<KeyValuePair<String, Int32>> result = null;
if (isBasedOnFrequency)
result = list.OrderByDescending(q => q.Value).ToList();
else
result = list.OrderByDescending(q => q.Key).ToList();
return result;
}
public static List<KeyValuePair<String, Int32>> TakeTop(this List<KeyValuePair<String, Int32>> list, Int32 n = 10)
{
List<KeyValuePair<String, Int32>> result = list.Take(n).ToList();
return result;
}
public static List<String> GetWords(this List<KeyValuePair<String, Int32>> list)
{
List<String> result = new List<String>();
foreach (var item in list)
{
result.Add(item.Key);
}
return result;
}
public static List<Int32> GetFrequency(this List<KeyValuePair<String, Int32>> list)
{
List<Int32> result = new List<Int32>();
foreach (var item in list)
{
result.Add(item.Value);
}
return result;
}
public static String AsString<T>(this List<T> list, string seprator = ", ")
{
String result = string.Empty;
foreach (var item in list)
{
result += string.Format("{0}{1}", item, seprator);
}
return result;
}
private static bool IsMemberOfBlackListWords(this String word, List<String> blackListWords)
{
bool result = false;
if (blackListWords == null) return false;
foreach (var w in blackListWords)
{
if (w.ToNormalString().Equals(word))
{
result = true;
break;
}
}
return result;
}
}

Categories