I'm essentially trying to read an xml file. One of the values has a suffix, e.g. "30d". This is meant to mean '30 days'. So I'm trying to convert this to a DateTime.Now.AddDays(30). To read this field in the XML, i decided to use an Enum:
enum DurationType { Min = "m", Hours = "h", Days = "d" }
Now I'm not exactly sure how exactly to approach this efficiently (I'm a little daft when it comes to enums). Should I separate the suffix, in this case "d", out of the string first, then try and match it in the enum using a switch statement?
I guess if you dumb down my question, it'd be: What's the best way to get from 30d, to DateTime.Now.AddDays(30) ?
You could make an ExtensionMethod to parse the string and return the DateTime you want
Something like:
public static DateTime AddDuration(this DateTime datetime, string str)
{
int value = 0;
int mutiplier = str.EndsWith("d") ? 1440 : str.EndsWith("h") ? 60 : 1;
if (int.TryParse(str.TrimEnd(new char[]{'m','h','d'}), out value))
{
return datetime.AddMinutes(value * mutiplier);
}
return datetime;
}
Usage:
var date = DateTime.Now.AddDuration("2d");
This seems like a good place to use regular expressions; specifically, capture groups.
Below is a working example:
using System;
using System.Text.RegularExpressions;
namespace RegexCaptureGroups
{
class Program
{
// Below is a breakdown of this regular expression:
// First, one or more digits followed by "d" or "D" to represent days.
// Second, one or more digits followed by "h" or "H" to represent hours.
// Third, one or more digits followed by "m" or "M" to represent minutes.
// Each component can be separated by any number of spaces, or none.
private static readonly Regex DurationRegex = new Regex(#"((?<Days>\d+)d)?\s*((?<Hours>\d+)h)?\s*((?<Minutes>\d+)m)?", RegexOptions.IgnoreCase);
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
var days = match.Groups["Days"].Value;
var hours = match.Groups["Hours"].Value;
var minutes = match.Groups["Minutes"].Value;
int daysAsInt32, hoursAsInt32, minutesAsInt32;
if (!int.TryParse(days, out daysAsInt32))
daysAsInt32 = 0;
if (!int.TryParse(hours, out hoursAsInt32))
hoursAsInt32 = 0;
if (!int.TryParse(minutes, out minutesAsInt32))
minutesAsInt32 = 0;
return new TimeSpan(daysAsInt32, hoursAsInt32, minutesAsInt32, 0);
}
static void Main(string[] args)
{
Console.WriteLine(ParseDuration("30d"));
Console.WriteLine(ParseDuration("12h"));
Console.WriteLine(ParseDuration("20m"));
Console.WriteLine(ParseDuration("1d 12h"));
Console.WriteLine(ParseDuration("5d 30m"));
Console.WriteLine(ParseDuration("1d 12h 20m"));
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
EDIT: Below is an alternative, slightly more condensed version of the above, though I'm not sure which one I prefer more. I'm usually not a fan of overly dense code.
I adjusted the regular expression to put a limit of 10 digits on each number. This allows me to safely use the int.Parse function, because I know that the input consists of at least one digit and at most ten (unless it didn't capture at all, in which case it would be empty string: hence, the purpose of the ParseInt32ZeroIfNullOrEmpty method).
// Below is a breakdown of this regular expression:
// First, one to ten digits followed by "d" or "D" to represent days.
// Second, one to ten digits followed by "h" or "H" to represent hours.
// Third, one to ten digits followed by "m" or "M" to represent minutes.
// Each component can be separated by any number of spaces, or none.
private static readonly Regex DurationRegex = new Regex(#"((?<Days>\d{1,10})d)?\s*((?<Hours>\d{1,10})h)?\s*((?<Minutes>\d{1,10})m)?", RegexOptions.IgnoreCase);
private static int ParseInt32ZeroIfNullOrEmpty(string input)
{
return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
}
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
return new TimeSpan(
ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
0);
}
EDIT: Just to take this one more step, I've added another version below, which handles days, hours, minutes, seconds, and milliseconds, with a variety of abbreviations for each. I split the regular expression into multiple lines for readability. Note, I also had to adjust the expression by using (\b|(?=[^a-z])) at the end of each component: this is because the "ms" unit was being captured as the "m" unit. The special syntax of "?=" used with "[^a-z]" indicates to match the character but not to "consume" it.
// Below is a breakdown of this regular expression:
// First, one to ten digits followed by "d", "dy", "dys", "day", or "days".
// Second, one to ten digits followed by "h", "hr", "hrs", "hour", or "hours".
// Third, one to ten digits followed by "m", "min", "minute", or "minutes".
// Fourth, one to ten digits followed by "s", "sec", "second", or "seconds".
// Fifth, one to ten digits followed by "ms", "msec", "millisec", "millisecond", or "milliseconds".
// Each component may be separated by any number of spaces, or none.
// The expression is case-insensitive.
private static readonly Regex DurationRegex = new Regex(#"
((?<Days>\d{1,10})(d|dy|dys|day|days)(\b|(?=[^a-z])))?\s*
((?<Hours>\d{1,10})(h|hr|hrs|hour|hours)(\b|(?=[^a-z])))?\s*
((?<Minutes>\d{1,10})(m|min|minute|minutes)(\b|(?=[^a-z])))?\s*
((?<Seconds>\d{1,10})(s|sec|second|seconds)(\b|(?=[^a-z])))?\s*
((?<Milliseconds>\d{1,10})(ms|msec|millisec|millisecond|milliseconds)(\b|(?=[^a-z])))?",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static int ParseInt32ZeroIfNullOrEmpty(string input)
{
return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
}
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
return new TimeSpan(
ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Seconds"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Milliseconds"].Value));
}
update:
Don't vote for this. I'm leaving it simply because it's an alternative approach. Instead look at sa_ddam213 and Dr. Wily's Apprentice's answers.
Should I separate the suffix, in this case "d", out of the string
first, then try and match it in the enum using a switch statement?
Yes.
For a fully working example:
private void button1_Click( object sender, EventArgs e ) {
String value = "30d";
Duration d = (Duration)Enum.Parse(typeof(Duration), value.Substring(value.Length - 1, 1).ToUpper());
DateTime result = d.From(new DateTime(), value);
MessageBox.Show(result.ToString());
}
enum Duration { D, W, M, Y };
static class DurationExtensions {
public static DateTime From( this Duration duration, DateTime dateTime, Int32 period ) {
switch (duration)
{
case Duration.D: return dateTime.AddDays(period);
case Duration.W: return dateTime.AddDays((period*7));
case Duration.M: return dateTime.AddMonths(period);
case Duration.Y: return dateTime.AddYears(period);
default: throw new ArgumentOutOfRangeException("duration");
}
}
public static DateTime From( this Duration duration, DateTime dateTime, String fullValue ) {
Int32 period = Convert.ToInt32(fullValue.ToUpper().Replace(duration.ToString(), String.Empty));
return From(duration, dateTime, period);
}
}
I really don't see how using an enum helps here.
Here's how I might approach it.
string s = "30d";
int typeIndex = s.IndexOfAny(new char[] { 'd', 'w', 'm' });
if (typeIndex > 0)
{
int value = int.Parse(s.Substring(0, typeIndex));
switch (s[typeIndex])
{
case 'd':
result = DateTime.Now.AddDays(value);
break;
case 'w':
result = DateTime.Now.AddDays(value * 7);
break;
case 'm':
result = DateTime.Now.AddMonths(value);
break;
}
}
Depending on the reliability of your input data, you might need to use int.TryParse() instead of int.Parse(). Otherwise, this should be all you need.
Note: I've also written a sscanf() replacement for .NET that would handle this quite easily. You can see the code for that in the article A sscanf() Replacement for .NET.
Try the following code, assuming that values like "30d" are in a string 'val'.
DateTime ConvertValue(string val) {
if (val.Length > 0) {
int prefix = Convert.ToInt32(val.Length.Remove(val.Length-1));
switch (val[val.Length-1]) {
case 'd': return DateTime.Now.AddDays(prefix);
case 'm': return DateTime.Now.AddMonths(prefix);
// etc.
}
throw new ArgumentException("string in unexpected format.");
}
Example of a console application example/tutorial:
enum DurationType
{
[DisplayName("m")]
Min = 1,
[DisplayName("h")]
Hours = 1 * 60,
[DisplayName("d")]
Days = 1 * 60 * 24
}
internal class Program
{
private static void Main(string[] args)
{
string input1 = "10h";
string input2 = "1d10h3m";
var x = GetOffsetFromDate(DateTime.Now, input1);
var y = GetOffsetFromDate(DateTime.Now, input2);
}
private static Dictionary<string, DurationType> suffixDictionary
{
get
{
return Enum
.GetValues(typeof (DurationType))
.Cast<DurationType>()
.ToDictionary(duration => duration.GetDisplayName(), duration => duration);
}
}
public static DateTime GetOffsetFromDate(DateTime date, string input)
{
MatchCollection matches = Regex.Matches(input, #"(\d+)([a-zA-Z]+)");
foreach (Match match in matches)
{
int numberPart = Int32.Parse(match.Groups[1].Value);
string suffix = match.Groups[2].Value;
date = date.AddMinutes((int)suffixDictionary[suffix]);
}
return date;
}
}
[AttributeUsage(AttributeTargets.Field)]
public class DisplayNameAttribute : Attribute
{
public DisplayNameAttribute(String name)
{
this.name = name;
}
protected String name;
public String Name { get { return this.name; } }
}
public static class ExtensionClass
{
public static string GetDisplayName<TValue>(this TValue value) where TValue : struct, IConvertible
{
FieldInfo fi = typeof(TValue).GetField(value.ToString());
DisplayNameAttribute attribute = (DisplayNameAttribute)fi.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
if (attribute != null)
return attribute.Name;
return value.ToString();
}
}
Uses an attribute to define your suffix, uses the enum value to define your offset.
Requires:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
It may be considered a hack to use the enum integer value but this example will still let you parse out all the Enums (for any other use like switch case) with little tweaks.
Enums can't be backed with non-numeric types, so string-based enums are out. It's possible you may be overthinking it. Without knowing any more about the problem, the most straightforward solution seems to be splitting off the last character, converting the rest to an int, and then handling each final char as a separate case.
I'd suggest using regexp to strip the number first and than execute Enum.Parse Method to evaluate the value of the enum. Than you can use a switch (see Corylulu's answer) to get the right offset, based on the parsed number and enum value.
i have a following type of string format ---
Proposal is given to {Jwala Vora#3/13} for {Amazon Vally#2/11} {1#3/75} by {MdOffice employee#1/1}
the string contains pair of { } with different positions and may be n number of times.
now i want to replace that pair with other strings which i will compute depending on the string between { } pair.
how to do this ?
You could try regular expressions. Specifically, Regex.Replace variants using MatchEvaluator should do the trick. See http://msdn.microsoft.com/en-US/library/cft8645c(v=vs.80).aspx for more information.
Something along these lines:
using System;
using System.Text.RegularExpressions;
public class Replacer
{
public string Replace(string input)
{
// The regular expression passed as the second argument to the Replace method
// matches strings in the format "{value0#value1/value2}", i.e. three strings
// separated by "#" and "/" all surrounded by braces.
var result = Regex.Replace(
input,
#"{(?<value0>[^#]+)#(?<value1>[^/]+)/(?<value2>[^}]+)}",
ReplaceMatchEvaluator);
return result;
}
private string ReplaceMatchEvaluator(Match m)
{
// m.Value contains the matched string including the braces.
// This method is invoked once per matching portion of the input string.
// We can then extract each of the named groups in order to access the
// substrings of each matching portion as follows:
var value0 = m.Groups["value0"].Value; // Contains first value, e.g. "Jwala Vora"
var value1 = m.Groups["value1"].Value; // Contains second value, e.g. "3"
var value2 = m.Groups["value2"].Value; // Contains third value, e.g. "13"
// Here we can do things like convert value1 and value2 to integers...
var intValue1 = Int32.Parse(value1);
var intValue2 = Int32.Parse(value2);
// etc.
// Here we return the value with which the matching portion is replaced.
// This would be some function of value0, value1 and value2 as well as
// any other data in the Replacer class.
return "xyz";
}
}
public static class Program
{
public static void Main(string[] args)
{
var replacer = new Replacer();
var result = replacer.Replace("Proposal is given to {Jwala Vora#3/13} for {Amazon Vally#2/11} {1#3/75} by {MdOffice employee#1/1}");
Console.WriteLine(result);
}
}
This program will output Proposal is given to xyz for xyz xyz by xyz.
You'll need to provide your app-specific logic in the ReplaceMatchEvaluator method to process value0, value1 and value2 as appropriate. The class Replacer can contain additional members that can be used to implement the replacement logic in ReplaceMatchEvaluator. Strings are processed by calling Replace on an instance of the Replacer class.
Well you can split the string by '{' and '}' and determine the contents that way.
But i think a better way would be to find the chars by index and then you know the starting index and the end index of a pair or curly brackets so that way you can reconstruct the string with the placeholders replaced.
But the best method may be using Regex.Replace but that will only help to replace the placeholders with values you want but i think your requirement is to also parse the text inside of the curly brackets and based on that chose the value to be inserted so this won't work well perhaps. Find and Replace a section of a string with wildcard type search
You may use the Regex.Replace Method (String, String, MatchEvaluator) method and the {.*?} pattern. The following example uses a dictionary to replace the values, but you may replace this with your own logic.
class Program
{
static Dictionary<string, string> _dict = new Dictionary<string, string>();
static void Main(string[] args)
{
_dict.Add("{Jwala Vora#3/13}","someValue1");
_dict.Add("{Amazon Vally#2/11}", "someValue2");
_dict.Add("{1#3/75}", "someValue3");
_dict.Add("{MdOffice employee#1/1}", "someValue4");
var input = #"Proposal is given to {Jwala Vora#3/13} for {Amazon Vally#2/11} {1#3/75} by {MdOffice employee#1/1}";
var result = Regex.Replace(input, #"{.*?}", Evaluate);
Console.WriteLine(result);
}
private static string Evaluate(Match match)
{
return _dict[match.Value];
}
}
Cannot you do something with string.Format()?
For example
string.Format("Proposal is given to {0} for {1} {2} by {3}", "Jwala Vora", "Amazon Vally", 1, "MdOffice employee");
I have a string that contains an int. How can I parse the int in C#?
Suppose I have the following strings, which contains an integer:
15 person
person 15
person15
15person
How can I track them, or return null if no integer is found in the string?
You can remove all non-digits, and parse the string if there is anything left:
str = Regex.Replace(str, "\D+", String.Empty);
if (str.Length > 0) {
int value = Int32.Parse(str);
// here you can use the value
}
Paste this code into a test:
public int? ParseAnInt(string s)
{
var match = System.Text.RegularExpressions.Regex.Match(s, #"\d+");
if (match.Success)
{
int result;
//still use TryParse to handle integer overflow
if (int.TryParse(match.Value, out result))
return result;
}
return null;
}
[TestMethod]
public void TestThis()
{
Assert.AreEqual(15, ParseAnInt("15 person"));
Assert.AreEqual(15, ParseAnInt("person 15"));
Assert.AreEqual(15, ParseAnInt("person15"));
Assert.AreEqual(15, ParseAnInt("15person"));
Assert.IsNull(ParseAnInt("nonumber"));
}
The method returns null is no number is found - it also handles the case where the number causes an integer overflow.
To reduce the chance of an overflow you could instead use long.TryParse
Equally if you anticipate multiple groups of digits, and you want to parse each group as a discreet number you could use Regex.Matches - which will return an enumerable of all the matches in the input string.
Use something like this :
Regex r = new Regex("\d+");
Match m = r.Match(yourinputstring);
if(m.Success)
{
Dosomethingwiththevalue(m.Value);
}
Since everyone uses Regex to extract the numbers, here's a Linq way to do it:
string input = "15person";
string numerics = new string(input.Where(Char.IsDigit).ToArray());
int result = int.Parse(numerics);
Just for the sake of completeness, it's probably not overly elegant. Regarding Jaymz' comment, this would return 151314 when 15per13so14n is passed.
I have strings that look like this:
1. abc
2. def
88. ghi
I'd like to be able to get the numbers from the strings and put it into a variable and then get the remainder of the string and put it into another variable. The number is always at the start of the string and there is a period following the number. Is there an easy way that I can parse the one string into a number and a string?
May not be the best way, but, split by the ". " (thank you Kirk)
everything afterwards is a string, and everything before will be a number.
You can call IndexOf and Substring:
int dot = str.IndexOf(".");
int num = int.Parse(str.Remove(dot).Trim());
string rest = str.Substring(dot).Trim();
var input = "1. abc";
var match = Regex.Match(input, #"(?<Number>\d+)\. (?<Text>.*)");
var number = int.Parse(match.Groups["Number"].Value);
var text = match.Groups["Text"].Value;
This should work:
public void Parse(string input)
{
string[] parts = input.Split('.');
int number = int.Parse(parts[0]); // convert the number to int
string str = parts[1].Trim(); // remove extra whitespace around the remaining string
}
The first line will split the string into an array of strings where the first element will be the number and the second will be the remainder of the string.
Then you can convert the number into an integer with int.Parse.
public Tuple<int, string> SplitItem(string item)
{
var parts = item.Split(new[] { '.' });
return Tuple.Create(int.Parse(parts[0]), parts[1].Trim());
}
var tokens = SplitItem("1. abc");
int number = tokens.Item1; // 1
string str = tokens.Item2; // "abc"
I have some strings like below:
string num1 = "D123_1";
string num2 = "D123_2";
string num3 = "D456_11";
string num4 = "D456_22";
string num5 = "D_123_D";
string num5 = "_D_123";
I want to make a function that will do the following actions:
1- Checks if given string DOES HAVE an Underscore in it, and this underscore should be after some Numbers and Follow with some numbers: in this case 'num5' and 'num6' are invalid!
2- Replace the numbers after the last underscore with any desired string, for example I want 'num1 = "D123_1"' to be changed into 'D123_2'
So far I came with this idea but it is not working :( First I dont know how to check for criteria 1 and second the replace statement is not working:
private string CheckAndReplace(string given, string toAdd)
{
var changedString = given.Split('_');
return changedString[changedString.Length - 1] + toAdd;
}
Any help and tips will be appriciated
What you are looking for is a regular expression. This is (mostly) from the top of my head. But it should easily point you in the right direction. The regular expression works fine.
public static Regex regex = new Regex("(?<character>[a-zA-Z]+)(?<major>\\d+)_(?<minor>\\d+)",RegexOptions.CultureInvariant | RegexOptions.Compiled);
Match m = regex.Match(InputText);
if (m.Succes)
{
var newValue = String.Format("{0}{1}_{2}"m.Groups["character"].Value, m.Groups["major"].Value, m.Groups["minor"].Value);
}
In your code you split the String into an array of strings and then access the wrong index of the array, so it isn't doing what you want.
Try working with a substring instead. Find the index of the last '_' and then get the substring:
private string CheckAndReplace(string given, string toAdd) {
int index = given.LastIndexOf('_')+1;
return given.Substring(0,index) + toAdd;
}
But before that check the validity of the string (see other answers). This code fragment will break when there's no '_' in the string.
You could use a regular expression (this is not a complete implementation, only a hint):
private string CheckAndReplace(string given, string toAdd)
{
Regex regex = new Regex("([A-Z]*[0-9]+_)[0-9]+");
if (regex.IsMatch(given))
{
return string.Concat(regex.Match(given).Groups[1].Value, toAdd);
}
else
{
... do something else
}
}
Use a good regular expression implementation. .NET has standard implementation of them