I have a custom formatted string read from a text file that has multiple occurrences of an instance of a template.
To clarify
I have a string template
--------------------
Id : {0}
Value : {1}
--------------------
I have read a text file whose content is as follows
--------------------
Id : 21
Value : Some Value 1
--------------------
--------------------
Id : 200
Value : Some Value 2
--------------------
--------------------
Id : 1
Value : Some Value 3
--------------------
--------------------
Id : 54
Value : Some Value 4
--------------------
I have class A which has 2 public properties Id and Value
class A
{
public string Id { get; set; }
public string Value { get; set; }
}
Is it possible to deserialize the whole text read from the text file to List<A>.
An approach without the "for" "foreach" or "while" loops would be better.
I have been parsing text files like this for 40 years. He is the best method
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
enum State
{
FIND_ID,
FIND_VALUE
}
const string FILENAME = #"c:\temp\test.txt";
static void Main(string[] args)
{
List<A> a_s = new List<A>();
string inputLine = "";
StreamReader reader = new StreamReader(FILENAME);
State state = State.FIND_ID;
A a = null;
while ((inputLine = reader.ReadLine()) != null)
{
inputLine = inputLine.Trim();
if (!inputLine.StartsWith("-") && inputLine.Length > 0)
{
switch (state)
{
case State.FIND_ID :
if (inputLine.StartsWith("Id"))
{
string[] inputArray = inputLine.Split(new char[] { ':' });
a = new A();
a_s.Add(a);
a.Id = inputArray[1].Trim();
state = State.FIND_VALUE;
}
break;
case State.FIND_VALUE:
if (inputLine.StartsWith("Value"))
{
string[] inputArray = inputLine.Split(new char[] { ':' });
a.Value = inputArray[1].Trim();
state = State.FIND_ID;
}
break;
}
}
}
}
}
class A
{
public string Id { get; set; }
public string Value { get; set; }
}
}
If you can modify your A class to have constructors like the following:
class A
{
public string Id { get; set; }
public string Value { get; set; }
public A() { }
public A(string s)
{
string[] vals = s.Split((new string[] { "\r\n" }), StringSplitOptions.RemoveEmptyEntries);
this.Id = vals[0].Replace("Id : ", string.Empty).Trim();
this.Value = vals[1].Replace("Value : ", string.Empty).Trim();
}
// only overridden here for printing
public override string ToString()
{
return string.Format("Id : {0}\r\nValue : {1}\r\n", this.Id, this.Value);
}
}
You could implement something of the following:
public static List<A> GetValues(string file)
{
List<string> vals = new List<string>(Regex.Split(System.IO.File.ReadAllText(file), "--------------------"));
vals.RemoveAll(delegate(string s) { return string.IsNullOrEmpty(s.Trim()); });
List<A> ret = new List<A>();
vals.ForEach(delegate(string s) { ret.Add(new A(s)); });
return ret;
}
public static void Main()
{
foreach (A a in GetValues(#"C:\somefile.txt")) {
Console.WriteLine(a);
}
}
Your original question asked to avoid loops; this doesn't have an explicit loop construct (for, foreach, do/while), but the underlying code does loop (e.g. the Regex.Split, vals.RemoveAll and vals.ForEach are all loops), so as the comments have pointed out, you can't really avoid loops in this scenario.
It should be noted that after some benchmarks, this method is surprisingly fast if the file to be read is in the exact format you have specified. As a comparison, I created a file and copy/pasted your example template (the 4 results you have posted) for a total of 1032 results and a file size of ~75k, the XML file resulted in about 65k (due to the less text of the ---), and I wrote the following benchmark test to run:
public class A
{
public string Id { get; set; }
public string Value { get; set; }
public A() { }
public A(string s)
{
string[] vals = s.Split((new string[] { "\r\n" }), StringSplitOptions.RemoveEmptyEntries);
this.Id = vals[0].Replace("Id : ", string.Empty).Trim();
this.Value = vals[1].Replace("Value : ", string.Empty).Trim();
}
public A(string id, string val)
{
this.Id = id;
this.Value = val;
}
// only overridden here for printing
public override string ToString()
{
return string.Format("Id : {0}\r\nValue : {1}\r\n", this.Id, this.Value);
}
}
public static List<A> GetValuesRegEx(string file)
{
List<string> vals = new List<string>(Regex.Split(System.IO.File.ReadAllText(file), "--------------------"));
vals.RemoveAll(delegate(string s) { return string.IsNullOrEmpty(s.Trim()); });
List<A> ret = new List<A>();
vals.ForEach(delegate(string s) { ret.Add(new A(s)); });
return ret;
}
public static List<A> GetValuesXml(string file)
{
List<A> ret = new List<A>();
System.Xml.Serialization.XmlSerializer srl = new System.Xml.Serialization.XmlSerializer(ret.GetType());
System.IO.FileStream f = new System.IO.FileStream(file,
System.IO.FileMode.OpenOrCreate,
System.IO.FileAccess.ReadWrite,
System.IO.FileShare.ReadWrite);
ret = ((List<A>)srl.Deserialize(f));
f.Close();
return ret;
}
public static List<A> GetValues(string file)
{
List<A> ret = new List<A>();
List<string> vals = new List<string>(System.IO.File.ReadAllLines(file));
for (int i = 0; i < vals.Count; ++i) {
if (vals[i].StartsWith("---") && ((i + 3) < vals.Count) && (vals[i + 3].StartsWith("---"))) {
ret.Add(new A(vals[i + 1].Replace("Id : ", string.Empty), vals[i + 2].Replace("Value : ", string.Empty)));
i += 3;
}
}
return ret;
}
public static List<A> GetValuesStream(string file)
{
List<A> ret = new List<A>();
string line = "";
System.IO.StreamReader reader = new System.IO.StreamReader(file);
int state = 0;
A a = null;
while ((line = reader.ReadLine()) != null) {
line = line.Trim();
if (!line.StartsWith("-") || line.Length > 0) {
switch (state) {
case 0:
if (line.StartsWith("Id")) {
string[] inputArray = line.Split(new char[] { ':' });
a = new A();
ret.Add(a);
a.Id = inputArray[1].Trim();
state = 1;
}
break;
case 1:
if (line.StartsWith("Value")) {
string[] inputArray = line.Split(new char[] { ':' });
a.Value = inputArray[1].Trim();
state = 0;
}
break;
}
}
}
return ret;
}
public static void Main()
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
for (int x = 0; x < 5; ++x) {
double avg = 0d;
for (int i = 0; i < 100; ++i) {
sw.Restart();
List<A> txt = GetValuesRegEx(#"C:\somefile.txt");
sw.Stop();
avg += sw.Elapsed.TotalSeconds;
}
Console.WriteLine(string.Format("avg: {0} s", (avg / 100)));
// best out of 5: 0.002380452 s
avg = 0d;
sw.Stop();
for (int i = 0; i < 100; ++i) {
sw.Restart();
List<A> txt = GetValuesXml(#"C:\somefile.xml");
sw.Stop();
avg += sw.Elapsed.TotalSeconds;
}
Console.WriteLine(string.Format("avg: {0} s", (avg / 100)));
// best out of 5: 0.002042312 s
avg = 0d;
sw.Stop();
for (int i = 0; i < 100; ++i) {
sw.Restart();
List<A> xml = GetValues(#"C:\somefile.xml");
sw.Stop();
avg += sw.Elapsed.TotalSeconds;
}
Console.WriteLine(string.Format("avg: {0} s", (avg / 100)));
// best out of 5: 0.001148025 s
avg = 0d;
sw.Stop();
for (int i = 0; i < 100; ++i) {
sw.Restart();
List<A> txt = GetValuesStream(#"C:\somefile.txt");
sw.Stop();
avg += sw.Elapsed.TotalSeconds;
}
Console.WriteLine(string.Format("avg: {0} s", (avg / 100)));
// best out of 5: 0.002459861 s
avg = 0d;
sw.Stop();
}
sw.Stop();
}
For clarity, here are the results when run on an Intel i7 # 2.2 GHz with a 5400 RPM HDD (with about 0.1% fragmentation):
GetValuesRegEx run time best average out of 5 runs: 0.002380452 s
GetValuesXml run time best average out of 5 runs: 0.002042312 s
GetValues (ReadAllLines/loop) run time best average out of 5 runs: 0.001148025 s
GetValuesStream (StreamReader/loop) run time best average out of 5 runs: 0.002459861 s
Your results may vary and this does not take into account any error handling, so you'll need to take that into account when using the code.
Hope that can help.
Related
How to make console app to read csv file that has row IsHidden( isHidden = false for to be shown )
The point is I have made everything up and running but cannot think of the logic for the true(hidden) and false(true) row to be read into console app and shows it those who should :D - sorry for my bad English :)
the code I'm using
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PreInterviewTask
{
class Program
{
static void Main(string[] args)
{
// Get the data from path.
string sampleCSV = #"C:\Users\Tomas\source\repos\PreInterviewTask\PreInterviewTask\HistoricalData\HistoricalData.csv";
string[,] values = LoadCSV(sampleCSV);
int num_rows = values.GetUpperBound(0) + 1;
int num_cols = values.GetUpperBound(1) + 1;
// Display the data to show we have it.
for (int c = 0; c < num_cols; c++)
Console.Write(values[0, c] + "\t");
//Read the data.
for (int r = 1; r < num_rows; r++)
{
// dgvValues.Rows.Add();
Console.WriteLine();
for (int c = 0; c < num_cols; c++)
{
Console.Write(values[r, c] + "\t");
}
}
Console.ReadLine();
}
private static string[,] LoadCSV(string filename)
{
// Get the file's text.
string whole_file = System.IO.File.ReadAllText(filename);
// Split into lines.
whole_file = whole_file.Replace('\n', '\r');
string[] lines = whole_file.Split(new char[] { '\r' },
StringSplitOptions.RemoveEmptyEntries);
// See how many rows and columns there are.
int num_rows = lines.Length;
int num_cols = lines[0].Split(',').Length;
// Allocate the data array.
string[,] values = new string[num_rows, num_cols];
// Load the array.
for (int r = 0; r < num_rows; r++)
{
string[] line_r = lines[r].Split(',');
for (int c = 0; c < num_cols; c++)
{
values[r, c] = line_r[c];
}
}
// Return the values.
return values;
}
}
}
the output i get :
ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references
and what should look like :
Example Output
. Company
.... About Us
....... Team
.... Mission
. References
.... Client 1
.... Client 2
See if following helps. I used your output as the input since I do not have the actual input. :
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication176
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
Menu menu = new Menu(FILENAME);
List<Menu> sortedRows = Menu.items.OrderBy(x => x).ToList();
menu.Print(sortedRows);
Console.ReadLine();
}
}
public class Menu : IComparable
{
public static List<Menu> items { get; set; }
public int ID { get; set; }
public string name { get; set; }
public int? parent { get; set; }
public Boolean hidden { get; set; }
public string[] linkUrl { get; set; }
public Menu() { }
public Menu(string filename)
{
StreamReader reader = new StreamReader(filename);
string line = "";
int rowCount = 0;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
if (line.Length > 0)
{
if (++rowCount == 1)
{
items = new List<Menu>();
}
else
{
Menu newMenu = new Menu();
items.Add(newMenu);
string[] splitArray = line.Split(new char[] { ';' }).ToArray();
newMenu.ID = int.Parse(splitArray[0]);
newMenu.name = splitArray[1];
newMenu.parent = (splitArray[2] == "NULL")? null : (int?)int.Parse(splitArray[2]);
newMenu.hidden = Boolean.Parse(splitArray[3]);
newMenu.linkUrl = splitArray[4].Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
}
}
}
}
public int CompareTo(object obj)
{
Menu other = (Menu)obj;
int min = Math.Min(this.linkUrl.Length, other.linkUrl.Length);
for (int i = 0; i < min; i++)
{
int compare = this.linkUrl[i].CompareTo(other.linkUrl[i]);
if (compare != 0) return compare;
}
return this.linkUrl.Length.CompareTo(other.linkUrl.Length);
}
public void Print(List<Menu> rows)
{
foreach (Menu menu in rows)
{
if (!menu.hidden)
{
int length = menu.linkUrl.Length - 1;
Console.WriteLine(".{0} {1}", new string('.', 3 * length), menu.name);
}
}
}
}
}
I have created an object with the attributes String, and the other is a List<String>.
I have also created a static List<MyObject> where i add then all my Objects.
Now my Problem is the second attribute is getting overridden.
For example I have 3 Objects:
Object1: "Name"; List with 3 Strings
Object2: "Name2"; List with 2 Strings
Object3: "Name3"; List with 5 Strings
If i add them now to my Object List, it looks like so
Name; List with 5 Strings
Name2; List with 5 Strings
Name3; List with 5 Strings
It override the second attributes to all the other Objects in the List.
Code:
for (int i = 0; i < 100; i++)
{
if (elo.ReadObjMask(i) > 0)
{
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
else
{
// Do nothing
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
eigenschaften.Clear();
}
}
Here is my object class:
public class EloMask
{
public string name;
public List<String> eigenschaften;
public EloMask(string iname, List<String> ieigenschaften)
{
name = iname;
eigenschaften = ieigenschaften;
}
}
The List<string> always points to the same instance because you are passing a reference to the list, not a copy. As a result, the list is cleared and filled again for each EloMask that you pass that list into.
To fix your issue, create a new list instead:
if (elo.ReadObjMask(i) > 0)
{
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
// create a new list here!!!
var eigenschaften = new List<string>();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
// clearing the list is no longer needed
}
Here is an example how you can do what you want:
public static List<Person> PersonsList = new List<Person>();
public static Random rd = new Random();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
List<string> tmpAbilities = new List<string>() {((char)rd.Next(255)).ToString(), ((char)rd.Next(255)).ToString() , ((char)rd.Next(255)).ToString() };
Person tmpPerson = new Person("TmpName_"+i,tmpAbilities);
PersonsList.Add(tmpPerson);
}
foreach (var persona in PersonsList)
{
Console.WriteLine(persona);
}
}
public class Person
{
public string Name { get; set; }
public List<string> Abilities;
public Person(string name,List<string> abilities)
{
Name = name;
Abilities = abilities;
}
public override string ToString()
{
string retVal = $"Name: {Name}\n";
foreach (var ability in Abilities)
{
retVal += $"Ability : {ability}\n";
}
return retVal;
}
}
for (int i = 0; i < 100; i++)
{
if (elo.ReadObjMask(i) > 0)
{
// Create a new listin here
eigenschaften = new List<string>();
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
else
{
// Do nothing
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
}
}
My code posted below, does not run, due to an
Error - Cannot implicitly convert type 'string' to 'int'
This error occurs to the iterator i within both my if conditions.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaleOrFemale
{
class Program
{
static void Main(string[] args)
{
string[,] names = new string [7,2] {{"Cheryl Cole","F"},{"Hilary Clinton","F"},{"Noel Gallagher","M"},{"David Cameron","M"},{"Madonna","F"},{"Sergio Aguero","M"},{"Sheik Mansour","M"}};
int mCount = 0;
int fCount = 0;
foreach ( var i in names)
{
if (names[i,1]== "M")
{
mCount += 1;
}
else if (names[i,1]=="F")
{
fCount += 1;
}
}
Console.WriteLine("mCount = {0}", mCount);
Console.WriteLine("fCount = {0}", fCount);
Console.Read();
}
}
}
Why can my array index not use the foreach Iterator in this case?
For your particular case, there is no need for foreach loop, foreach will iterate each element in your multi-dimensional array and return each element one by one. That is why you are getting the error. Since i will be string value on each iteration and you are trying to use it in array index, which can only accept an integer value.
You can fix this by using a simple for loop like:
for (var i = 0; i < names.GetLength(0); i++)
{
if (names[i, 1] == "M")
{
mCount += 1;
}
else if (names[i, 1] == "F")
{
fCount += 1;
}
}
Notice the GetLength method, it will return the length of specified dimension.
But a better option would be to use a class for your data, instead of multi-dimensional array, and even an enum for gender.
public class MyDataClass
{
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female,
Other,
}
And then you can do:
List<MyDataClass> list = new List<MyDataClass>
{
new MyDataClass {Name = "Cheryl Cole", Gender = Gender.Female},
new MyDataClass {Name = "Hilary Clinton", Gender = Gender.Female},
new MyDataClass {Name = "Noel Gallagher", Gender = Gender.Female},
new MyDataClass {Name = "David Cameron", Gender = Gender.Male},
new MyDataClass {Name = "Madonna", Gender = Gender.Female},
new MyDataClass {Name = "Sergio Aguero", Gender = Gender.Male},
new MyDataClass {Name = "Sheik Mansour", Gender = Gender.Male},
};
foreach (var item in list)
{
if (item.Gender == Gender.Male)
mCount += 1;
if (item.Gender == Gender.Female)
fCount += 1;
}
You are trying to access the array via indexes, indexes are meant to be integers.
Here when you call names[i,1], then i should be integer not string. When you have foreach ( var i in names) then i is inferred to be string because name is array of type string[,].
If you want to access each element via index try using a for loop instead
for(var i = 0; i < names.GetLength(0); i++)
{
if (names[i,1]== "M")
{
mCount += 1;
}
else if (names[i,1]=="F")
{
fCount += 1;
}
}
Keep in mind that when you use foreach you are accessing elements not the indexes.
But best way here is to define a class instead of holding data into multi-dimensional arrays.
public class Person
{
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
and the program will looks like this
var people = new List<Person>();
people.Add(new Person(){ Name = "Cheryl Cole", Gender = Gender.Female });
people.Add(new Person(){ Name = "Hilary Clinton", Gender = Gender.Female });
people.Add(new Person(){ Name = "Noel Gallagher", Gender = Gender.Male });
int mCount = 0;
int fCount = 0;
foreach (var person in people)
{
if (person.Gender == Gender.Male)
mCount += 1;
else if (person.Gender == Gender.Female)
fCount += 1;
}
Console.WriteLine("mCount = {0}", mCount);
Console.WriteLine("fCount = {0}", fCount);
Console.Read();
or you can use linq for the whole concept of counting items with specific conditions
Console.WriteLine("#Male = {0}", people.Count(x=>x.Gender == Gender.Male));
Console.WriteLine("#Female = {0}", people.Count(x=>x.Gender==Gender.Female));
Your iterator will return an array, not an int.
Try something like this:
foreach ( var i in names)
{
if (i[1]== "M")
{
mCount += 1;
}
else if (i[1]=="F")
{
fCount += 1;
}
}
Although as others mentioned, this is a sloppy way of doing it. Better to use structs and/or custom classes and/or enums.
var i dose not contain int value. So cannot access like this names[i,1].So your implementation should be changed like as follows.
foreach ( var i in names){
if (i[1] == "M"){
mCount++;
}
else if (i[1] == "F"){
fCount++;
}
}
At compile-time, the variable i is determined as a String. So using the foreach iterator isn't going to work with your current implementation.
Alternatively, you can use the for iterator with the following code:
string[,] names = new string[7, 2] { { "Cheryl Cole", "F" }, { "Hilary Clinton", "F" }, { "Noel Gallagher", "M" }, { "David Cameron", "M" }, { "Madonna", "F" }, { "Sergio Aguero", "M" }, { "Sheik Mansour", "M" } };
int mCount = 0;
int fCount = 0;
int length = names.GetLength(0);
for (int i = 0; i < length; i++)
{
if (names[i, 1] == "M")
{
mCount += 1;
}
else if (names[i, 1] == "F")
{
fCount += 1;
}
}
Console.WriteLine("mCount = {0}", mCount);
Console.WriteLine("fCount = {0}", fCount);
Console.Read();
which outputs:
mCount = 4
fCount = 3
The above code uses names.GetLength(0); in order to get the length of the array at dimension 0. In our case, this outputs 7.
Since you are using foreach to loop, your counter variable (i) is being set to a string (value) from the array...
Use a for loop instead...
for(int i=0; i< names.GetLength(0);i++)
You should explore the option of using classes and LINQ to make the code cleaner and maintainable.
using System;
using System.Collections.Generic;
using System.Linq;
public enum Gender
{
Female, Male
}
public class Person
{
public string Name { get; private set; }
public Gender Gender { get; private set; }
public Person(string name, Gender gender)
{
Name = name;
Gender = gender;
}
}
public class Program
{
public static void Main()
{
var persons = new[] { new Person("Cheryl Cole", Gender.Female), new Person("David Cameron", Gender.Male) };
var mCount = persons.Count(p => p.Gender == Gender.Male);
var fCount = persons.Count(p => p.Gender == Gender.Female);
Console.WriteLine("mCount = {0}", mCount);
Console.WriteLine("fCount = {0}", fCount);
}
}
I have inputs like following:
"10+18+12+13"
"10+18-12+13"
"2-5"
e.g. number followed by a "+" or "-"
I created class MathOper
public class MathOper
{
public int Num { get; set; }
public string Oper { get; set; } //this display the number will be operated.
}
I want to return list of MathOper as following
"10+18-12+13" will return
new MathOper(){Num=10,"+"}
new MathOper(){Num=18,"+"}
new MathOper(){Num=12,"-"}
new MathOper(){Num=13,"+"}
I tried to code that by this way:
public class MathOperCreator
{
private readonly string _mathString;//8+2-3
public MathOperCreator(string mathString)
{
this._mathString = mathString.Trim();
}
public List<MathOper> Create()
{
var lMo = new List<MathOper>();
int l = this._mathString.Length;//5
for (int i = 0; i < l; i = i + 2)
{
char n = _mathString[i];
int n1 = int.Parse(n.ToString(CultureInfo.InvariantCulture));
string o1 = "+";
if (i > 0)
{
o1 = _mathString[i - 1].ToString(CultureInfo.InvariantCulture);
}
var mo = new MathOper { Num = n1, Oper = o1 };
lMo.Add(mo);
}
return lMo;
}
}
I found that it is only for number with one char, if the number is two char ,such as 18 , it doesn't work.
Please advice how implement described functionality?
This is a tested Solution
//Model class
public class MathOperation
{
public Int32 Num { get; set; }
public String Operation { get; set; }
}
String testData = "10+18+12-13";
String[] GetNumbers = testData.Split(new Char[] { '+', '-' });
String[] GetOperators = Regex.Split(testData, "[0-9]+");
//remove empty entries in operator
GetOperators = GetOperators.Where(x => !string.IsNullOrEmpty(x)).ToArray();
List<MathOperation> list = new List<MathOperation>();
MathOperation mathOperation = new MathOperation();
for (int i = 0; i < GetNumbers.Count(); i++)
{
mathOperation.Num = Convert.ToInt32(GetNumbers[i]);
mathOperation.Operation = (i>GetOperators.Length)? GetOperators[i] : null;
list.Add(mathOperation);
}
This will give a list like
{Num=10,"+"}
{Num=18,"+"}
{Num=12,"-"}
{Num=13,"null"} //as in my test string there is no char after 13
if you dont want a null always a + then
mathOperation.Operation = (i>GetOperators.Length)? GetOperators[i] : "+";
then This will give a list like
{Num=10,"+"}
{Num=18,"+"}
{Num=12,"-"}
{Num=13,"+"} //as in my test string there is no char after 13
This works for me.
//you can change in to MathOper
List<Tuple<int, char>> result = new List<Tuple<int, char>>();
string _mathString = "2-2+10-13"; //Test
char sign = '-';
if (_mathString[0] != '-') //checking the first sign
{
sign = '+';
}
while (_mathString.Length > 0)
{
int nextPl = _mathString.IndexOf('+');
int nextMn = _mathString.IndexOf('-');
if (nextPl == -1 && nextMn == -1) //condition when _mathString contains only number
{
result.Add(new Tuple<int, char>(int.Parse(_mathString), sign));
break;
}
else
{
//getting the end position of first number
int end = nextPl == -1 ? nextMn : (nextMn == -1 ? nextPl : (Math.Min(nextPl, nextMn)));
//retrieving first number
result.Add(new Tuple<int, char>(int.Parse(_mathString.Substring(0, end)), sign));
_mathString = _mathString.Remove(0, end);
//retrieving next sign
sign = _mathString[0];
_mathString = _mathString.Remove(0, 1);
}
}
Try this, I think it works the way you wanted: (Easy to understand solution but not optimal)
public class MathOper
{
public int Num { get; set; }
public string Oper { get; set; } //this display the number will be operated.
}
public class MathOperCreator
{
public readonly string _mathString;//8+2-3
public MathOperCreator(string mathString)
{
this._mathString = mathString.Trim();
}
public List<MathOper> Create()
{
var lMo = new List<MathOper>();
int l = this._mathString.Length;//5
string _mathStringTemp;
char[] charArr = _mathString.ToCharArray();
if (charArr[0] != '+' || charArr[0] != '-')
{
_mathStringTemp = "+"+_mathString;
} else
{
_mathStringTemp = _mathString;
}
char[] delimitersNumb = new char[] { '+', '-' };
string[] numbers = _mathStringTemp.Split(delimitersNumb,
StringSplitOptions.RemoveEmptyEntries);
string[] operators = new string[numbers.Length];
int count = 0;
foreach (char c in _mathStringTemp)
{
if (c == '+' || c == '-')
{
operators[count] = c.ToString();
count++;
}
}
for(int i=0; i < numbers.Length; i++)
{
lMo.Add(new MathOper(){Num = int.Parse(numbers[i]), Oper = operators[i]});
Console.WriteLine(operators[i]+" "+numbers[i]);
}
return lMo;
}
}
Question is simple
is this code
public Dictionary<string, SomeObject> values = new Dictionary<string, SomeObject>();
void Function()
{
values["foo"].a = "bar a";
values["foo"].b = "bar b";
values["foo"].c = "bar c";
values["foo"].d = "bar d";
}
same fast as this code
public Dictionary<string, SomeObject> values = new Dictionary<string, SomeObject>();
void Function()
{
var someObject = values["foo"];
someObject.a = "bar a";
someObject.b = "bar b";
someObject.c = "bar c";
someObject.d = "bar d";
}
common sense tell me that it should be faster to look up the reference in dictionary once and store it somewhere so that it doesn't need to be looked up multiple times, but I don't really know how dictionary works.
So is it faster or not? And why?
Yep, you are correct. Your first approach does the dictionary lookup 4 times, while the second does it once. The second is definitely better.
However, in real life, a dictionary lookup is ridiculously fast, so unless you've got a massive dictionary the difference won't be noticeable, maybe not even measurable.
Joe's absolutely right, but as if it wasn't enough I've done a simple obvious test:
static void Main(string[] args)
{
var dict = new Dictionary<string, Foo>();
var number = 10000000;
for (int i = 0; i < number; i++)
{
dict[i.ToString()] = new Foo();
}
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < number; i++)
{
var key = i.ToString();
dict[key].A = "a";
dict[key].B = "b";
dict[key].C = "c";
dict[key].D = "d";
}
watch.Stop();
Console.Out.WriteLine(watch.ElapsedMilliseconds);
watch.Reset();
watch.Start();
for (int i = 0; i < number; i++)
{
var key = i.ToString();
var foo = dict[key];
foo.A = "a";
foo.B = "b";
foo.C = "c";
foo.D = "d";
}
watch.Stop();
Console.Out.WriteLine(watch.ElapsedMilliseconds);
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
}
On my machine this outputs to:
3423
2113
Press any key to continue . . .
Having only 1 lookup definitely reduces the total time for big numbers.
I was curious. The following unit test, (possibly) shows that the second method is about 25% faster. (121 ms vs 91 ms). Going from 6 fields to 2 narrowed the gap, 40 ms vs 33 ms. I say possibly since I wrote this fairly quickly and I'm not convinced it's immune to measuring some side effect, but it shows the expected behavior, so why question it. (hah).
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics;
namespace TestProject1
{
public class DataObject
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
public string E { get; set; }
public string F { get; set; }
}
[TestClass]
public class UnitTest1
{
public static Dictionary<string, DataObject> dict = new Dictionary<string, DataObject>();
static string lookie;
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext) {
Random rand = new Random(123545);
for (int i = 0; i < 10000; i++)
{
string key = rand.NextDouble().ToString();
DataObject dob = new DataObject();
dict.Add(key, dob);
if (i == 4567)
lookie = key;
}
}
[TestMethod]
public void TestMethod()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int j = 0; j < 100000; j++)
{
dict[lookie].A = "val" + j;
dict[lookie].B = "val" + j;
dict[lookie].C = "val" + j;
dict[lookie].D = "val" + j;
dict[lookie].E = "val" + j;
dict[lookie].F = "val" + j;
}
sw.Stop();
System.Diagnostics.Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int j = 0; j < 100000; j++)
{
DataObject dob = dict[lookie];
dob.A = "val" + j;
dob.B = "val" + j;
dob.C = "val" + j;
dob.D = "val" + j;
dob.E = "val" +j;
dob.F = "val" +j;
}
sw.Stop();
System.Diagnostics.Debug.WriteLine(sw.ElapsedMilliseconds);
}
}
}