Before using String.Format to format a string in C#, I would like to know how many parameters does that string accept?
For eg. if the string was "{0} is not the same as {1}", I would like to know that this string accepts two parameters
For eg. if the string was "{0} is not the same as {1} and {2}", the string accepts 3 parameters
How can I find this efficiently?
String.Format receives a string argument with format value, and an params object[] array, which can deal with an arbitrary large value items.
For every object value, it's .ToString() method will be called to resolve that format pattern
EDIT: Seems I misread your question. If you want to know how many arguments are required to your format, you can discover that by using a regular expression:
string pattern = "{0} {1:00} {{2}}, Failure: {0}{{{1}}}, Failure: {0} ({0})";
int count = Regex.Matches(Regex.Replace(pattern,
#"(\{{2}|\}{2})", ""), // removes escaped curly brackets
#"\{\d+(?:\:?[^}]*)\}").Count; // returns 6
As Benjamin noted in comments, maybe you do need to know number of different references. If you don't using Linq, here you go:
int count = Regex.Matches(Regex.Replace(pattern,
#"(\{{2}|\}{2})", ""), // removes escaped curly brackets
#"\{(\d+)(?:\:?[^}]*)\}").OfType<Match>()
.SelectMany(match => match.Groups.OfType<Group>().Skip(1))
.Select(index => Int32.Parse(index.Value))
.Max() + 1; // returns 2
This also address #280Z28 last problem spotted.
Edit by 280Z28: This will not validate the input, but for any valid input will give the correct answer:
int count2 =
Regex.Matches(
pattern.Replace("{{", string.Empty),
#"\{(\d+)")
.OfType<Match>()
.Select(match => int.Parse(match.Groups[1].Value))
.Union(Enumerable.Repeat(-1, 1))
.Max() + 1;
You'll have to parse through the string and find the highest integer value between the {}'s...then add one.
...or count the number of sets of {}'s.
Either way, it's ugly. I'd be interested to know why you need to be able to figure out this number programatically.
EDIT
As 280Z28 mentioned, you'll have to account for the various idiosyncrasies of what can be included between the {}'s (multiple {}'s, formatting strings, etc.).
I rely on ReSharper to analyze that for me, and it is a pity that Visual Studio does not ship with such a neat feature.
Related
I have created a MadLibs style game where the user enters responses to prompts which in turn replace blanks, represented by %s0, %s1 etc., in a story. I have this working using a for loop but someone else suggested I could do it using regex. What I have so far is below, which replaces all instances of %s+number with "wibble". What I was wondering is if it is possible to store the number found by the regex in a temporary variable and in turn use that to return a value from the list Words? E.g. return Regex.Replace(story, pattern, Global.Words[x]); where x is the number returned by the regex pattern as it goes over the string.
static void Main(string[] args)
{
Globals.Words = new List<string>();
Globals.Words.Add("nathan");
Globals.Words.Add("bob");
var text = "Once upon a time there was a %s0 and it was %s1";
Console.WriteLine(FindEscapeCharacters(text));
}
public static string FindEscapeCharacters(string story)
{
var pattern = #"%s([0-9]+)";
return Regex.Replace(story, "%s([0-9]+)", "wibble");
}
Thanks in advance, Nathan.
Not a direct answer to your question about regexes, but if I understand you correctly, there is an easier way to do this:
string baseString = "I have a {0} {1} in my {0} {2}.";
List<string> words = new List<string>() { "red", "cat", "hat" };
string outputString = String.Format(baseString, words.ToArray());
outputString will be I have a red cat in my red hat..
Is that not what you want, or is there more to your question that I'm missing?
Minor elaboration
String.Format uses the following signature:
string Format(string format, params object[] values)
The neat thing about params is that you can either list values separately:
var a = String.Format("...", valueA, valueB, valueC);
but you can also pass in an array directly:
var a = String.Format("...", valueArray);
Note that you can't mix and match the two approaches.
Yes, you are very close in your attempt with Regex.Replace; the last step is to change constant "wibble" into lambda match => how_to_replace_the_match:
var text = "Once upon a time there was a %s0 and it was %s1";
// Once upon a time there was a nathan and it was bob
var result = Regex.Replace(
text,
"%s([0-9]+)",
match => Globals.Words[int.Parse(match.Groups[1].Value)]);
Edit: In case you don't want working with capturing groups by their numbers, you can name them explicitly:
// Once upon a time there was a nathan and it was bob
var result = Regex.Replace(
text,
"%s(?<number>[0-9]+)",
match => Globals.Words[int.Parse(match.Groups["number"].Value)]);
There is an overload of Regex.Replace that, rather than taking a string for the last argument, takes a MatchEvaluator delegate - a function that takes a Match object and returns a string.
You could make that function parse the integer from the Match's Groups[1].Value property and then use that to index into your list, returning the string you find.
I'm new to the C#/MVC world. I spend a lot of time today figuring out how to display a DateTimeOffset object in the format i want. Finally got it working this way.
Html.TextBoxFor(model => model.DeliveryDate,"{0:MM/dd/yyyy}",
new { htmlAttributes = new { #class = "datepicker" } })
But I still don't understand the importance of '0' in the format string. the page breaks if i replace the 0 with any other number or totally remove it. Can someone help me understand this?
From String.Format Method
The {0} in the format string is a format item. 0 is the index of the object whose string value will be inserted at that position. (Indexes start at 0.) If the object to be inserted is not a string, its ToString method is called to convert it to one before inserting it in the result string.
That's a format string with parameters (like used in e.g. Console.WriteLine, or string.Format). The {0} would be the placeholder for the first argument, and {0:mm/dd/yyyy} is simply a format string to convert the first argument to a string.
When you use the string.Format you can pass the space for arguments like {0}, {1}, etc which is the indexes you pass as arguments for the method. It is the same for asp.net razor helpers.
You also can provide the format after the index separating by :, for sample: {0:0.00} as format for a number with 2 decimals places or {1:dd/MM/yyyy} for dates etc.
String Interpolation
There is a new way to implement it using the String Interpolation. Basically, you can concat the values on your string without generating new strings. For sample:
var i = 18;
var s = $"You are {age} years old.";
Since you start the string with $, you can pass arguments between { and }. You also can use the same formats to format your data as you use on string.Format. For sample:
var today = $"Today is {DateTime.Now:D}";
var date = DateTime.Now.Add(1);
var tommorrow = $"Tommorrow is {date:dd/MM/yyyy}";
See the documentation for String.Format():
https://msdn.microsoft.com/en-us/library/system.string.format.aspx
In a nutshell, when the model is rendered to HTML text, the DeliveryDate object value will be passed to String.Format(), where {0} indicates the index of the first value in an array of values being passed to Format(). So {0:MM/dd/yyyy} just means to format the first value in the array using date components. Basically, it will do something like this internally:
String s = SomeValueArray[0].ToString("MM/dd/yyyy");
0 is a placeholder for your argument / property (in this case) DeliveryDate.. Similar to String.Format examples... so when your View is rendered.. the 0 will be replaced with whatever value that DeliveryDate is holding in the format MM/dd/yyyy
I have looked about everywhere (exaggeration) before I started asking the question and have come up with nothing, I think the question may be confusing but I am trying to pass the variable i to the parameters in the WriteLine(..) method string, here is an example;
Expression<Func<int, int, int>> expression = (a, b) => a + b;
for(int i = 0; i < expression.Parameters.Count; i++)
{
Console.WriteLine("Expression param[{i+1}]: {i}", expression.Parameters[i]);
}
Is this valid in c#? to add the int i into the Console.WriteLine(..) method.
I have also tried:
Console.WriteLine("Expression param[{" + i+1 +"}]: {"+ i +"}", expression.Parameters[i]);
It work's
Console.WriteLine("Expression param[{0}]: {1}", i+1, expression.Parameters[i]);
But if you are using C# 6, string interpolation is better:
Console.WriteLine($"Expression param[{i+1}]: {expression.Parameters[i]}");
String interpolation lets you more easily format strings. String.Format and its cousins are very versatile, but their use is somewhat clunky and error prone. Particularly unfortunate is the use of numbered placeholders like {0} in the format string, which must line up with separately supplied arguments.
ref: https://blogs.msdn.microsoft.com/csharpfaq/2014/11/20/new-features-in-c-6/
Use it in this way:
Console.WriteLine("Expression param[{0}]: {1}", i, expression.Parameters[i]);
{0} refers to the first argument after the format (i).
{1} refers to the second argument after the format (expression.Parameters[i]).
And so on.
This has been asked a few different ways but I am debating on "my way" vs "your way" with another developer. Language is C#.
I want to parse a pipe delimited string where the first 2 characters of each chunk is my tag.
The rules. Not my rules but rules I have been given and must follow.
I can't change the format of the string.
This function will be called possibly many times so efficiency is key.
I need to keep is simple.
The input string and tag I am looking for may/will change during runtime.
Example input string: AOVALUE1|ABVALUE2|ACVALUE3|ADVALUE4
Example tag I may need value for: AB
I split string into an array based on delimiter and loop through the array each time the function is called. I then looked at the first 2 characters and return the value minus the first 2 characters.
The "other guys" way is to take the string and use a combination of IndexOf and SubString to find the starting point and ending point of the field I am looking for. Then using SubString again to pullout the value minus the first 2 characters. So he would say IndexOf("|AB") the find then next pipe in the string. This would be the start and end. Then SubString that out.
Now I should think that IndexOf and SubString would parse the string each time at a char by char level so this would be less efficient than using large chunks and reading the string minus the first 2 characters. Or is there another way the is better then what both of us has proposed?
The other guy's approach is going to be more efficient in time given that input string needs to be reevaluated each time. If the input string is long, it is also won't require the extra memory that splitting the string would.
If I'm trying to code a really tight loop I prefer to directly use array/string operators rather than LINQ to avoid that additional overhead:
string inputString = "AOVALUE1|ABVALUE2|ACVALUE3|ADVALUE4";
static string FindString(string tag)
{
int startIndex;
if (inputString.StartsWith(tag))
{
startIndex = tag.Length;
}
else
{
startIndex = inputString.IndexOf(string.Format("|{0}", tag));
if (startIndex == -1)
return string.Empty;
startIndex += tag.Length + 1;
}
int endIndex = inputString.IndexOf('|', startIndex);
if (endIndex == -1)
endIndex = inputString.Length;
return inputString.Substring(startIndex, endIndex - startIndex);
}
I've done a lot of parsing in C# and I would probably take the approach suggested by the "other guys" just because it would be a bit lighter on resources used and likely to be a little faster as well.
That said, as long as the data isn't too big, there's nothing wrong with the first approach and it will be much easier to program.
Something like this may work ok
string myString = "AOVALUE1|ABVALUE2|ACVALUE3|ADVALUE4";
string selector = "AB";
var results = myString.Split('|').Where(x => x.StartsWith(selector)).Select(x => x.Replace(selector, ""));
Returns: list of the matches, in this case just one "VALUE2"
If you are just looking for the first or only match this will work.
string result = myString.Split('|').Where(x => x.StartsWith(selector)).Select(x => x.Replace(selector, "")).FirstOrDefault();
SubString does not parse the string.
IndexOf does parse the string.
My preference would be the Split method, primarily code coding efficiency:
string[] inputArr = input.Split("|".ToCharArray()).Select(s => s.Substring(3)).ToArray();
is pretty concise. How many LoC does the substring/indexof method take?
Is there a way to tell the String.Format() function (without writing my own function) how many placeholders there are dynamically? It we be great to say 15, and know I'd have {0}-{14} generated for me. I'm generate text files and I often have more than 25 columns. It would greatly help.
OK,
I will rephrase my question. I wanted to know if it is at all possible to tell the String.Format function at execution time how many place-holders I want in my format string without typing them all out by hand.
I'm guessing by the responses so far, I will just go ahead and write my own method.
Thanks!
You could use Enumerable.Range and LINQ to generate your message string.
Enumerable.Range(0, 7).Select(i => "{" + i + "}").ToArray()
generates following string:
"{0}{1}{2}{3}{4}{5}{6}"
Adding a bit to AlbertEin's response, I don't believe String.Format can do this for you out-of-the-box. You'll need to dynamically create the format string prior to using the String.Format method, as in:
var builder = new StringBuilder();
for(var i = 0; i < n; ++i)
{
builder.AppendFormat("{0}", "{" + i + "}");
}
String.Format(builder.ToString(), ...);
This isn't exactly readable, though.
Why use string.Format when there is no formatting (atleast from what I can see in your question)? You could use simple concatenation using stringbuilder instead.
There is a way to do this directly:
Just create a custom IFormatProvider, then use this overload of string.Format.
For example, if you want to always have 12 decimal points, you can do:
CultureInfo culture = Thread.CurrentThread.CurrentCulture.Clone(); // Copy your current culture
NumberFormatInfo nfi = culture.NumberFormat;
nfi.NumberDecimalDigits = 12; // Set to 12 decimal points
string newResult = string.Format(culture, "{0}", myDouble); // Will put it in with 12 decimal points