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;
}
}
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);
}
}
}
}
}
for(int i = 0; i < Rect.rectangles.Count; i++)
{
if (Rect.rectangles[i].transform != null && Rect.rectangles[i].transform != "")
{
string firstNumber = Rect.rectangles[i].transform.Substring(18, 10);
string secondNumber = Rect.rectangles[i].transform.Substring(7, 10);
double a = Convert.ToDouble(firstNumber);
double b = Convert.ToDouble(secondNumber);
}
}
The first number is 0.49052715 and the Convert.ToDouble of a is fine.
But when it's getting to the line:
double b = Convert.ToDouble(secondNumber);
I'm getting exception:
FormatException: Unknown char: ,
But there is no any char/s at all the string in secondNumber is: 0.87142591
This is the whole string in transform: matrix(0.87142591,0.49052715,-0.49052715,0.87142591,0,0)
And i'm extracting the first two numbers: 0.49052715 and 0.87142591 and then trying to convert them to double. But getting the exception.
The code including the Rect.rectangles definition:
private void ParseSvgMap()
{
XDocument document = XDocument.Load(#"C:\Users\mysvg\Documents\my.svg");
Rect.rectangles = document.Descendants().Where(x => x.Name.LocalName == "rect").Select(x => new Rect()
{
style = Rect.GetStyle((string)x.Attribute("style")),
id = (string)x.Attribute("id"),
width = (double)x.Attribute("width"),
height = (double)x.Attribute("width"),
x = (double?)x.Attribute("width"),
y = (double?)x.Attribute("width"),
transform = x.Attribute("transform") == null ? "" : (string)x.Attribute("transform")
}).ToList();
for(int i = 0; i < Rect.rectangles.Count; i++)
{
if (Rect.rectangles[i].transform != null && Rect.rectangles[i].transform != "")
{
string firstNumber = Rect.rectangles[i].transform.Substring(18, 10);
string secondNumber = Rect.rectangles[i].transform.Substring(7, 10);
double a = Convert.ToDouble(firstNumber);
double b = Convert.ToDouble(secondNumber);
float degree = FindDegree(a, b);
}
}
}
public static float FindDegree(double a, double b)
{
float value = (float)((System.Math.Atan2(a, b) / System.Math.PI) * 180f);
if (value < 0) value += 360f;
return value;
}
public class Rect
{
public static List<Rect> rectangles { get; set; }
public Dictionary<string, string> style { get; set; }
public string id { get; set; }
public double width { get; set; }
public double height { get; set; }
public double? x { get; set; }
public double? y { get; set; }
public string transform { get; set; }
public double degree { get; set; }
public static Dictionary<string, string> GetStyle(string styles)
{
string pattern = #"(?'name'[^:]+):(?'value'.*)";
string[] splitArray = styles.Split(new char[] { ';' });
Dictionary<string, string> style = splitArray.Select(x => Regex.Match(x, pattern))
.GroupBy(x => x.Groups["name"].Value, y => y.Groups["value"].Value)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
return style;
}
}
Code like .Substring(18, 10) is hopelessly brittle and (as you have discovered) is an accident waiting to happen. Numbers aren't guaranteed to be the same length. Just look at the sample data you provided.
You should use something like a regex to extract the numbers from the strings before attempting to parse them.
var regex = new Regex(#"matrix\((?<num>-?(?:\d|\.)+)(?:,(?<num>-?(?:\d|\.)+))*\)");
var data = "matrix(0.87142591,0.49052715,-0.49052715,0.87142591,0,0)";
var values = regex
.Matches(data)
.Cast<Match>()
.SelectMany(m => m.Groups["num"]
.Captures
.Cast<Capture>()
.Select(c=>c.Value))
.Select(double.Parse)
.ToList();
Then pick out the numbers you want with values[0] and values[1].
Its doomed to fail.
I would use following regex:
string test = "matrix(0.1234,0.23233433,0.34,-3343,-3.33)";
Regex reg = new Regex("[-]?[0-9]+(\\.[0-9]+)?");
MatchCollection coll = reg.Matches(test);
You can write a method along the lines of :
public double fixString(string incoming)
{
Double a;
if(incoming.Contains(','))
{
string fixedNum = incoming.Remove(incoming.IndexOf(','));
a = Convert.ToDouble(fixedNum);
return a;
}
else
{
a = Convert.ToDouble(incoming);
return a;
}
}
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);
}
}
I am working my way through a C# course so this question is more academic than real world.
I want to search for string in an array of classes that includes one or more subsidiary (embedded)array of classes. I want to be able to search both the parent and the subsidiary arrays. I've been trying the NET Framework Class Library Array Methods but am getting nowhere - you'll see my Array.IndexOf returns -1. I've pasted my code below and would be grateful for any advice. I know there are more sophisticated ways to do this but I need to stick with arrays for the time being. Thanks in advance.
using System;
namespace Nested_Arrays
{
public class Program
{
static void Main(string[] args)
{
Student[] StudentArray = new Student[3];
StudentSubjects[] StudentSubjectsArray = new StudentSubjects[3];
StudentArray[0] = new Student() {
StudentName = "Peter", StudentLocation = "Australia"
};
StudentArray[0].StudentSubjectsArray[0] = new StudentSubjects() {
SubjectName = "Calculus", StudentsResult = "Pass"
};
StudentArray[0].StudentSubjectsArray[1] = new StudentSubjects() {
SubjectName = "Algebra", StudentsResult = "Pass"
};
StudentArray[0].StudentSubjectsArray[2] = new StudentSubjects() {
SubjectName = "Statistics", StudentsResult = "Pass"
};
StudentArray[1] = new Student() {
StudentName = "Michelle", StudentLocation = "France"
};
StudentArray[1].StudentSubjectsArray[0] = new StudentSubjects() {
SubjectName = "Engineering", StudentsResult = "Pass"
};
StudentArray[1].StudentSubjectsArray[1] = new StudentSubjects() {
SubjectName = "Algebra", StudentsResult = "Pass"
};
StudentArray[1].StudentSubjectsArray[2] = new StudentSubjects() {
SubjectName = "Aramaic", StudentsResult = "Pass"
};
StudentArray[2] = new Student() {
StudentName = "Mitchell", StudentLocation = "Canada"
};
StudentArray[2].StudentSubjectsArray[0] = new StudentSubjects() {
SubjectName = "Engineering", StudentsResult = "Pass"
};
StudentArray[2].StudentSubjectsArray[1] = new StudentSubjects() {
SubjectName = "Greek", StudentsResult = "Pass"
};
StudentArray[2].StudentSubjectsArray[2] = new StudentSubjects() {
SubjectName = "Aramaic", StudentsResult = "Pass"
};
for (int i = 0; i < 3; i++)
{
Console.WriteLine($ "\n{i + 1,3} {StudentArray[i].StudentName,-10} {StudentArray[i].StudentLocation,-15}");
for (int j = 0; j < 3; j++) {
Console.WriteLine($ "{j + 1,6} {StudentArray[i].StudentSubjectsArray[j].SubjectName,-15} {StudentArray[i].StudentSubjectsArray[j].StudentsResult,-10}");
}
}
String searchString = "Mitchell";
Console.WriteLine($ "\n We are searching for \"{searchString}\"");
int index = Array.IndexOf(StudentArray, searchString);
Console.WriteLine(" The first occurrence of \"{0}\" is at index {1}.", searchString, index);
searchString = "Aramaic";
Console.WriteLine($ "\n We are searching for \"{searchString}\"");
index = Array.IndexOf(StudentSubjectsArray, searchString);
Console.WriteLine(" The first occurrence of \"{0}\" is at index {1}.", searchString, index);
Console.WriteLine($ "\n");
}
public class Student
{
private string studentName;
public string StudentName {
get {
return studentName;
}
set {
studentName = value;
}
}
private string studentLocation;
public string StudentLocation {
get {
return studentLocation;
}
set {
studentLocation = value;
}
}
private StudentSubjects[] studentSubjectsArray = new StudentSubjects[3];
public StudentSubjects[] StudentSubjectsArray {
get {
return studentSubjectsArray;
}
set {
studentSubjectsArray = value;
}
}
//Constructor
public Student() {}
}
public class StudentSubjects {
private string subjectName;
public string SubjectName {
get {
return subjectName;
}
set {
subjectName = value;
}
}
private string studentsResult;
public string StudentsResult {
get {
return studentsResult;
}
set {
studentsResult = value;
}
}
//Constructor
public StudentSubjects() {}
}
}
}
Array.IndexOf searches for the specified object and returns the index of its first occurrence in a one-dimensional array.
In your case you need to search for object properties and find an index of matched object. I suggest use use Linq to search for an object that matches property value and then search for an index of an object (as follows).
var item = StudentArray.FirstOrDefault(x=> x.StudentName.Equals(searchString, StringComparison.CurrentCultureIgnoreCase)
|| x.StudentSubjectsArray.Any(s=>s.SubjectName.Equals(searchString, StringComparison.CurrentCultureIgnoreCase)));
if(item!= null)
index = Array.IndexOf(StudentArray, item);
Check this working demo
I think that I've finally did it , it took me a wile, because i'm a novice..
So,
1) first off all I added two lists of strings in each Class
like:
public class StudentSubjects
{
public List<String> studentsubjectlist = new List<string>();
and
public class StudentSubjects
{
public List<String> studentsubjectlist = new List<string>();
then you will need to add all your propertys to those lists, by using "Set" accessor of the property:
public string StudentName
{
get
{
return studentName;
}
set
{
studentName = value;
studentslist.Add(value);
}
}
If you do debugging in this stage you will see there is a list in every object in the array, that contains all the propertys of the Object.
2) Your index has to be an Array because it will contain more then one number:
int[] index = new int[StudentArray.Length];
3) Now we can loop.
int i;
int j;
int x;
for ( i = 0; i < StudentArray.Length; i++)
{
for ( j = 0; j < StudentArray[i].studentslist.Count; j++)
{
if (StudentArray[i].studentslist[j] == searchString)
{
index[0] = i;
index[1] = -1;
index[2] = j;
break;
}
}
for ( x = 0, j = 0; j < StudentArray[i].StudentSubjectsArray[x].studentsubjectlist.Count; j++)
{
if (StudentArray[i].StudentSubjectsArray[x].studentsubjectlist[j] == searchString)
{
index[0] = i;
index[1] = x;
index[2] = j;
break;
}
else if (j == StudentArray[i].StudentSubjectsArray [x].studentsubjectlist.Count)
{
x++;
j = 0;
}
}
}
4)Pay attention that:
int i is a index of the Student in the Array,
int x is a index of the StudentSubject, if searchword is inside of Student object and not inside StudentSubject, x will return -1.
int j is a index of property inside of List.
5) You will also need to change your Console.Writeline to:
Console.WriteLine( "\n We are searching for \"{searchString}\"");
Console.WriteLine(" The first occurrence of \"{0}\" is at index {1}.{2}. {3}.", searchString, index[0], index[1], index[2]);
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.