I am creating several variables by parsing a long string into:
Year
Make
Model
Color
ColorLower
Style
Depending on the record I may have details in some or all of these variables. In most cases, though, some are blank. Following the variables being populated I add them into a database field that is the description of a car/vehicle.
Currently my if/else block goes one by one and if a variable has a non-zero length, the concatenated description variable
if (length($Year)>0)
{
$Description == $Description + " " + Year
}
elsif (length($Make) > 0)
$Description == $Description + " " + $Make
} ...and so on
What I have now is working, I'd be interested in hearing is there is a shorter, more compact way that I could maximize my code.
Thank you!
Better way is to use StringBuilder
using System;
using System.Text;
namespace stringy
{
class MainClass
{
public static void Main (string[] args)
{
string hello = "Hello World!";
int i = 123;
double d = 3.14;
StringBuilder sb = new StringBuilder();
sb.Append(hello);
sb.Append(i);
sb.Append(d);
Console.WriteLine (sb.ToString());
}
}
}
Adding strings includes lot of allocations and reallocations.
string Year = "2015", Make = "Ford", Model = "Rustbucket",
Color = "Red", ColorLower = "Green", Style = "Car";
string[] stuff = { Year, Make, Model, Color, ColorLower, Style };
string Description = "Start ";
Description+=String.Join(" ", stuff.Where(t => t != ""));
No pain! No gain! Checking, and memory optimization:
public class Entity{
public string Year {get;set;}
public string Make {get;set;}
public string Model {get;set;}
public string Color {get;set;}
public string ColorLower {get;set;}
public string Style {get;set;}
public string Description{
get {
string format = "{0} ";
StringBuilder sb = new StringBuilder();
sb.Append(!string.IsNullOrEmpty(Year) && !string.IsNullOrEmpty(Year.Trim())
? string.Format(format, Year.Trim()) : string.Empty);
sb.Append(!string.IsNullOrEmpty(Make) && !string.IsNullOrEmpty(Make.Trim())
? string.Format(format, Make.Trim()) : string.Empty);
sb.Append(!string.IsNullOrEmpty(Model) && !string.IsNullOrEmpty(Model.Trim())
? string.Format(format, Model.Trim()) : string.Empty);
sb.Append(!string.IsNullOrEmpty(Color) && !string.IsNullOrEmpty(Color.Trim())
? string.Format(format, Color.Trim()) : string.Empty);
sb.Append(!string.IsNullOrEmpty(ColorLower) && !string.IsNullOrEmpty(ColorLower.Trim())
? string.Format(format, ColorLower.Trim()) : string.Empty);
sb.Append(!string.IsNullOrEmpty(Style) && !string.IsNullOrEmpty(Style.Trim())
? string.Format(format.Trim(), Style.Trim()) : string.Empty);
return sb.ToString();
}
}
}
static void Main()
{
var e1 = new Entity{Year="Y",Make="M",Model="Md",
Color="C",ColorLower="CL",Style="S"};
Console.WriteLine(e1.Description); // 'Y M Md C CL S'
}
Related
i'm having a string in c# for which i have to find a specific word "code" in the string and have to get the remaining string after the word "code".
The string is
"Error description, code : -1"
so i have to find the word code in the above string and i have to get the error code.
I have seen regex but now clearly understood. Is there any simple way ?
string toBeSearched = "code : ";
string code = myString.Substring(myString.IndexOf(toBeSearched) + toBeSearched.Length);
Something like this?
Perhaps you should handle the case of missing code :...
string toBeSearched = "code : ";
int ix = myString.IndexOf(toBeSearched);
if (ix != -1)
{
string code = myString.Substring(ix + toBeSearched.Length);
// do something here
}
var code = myString.Split(new [] {"code"}, StringSplitOptions.None)[1];
// code = " : -1"
You can tweak the string to split by - if you use "code : ", the second member of the returned array ([1]) will contain "-1", using your example.
Simpler way (if your only keyword is "code" ) may be:
string ErrorCode = yourString.Split(new string[]{"code"}, StringSplitOptions.None).Last();
add this code to your project
public static class Extension {
public static string TextAfter(this string value ,string search) {
return value.Substring(value.IndexOf(search) + search.Length);
}
}
then use
"code : string text ".TextAfter(":")
use indexOf() function
string s = "Error description, code : -1";
int index = s.indexOf("code");
if(index != -1)
{
//DO YOUR LOGIC
string errorCode = s.Substring(index+4);
}
string founded = FindStringTakeX("UID: 994zxfa6q", "UID:", 9);
string FindStringTakeX(string strValue,string findKey,int take,bool ignoreWhiteSpace = true)
{
int index = strValue.IndexOf(findKey) + findKey.Length;
if (index >= 0)
{
if (ignoreWhiteSpace)
{
while (strValue[index].ToString() == " ")
{
index++;
}
}
if(strValue.Length >= index + take)
{
string result = strValue.Substring(index, take);
return result;
}
}
return string.Empty;
}
string originalSting = "This is my string";
string texttobesearched = "my";
string dataAfterTextTobeSearch= finalCommand.Split(new string[] { texttobesearched }, StringSplitOptions.None).Last();
if(dataAfterTextobeSearch!=originalSting)
{
//your action here if data is found
}
else
{
//action if the data being searched was not found
}
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);
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
string db;
ComboBox fieldBox = new ComboBox()
TextBox ValueBox = new TextBox()
ListBox dbValues = new ListBox()
private void LoadDB()
{
//Structure
string myStruct = "NAME\nAGE\nSEX\nSKILL
db = "John\t20\tMale\tNoob\n
Joe\t20\tMale\tMedium\n
Jessica\t27\tFemale\tExpert\n
John\t21\tMale\tMedium
";
//Load struct to combobox
string[] mbstr = myStruct.Split('\n');
for (int i = 0; i < mbstr.Length; i++)
{
fieldBox.Items.Add(mbstr[i]);
}
string[] db2 = db.Split('\n');
for (int i = 1; i < db2.Length - 1; i++)
{
//Display name and age in combobox
dbValues.Items.Add(db2[i].Split('\t')[0] + " - " + db2[i].Split('\t')[1]);
}
}
void ValueBoxKeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode != Keys.Enter)
return;
db.Split('\n')[dbValues.SelectedIndex].Split('\t')[fieldBox.SelectedIndex] = valueBox.Text;
MessageBox.Show("Value set: " +
db.Split('\n')[dbValues.SelectedIndex + 1].Split('\t')[fieldBox.SelectedIndex]
+ " to " + valueBox.Text + ".");
}
This is where it fails:
db.Split('\n')[dbValues.SelectedIndex].Split('\t')[fieldBox.SelectedIndex] = valueBox.Text;
I tried this, and tried to assign to db, but not working though. My original string is unchanged.
I do not want to convert to list and string back, i want to change directly.
How can i do this?
Your first problem is your string: unless you use # escaping you can't have your string cross multiple lines, and if you use # escaping you can't do \t or \n and retain their escaped meaning of tab and newline.
The second problem is a fundamental misunderstanding of the .NET string, string's are immutable. Split will create an array, there is no reference back to the original string, or the second array your splitting. You would need to do something like:
[TestClass]
public class StringTest
{
public TestContext TestContext { get; set; }
[TestMethod]
public void RewriteString()
{
var str = "Garry\t19\tMale\tNoob\n" +
"Joe\t25\tMale\tMedium\n" +
"Gary\t33\tFemale\tExpert";
var rows = str.Split('\n');
var columns = rows[0].Split('\t');
columns[0] = "Jerry";
rows[0] = string.Join("\t", columns);
str = string.Join("\n", rows);
TestContext.WriteLine(str);
}
}
Test Name: RewriteString
Test Outcome: Passed
Result StandardOutput: TestContext Messages:
Jerry 19 Male Noob
Joe 25 Male Medium
Gary 33 Female Expert
Would really hope there would be an easier way to do this, possibly with a Regex?
Now to really look at your (new) question. I have refactored exactly what you have, as I do not know your data situation I'm not entirely sure using a string as a database is a good idea: (this will compile without any references because of the use of dynamic).
public class SomeView
{
string db;
dynamic fieldBox = null;
dynamic valueBox = null;
dynamic dbValues = null;
dynamic MessageBox = null;
private void LoadDB()
{
//Structure
string myStruct = "NAME\nAGE\nSEX\nSKILL";
db = "John\t20\tMale\tNoob\n" +
"Joe\t20\tMale\tMedium\n" +
"Jessica\t27\tFemale\tExpert\n" +
"John\t21\tMale\tMedium";
//Load struct to combobox
string[] mbstr = myStruct.Split('\n');
for (int i = 0; i < mbstr.Length; i++)
{
fieldBox.Items.Add(mbstr[i]);
}
string[] db2 = db .Split('\n');
for (int i = 1; i < db2.Length - 1; i++)
{
var data = db2[i].Split('\t'); //expensive only do once
//Display name and age in combobox
dbValues.Items.Add(data[0] + " - " + data[1]);
}
}
protected string Transform(string value, int row, int column, string replacement, out string old)
{
var rows = value.Split('\n');
var columns = rows[row].Split('\t');
old = columns[column];
columns[column] = replacement;
rows[row] = string.Join("\t", columns);
return string.Join("\n", rows);
}
void ValueBoxKeyDown(object sender, dynamic e)
{
if (e.KeyCode != "enter")
return;
string old;
string newValue = this.Transform(db, dbValues.SelectedIndex, fieldBox.SelectedIndex, valueBox.Text, out old);
MessageBox.Show("Value set: " + old + " to " + valueBox.Text + ".");
}
}
So this is better:
public class SomeView
{
dynamic fieldBox = null;
dynamic valueBox = null;
dynamic dbValues = null;
dynamic MessageBox = null;
private List<Person> People = new List<Person>();
private void LoadDB()
{
//Structure
string myStruct = "NAME\nAGE\nSEX\nSKILL";
string db = "John\t20\tMale\tNoob\n" +
"Joe\t20\tMale\tMedium\n" +
"Jessica\t27\tFemale\tExpert\n" +
"John\t21\tMale\tMedium";
//Load struct to combobox
string[] mbstr = myStruct.Split('\n');
for (int i = 0; i < mbstr.Length; i++)
{
fieldBox.Items.Add(mbstr[i]);
}
People.Clear();
foreach(var row in db.Split('\n'))
{
var columns = row.Split('\t');
Person p = new Person();
p.Name = columns[0];
p.Age = int.Parse(columns[1]);
p.Sex = (Person.Sexs)Enum.Parse(typeof(Person.Sexs), columns[2]);
p.SkillLevel = (Person.SkillLevels)Enum.Parse(typeof(Person.SkillLevels), columns[2]);
People.Add(p);
dbValues.Items.Add(string.Format("{0}-{1}", p.Name, p.Age);
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public enum Sexs
{
Male,
Female
}
public Sexs Sex { get; set; }
public enum SkillLevels
{
Noob,
Medium,
Expert
}
public SkillLevels SkillLevel { get; set; }
}
void ValueBoxKeyDown(object sender, dynamic e)
{
if (e.KeyCode != "enter")
return;
Person p = this.People[dbValues.SelectedIndex];
switch((int)fieldBox.SelectedIndex)
{
case 0: p.Name = valueBox.Text; break;
case 1: p.Age = int.Parse(valueBox.Text); break;
case 2: p.Sex = (Person.Sexs)Enum.Parse(typeof(Person.Sexs), valueBox.Text); break;
case 3: p.SkillLevel = (Person.SkillLevels)Enum.Parse(typeof(Person.SkillLevels), valueBox.Text); break;
default: throw new NotImplementedException();
}
MessageBox.Show("Value set: " + old + " to " + valueBox.Text + ".");
}
}
However this is still garbage, since if you have a strongly typed data set you can actually bind this to form controls without directly manipulating the item.
https://msdn.microsoft.com/en-us/library/c8aebh9k(v=vs.110).aspx
You cannot change the return-value of a method returning a string as strings are immutable. What you can do instead is the following:
string myDatabase =
"Garry\t19\tMale\tNoob\n" +
"Joe\t25\tMale\tMedium\n" +
"Gary\t33\tFemale\tExpert";
var tmp = "";
foreach(var line in myString.Split('\n')) {
tmp = tmp + Regex.Replace(line, "^.*?(?=\\t)", myReplaceText);
}
myString = tmp;
This regex will search for everything before the very first tab within every line, replaces it by "Jerry"and and concatenates every so replaced line into myString.
I have an array which contains following values:
str[0]= "MeterNr 29202"
str[1]="- 20111101: position 61699 (Previous calculation) "
str[2]="- 20111201: position 68590 (Calculation) consumption 6891 kWh"
str[3]="- 20111101: position 75019 (Previous calculation) "
str[4]="MeterNr 50273"
str[5]="- 20111101: position 18103 (Previous reading) "
str[6]="- 20111201: position 19072 (Calculation) consumption 969 kWh "
I want to split the rows in logical order so that I can store them in following Reading class. I have problems with spliting the values. Everything in brackets () is ItemDescription.
I will be thankful for the quick answer.
public class Reading
{
public string MeterNr { get; set; }
public string ItemDescription { get; set; }
public string Date { get; set; }
public string Position { get; set; }
public string Consumption { get; set; }
}
You should parse the values one by one.
If you have a string, which starts with "MeterNr", you should save it as currentMeterNumber and parse the values further.
Otherwise, you can parse the values with Regex:
var dateRegex = new Regex(#"(?<=-\s)(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})");
var positionRegex = new Regex(#"(?<=position\s+)(\d+)");
var descriptionRegex = new Regex(#"(?<=\()(?<description>[^)]+)(?=\))");
var consuptionRegex = new Regex(#"(?<=consumption\s+)(?<consumption>(?<consumtionValue>\d+)\s(?<consumptionUom>\w+))");
I hope, you would be able to create the final algorithm, as well as understand how each of those expressions works. A final point could be to combine them all into single Regex. You should do it yourself to enhance your skills.
P.S.: There are a lot of tutorials in Internet.
I would just use a for loop and string indexes etc, but then I am a bit simple like that! Not sure of your data (i.e. if things might be missing) but this would work on the data you have posted...
var readings = new List<Reading>();
int meterNrLength = "MeterNr".Length;
int positionLength = "position".Length;
int consumptionLength = "consumption".Length;
string meterNr = null;
foreach(var s in str)
{
int meterNrIndex = s.IndexOf("MeterNr",
StringComparison.OrdinalIgnoreCase);
if (meterNrIndex != -1)
{
meterNr = s.Substring(meterNrIndex + meterNrLength).Trim();
continue;
}
var reading = new Reading {MeterNr = meterNr};
string rest = s.Substring(0, s.IndexOf(':'));
reading.Date = rest.Substring(1).Trim();
rest = s.Substring(s.IndexOf("position") + positionLength);
int bracketIndex = rest.IndexOf('(');
reading.Position = rest.Substring(0, bracketIndex).Trim();
rest = rest.Substring(bracketIndex + 1);
reading.ItemDescription = rest.Substring(0, rest.IndexOf(")"));
int consumptionIndex = rest.IndexOf("consumption",
StringComparison.OrdinalIgnoreCase);
if (consumptionIndex != -1)
{
reading.Consumption = rest.Substring(consumptionIndex + consumptionLength).Trim();
}
readings.Add(reading);
}
public static List<Reading> Parser(this string[] str)
{
List<Reading> result = new List<Reading>();
string meterNr = "";
Reading reading;
foreach (string s in str)
{
MatchCollection mc = Regex.Matches(s, "\\d+|\\((.*?)\\)");
if (mc.Count == 1)
{
meterNr = mc[0].Value;
continue;
}
reading = new Reading()
{
MeterNr = meterNr,
Date = mc[0].Value,
Position = mc[1].Value,
ItemDescription = mc[2].Value.TrimStart('(').TrimEnd(')')
};
if (mc.Count == 4)
reading.Consumption = mc[3].Value;
result.Add(reading);
}
return result;
}
I've taken a few school classes along time ago on and to be honest i never really understood the concept of classes. I recently "got back on the horse" and have been trying to find some real world application for creating a class.
you may have seen that I'm trying to parse a lot of family tree data that is in an very old and antiquated format called gedcom
I created a Gedcom Reader class to read in the file , process it and make it available as two lists that contain the data that i found necessary to use
More importantly to me is i created a class to do it so I would very much like to get the experts here to tell me what i did right and what i could have done better ( I wont say wrong because the thing works and that's good enough for me)
Class:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace GedcomReader
{
class Gedcom
{
private string GedcomText = "";
public struct INDI
{
public string ID;
public string Name;
public string Sex;
public string BDay;
public bool Dead;
}
public struct FAM
{
public string FamID;
public string Type;
public string IndiID;
}
public List<INDI> Individuals = new List<INDI>();
public List<FAM> Families = new List<FAM>();
public Gedcom(string fileName)
{
using (StreamReader SR = new StreamReader(fileName))
{
GedcomText = SR.ReadToEnd();
}
ReadGedcom();
}
private void ReadGedcom()
{
string[] Nodes = GedcomText.Replace("0 #", "\u0646").Split('\u0646');
foreach (string Node in Nodes)
{
string[] SubNode = Node.Replace("\r\n", "\r").Split('\r');
if (SubNode[0].Contains("INDI"))
{
Individuals.Add(ExtractINDI(SubNode));
}
else if (SubNode[0].Contains("FAM"))
{
Families.Add(ExtractFAM(SubNode));
}
}
}
private FAM ExtractFAM(string[] Node)
{
string sFID = Node[0].Replace("# FAM", "");
string sID = "";
string sType = "";
foreach (string Line in Node)
{
// If node is HUSB
if (Line.Contains("1 HUSB "))
{
sType = "PAR";
sID = Line.Replace("1 HUSB ", "").Replace("#", "").Trim();
}
//If node for Wife
else if (Line.Contains("1 WIFE "))
{
sType = "PAR";
sID = Line.Replace("1 WIFE ", "").Replace("#", "").Trim();
}
//if node for multi children
else if (Line.Contains("1 CHIL "))
{
sType = "CHIL";
sID = Line.Replace("1 CHIL ", "").Replace("#", "");
}
}
FAM Fam = new FAM();
Fam.FamID = sFID;
Fam.Type = sType;
Fam.IndiID = sID;
return Fam;
}
private INDI ExtractINDI(string[] Node)
{
//If a individual is found
INDI I = new INDI();
if (Node[0].Contains("INDI"))
{
//Create new Structure
//Add the ID number and remove extra formating
I.ID = Node[0].Replace("#", "").Replace(" INDI", "").Trim();
//Find the name remove extra formating for last name
I.Name = Node[FindIndexinArray(Node, "NAME")].Replace("1 NAME", "").Replace("/", "").Trim();
//Find Sex and remove extra formating
I.Sex = Node[FindIndexinArray(Node, "SEX")].Replace("1 SEX ", "").Trim();
//Deterine if there is a brithday -1 means no
if (FindIndexinArray(Node, "1 BIRT ") != -1)
{
// add birthday to Struct
I.BDay = Node[FindIndexinArray(Node, "1 BIRT ") + 1].Replace("2 DATE ", "").Trim();
}
// deterimin if there is a death tag will return -1 if not found
if (FindIndexinArray(Node, "1 DEAT ") != -1)
{
//convert Y or N to true or false ( defaults to False so no need to change unless Y is found.
if (Node[FindIndexinArray(Node, "1 DEAT ")].Replace("1 DEAT ", "").Trim() == "Y")
{
//set death
I.Dead = true;
}
}
}
return I;
}
private int FindIndexinArray(string[] Arr, string search)
{
int Val = -1;
for (int i = 0; i < Arr.Length; i++)
{
if (Arr[i].Contains(search))
{
Val = i;
}
}
return Val;
}
}
}
Implementation:
using System;
using System.Windows.Forms;
using GedcomReader;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string path = #"C:\mostrecent.ged";
string outpath = #"C:\gedcom.txt";
Gedcom GD = new Gedcom(path);
GraphvizWriter GVW = new GraphvizWriter("Family Tree");
foreach(Gedcom.INDI I in GD.Individuals)
{
string color = "pink";
if (I.Sex == "M")
{
color = "blue";
}
GVW.ListNode(I.ID, I.Name, "filled", color, "circle");
if (I.ID == "ind23800")
{MessageBox.Show("stop");}
//"ind23800" [ label="Sarah Mandley",shape="circle",style="filled",color="pink" ];
}
foreach (Gedcom.FAM F in GD.Families)
{
if (F.Type == "par")
{
GVW.ConnNode(F.FamID, F.IndiID);
}
else if (F.Type =="chil")
{
GVW.ConnNode(F.IndiID, F.FamID);
}
}
string x = GVW.SB.ToString();
GVW.SaveFile(outpath);
MessageBox.Show("done");
}
}
I am particularly interested in if anything could be done about the structures i don't know if how i use them in the implementation is the greatest but again it works
Thanks alot
Quick thoughts:
Nested types should not be visible.
ValueTypes (structs) should be immutable.
Fields (class variables) should not be public. Expose them via properties instead.
Check passed arguments for invalid values, like null.
It might be more readable. It's hard to read and understand.
You may study SOLID principles (http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod)
Robert C. Martin gave good presentation on Oredev 2008 about clean code (http://www.oredev.org/topmenu/video/agile/robertcmartincleancodeiiifunctions.4.5a2d30d411ee6ffd2888000779.html)
Some recomended books to read about code readability:
Kent Beck "Implemetation patterns"
Robert C Martin "Clean Code" Robert C
Martin "Agile Principles, Patterns
and Practices in C#"
I suggest you check this place out: http://refactormycode.com/.
For some quick things, your naming is the biggest thing I would start to change.
No need to use ALL-CAPS or abbreviated terms.
Also, FxCop will help with a lot of suggested changes. For example, FindIndexinArray would be named FindIndexInArray.
EDIT:
I don't know if this is a bug in your code or by-design, but in FindIndexinArray, you don't break from your loop once you find a match. Do you want the first (break) or last (no break) match in the array?