I am working on an assignment which stores data from .csv file into array. I have used for(int i = 0; i < data.Length; i++), but i++ is unreachable. Have a look on the code you will get to know. The problem is in storing only perhaps. Help me if you can.
Thanks
static void Load(string[] EmployeeNumbers, string[] EmployeeNames, string[] RegistrationNumbers, float[] EngineCapacityArray,
int[] StartKilometresArray, int[] EndKilometresArray, string[] TripDescriptions, bool[] PassengerCarriedArray,
ref int NextAvailablePosition, ref int RecordCount, ref int CurrentRecord)
{
string str = "";
FileStream fin;
string[] data;
bool tval = false;
// Open the input file
try
{
fin = new FileStream("carallowance.csv", FileMode.Open);
}
catch (IOException exc)
{
Console.WriteLine(exc.Message);
return;
}
// Read each line of the file
StreamReader fstr_in = new StreamReader(fin);
try
{
while ((str = fstr_in.ReadLine()) != null)
{
// Separate the line into the name and age
data = str.Split(';');
if (data.Length == 8)
{
Console.WriteLine("Error: Could not load data from the file. Possibly incorrect format.");
}
for (int i = 0; i < data.Length; i++)
{
EmployeeNumbers[NextAvailablePosition] = data[0];
EmployeeNames[NextAvailablePosition] = data[1];
RegistrationNumbers[NextAvailablePosition] = data[2];
tval = float.TryParse(data[3], out EngineCapacityArray[NextAvailablePosition]);
tval = int.TryParse(data[4], out StartKilometresArray[NextAvailablePosition]);
tval = int.TryParse(data[5], out EndKilometresArray[NextAvailablePosition]);
TripDescriptions[NextAvailablePosition] = data[6];
tval = bool.TryParse(data[7], out PassengerCarriedArray[NextAvailablePosition]);
CurrentRecord = NextAvailablePosition;
NextAvailablePosition++;
RecordCount++;
Console.WriteLine("Your file is sucessfully loaded.");
break;
}
}
}
catch (IOException exc)
{
Console.WriteLine(exc.Message);
}
// Close the file
fstr_in.Close();
}
It's unreachable because of the break; at the end of the loop. That forces the for loop to stop executing after the first time around. If you run this in a console project, it'll only put out a 0.
private static void Main(string[] args)
{
for (int i = 0; i < 2; i++)
{
Console.WriteLine(i.ToString());
break;
}
}
Perhaps the code review stackexchange would be better. There's a number of issues here.
First we can simplify using a framework callto File.ReadAllLines(...). That will give you a sequence of all lines in the file. Then you want to transform that into a sequence of arrays (split on ','). That's straightforward:
var splitLines = File.ReadAllLines("\path")
.Select(line => line.Split(new char[] { ',' }));
Now you can just iterate over splitLines with a foreach.
(I do notice that you seem to be setting values into the arrays that are passed in. Try to not get into the habit of doing that. These kinds of side effects and abuse of reference params is prone to becoming very brittle.)
Then this seems very odd:
if (data.Length == 8)
{
Console.WriteLine("...");
}
I suspect that you just have a typo in your comparison operator (should be !=). If you don't care about writing to the console on bad data, you can simply just filter out the bad data after the transformation. That looks like:
var splitLines = File.ReadAllLines("\path")
.Select(line => line.Split(new char[] { ',' }))
.Where(data => data.Length == 8);
Now recall that [int/float].TryParse(s, out v) will set v to be the value that was parsed, or the default value for the type, and return true if the parse was successful. That "or default" is important here. That means that you're stuffing bad/invalid values if they can't be parsed, and you're doing nothing with tval.
Instead of all of that, consider an object/type that represents a record from your dataset. It looks like you're trying to track employee mileage from a csv table. That looks something like:
public class MileageRecord
{
public string Name { get; set; }
/* More properties */
public MileageRecord FromCSV(string[] data)
{
/* try parsing, if not then log errs to file and return null */
}
}
Now you've gotten rid of all of your side effects and the whole thing is cleaner. Loading all this data from file is as straightforward as this:
public static IEnumerable<MileageRecord> Load()
{
return File.ReadAllLines("\path")
//.Skip(1) // if first line of the file is column headers
.Select(line => line.Split(new char[] { ',' }))
.Where(data => data.Length == 8)
.Select(data => MileageRecord.FromCSV(data))
.Where(mileage => mileage != null);
}
This piece of code:
NextAvailablePosition++;
RecordCount++;
Console.WriteLine("Your file is sucessfully loaded.");
break; // <-- this instruction
}
Takes you out of the for loop without the possibility to increment i value.
Problem : you are supposed to add break statement inside the if condition(which is inside the while loop) , so that if the data Length does not match with 8 then it will break/come out from loop. but you have mistakenly added break inside the for-loop.that why it only executed for 1st time and comesout of the loop.
Solution : Move the break statement from for loop to if-blcok inside the while loop.
Try This:
Step 1: Remove the break statement from for-loop.
CurrentRecord = NextAvailablePosition;
NextAvailablePosition++;
RecordCount++;
Console.WriteLine("Your file is sucessfully loaded.");
// break; //move this statement to inside the if block
Step 2: place the break statement in if-block inside while loop.
if (data.Length == 8)
{
Console.WriteLine("Error: Could not load data from the file. Possibly incorrect format.");
break;
}
Suggestion : you can re-write your code using File.ReadAllLines() method to avoid the complexity as below :
static void Load(string[] EmployeeNumbers, string[] EmployeeNames, string[] RegistrationNumbers, float[] EngineCapacityArray,
int[] StartKilometresArray, int[] EndKilometresArray, string[] TripDescriptions, bool[] PassengerCarriedArray,
ref int NextAvailablePosition, ref int RecordCount, ref int CurrentRecord)
{
string str = "";
string[] data;
bool tval = false;
String [] strLines=File.ReadAllLines("carallowance.csv");
for(int i=0;i<strLines.Length;i++)
{
str=strLines[i];
data = str.Split(';');
if (data.Length == 8)
{
Console.WriteLine("Error: Could not load data from the file. Possibly incorrect format.");
break;
}//End of if block
else
{
EmployeeNumbers[NextAvailablePosition] = data[0];
EmployeeNames[NextAvailablePosition] = data[1];
RegistrationNumbers[NextAvailablePosition] = data[2];
tval = float.TryParse(data[3], out EngineCapacityArray[NextAvailablePosition]);
tval = int.TryParse(data[4], out StartKilometresArray[NextAvailablePosition]);
tval = int.TryParse(data[5], out EndKilometresArray[NextAvailablePosition]);
TripDescriptions[NextAvailablePosition] = data[6];
tval = bool.TryParse(data[7], out PassengerCarriedArray[NextAvailablePosition]);
CurrentRecord = NextAvailablePosition;
NextAvailablePosition++;
RecordCount++;
} //End of else block
} //End of for loop
Console.WriteLine("Your file is sucessfully loaded.");
} //End of function
Related
I have a text file. I need to find the total count of line numbers which starts with "3" and the total count is already available in the file which is available in the position line starts with "7200" - Position starts with 05 and length is 6. Similar way. Total amount also available in the line starts with "7200" - Position starts with 21 and length is 12.
211 87236486287346872837468724682871238483XYZ BANK
1200ABCDEF 8128361287AXTAKJ COLL
3270210000893281012870095628 00002500 8981273687jhgsjhdg
3270210000896281712870095628 00002500 1231273687jhgajhdj
3270210000891286712870095628 00002500 4561273687cxvnmbal
3270210000899283612870095628 00002500 7891273687nmkdkjhk
720000000400021000080000000100000000000000008128361287
9000001000001000000010002100008000000010000000000000000
For example : in my file total count of line starts with 3 is available in line starts with "7" i.e. "000004"
Total amount is in line starts with "7" i.e. "000000010000"
Currently I am using my below c# code to loop the entire file and navigate to line starts with 7 and read the values which are available in the above mentioned positions, but is taking too much of time due to file records count might be too big like 200K
foreach (var line in FileLines)
{
//// If line length is zero, then do nothing
if (line.Length == 0)
{
continue;
}
switch (line.Substring(1, 1))
{
case 7:
totalCount = int.Parse(line.Substring(4, 6));
TotalAmount = line.Substring(20, 12);
break;
default:
throw new Exception;
}
}
is there any way I can able to rewrite my code using LINQ, so that I get little better performance?
Here is the Linq statement. What would make this more efficient is that it uses Reverse since you mention that the information you're looking for is in the footer.
static void Main(string[] args)
{
var path = Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"TextFile.txt");
try
{
var count =
int.Parse(
File.ReadAllLines(path)
.Reverse()
.First(line => line.Any() && (line.First() == '7'))
.Substring(4, 6));
Console.WriteLine($"Count = {count}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.Assert(false, ex.Message);
}
}
EDIT
You have asked a great question about the performance. The great thing is that we don't have to speculate or guess! There is always a way to measure performance.
Here's the benchmark I just put together. And look, I did it really quickly so if anyone spots something I missed please point it out. But here's what I get:
static void Main(string[] args)
{
var path = Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"TextFile.txt");
try
{
// 200K lines of random guids
List<string> builder =
Enumerable.Range(0, 200000)
.Select(n => $"{{{System.Guid.NewGuid().ToString()}}}")
.ToList();
var footer =
File.ReadAllLines(path);
builder.AddRange(footer);
var FileLines = builder.ToArray();
var benchmark = new System.Diagnostics.Stopwatch();
benchmark.Start();
int totalCount = int.MinValue;
foreach (var line in FileLines)
{
//// If line length is zero, then do nothing
if (line.Length == 0)
{
continue;
}
// Original code from post
// switch (line.Substring(1, 1))
// Should be:
switch (line.Substring(0, 1))
{
case "7":
totalCount = int.Parse(line.Substring(4, 6));
// This is another issue!! Breaking from the switch DOESN'T break from the loop
break;
// SHOULD BE: goto breakFromInner;
// One of the few good reasons to use a goto statement!!
}
}
benchmark.Stop();
Console.WriteLine($"200K lines using Original code: Elapsed = {benchmark.Elapsed}");
Console.WriteLine($"Count = {totalCount}");
benchmark.Restart();
for (int i = FileLines.Length - 1; i >= 0; i--)
{
var line = FileLines[i];
//// If line length is zero, then do nothing
if (line.Length == 0)
{
continue;
}
// Original code from post
// switch (line.Substring(1, 1))
// Should be:
switch (line.Substring(0, 1))
{
case "7":
totalCount = int.Parse(line.Substring(4, 6));
// One of the few good reasons to use a goto statement!!
goto breakFromInner;
}
}
// See note
breakFromInner:
benchmark.Stop();
Console.WriteLine($"200K lines using Original code with reverse: Elapsed = {benchmark.Elapsed}");
Console.WriteLine($"Count = {totalCount}");
benchmark.Restart();
var count =
int.Parse(
FileLines
.Reverse()
.First(line => line.Any() && (line.First() == '7'))
.Substring(4, 6));
benchmark.Stop();
Console.WriteLine($"200K lines using Linq with Reverse: Elapsed = {benchmark.Elapsed}");
Console.WriteLine($"Count = {count}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.Assert(false, ex.Message);
}
}
The code below is supposed to read a text file and count all ASCII characters in the file and add up the frequency. Then, it has to write the character, ASCII value and frequency to an output file. The code is below:
class CharacterFrequency
{
char ch;
int frequency;
public char getCharacter()
{
return ch;
}
public void setCharacter(char ch)
{
this.ch = ch;
}
public int getfrequency()
{
return frequency;
}
public void setfrequency(int frequency)
{
this.frequency = frequency;
}
static void Main()
{
Console.WriteLine("Enter the file path");
var InputFileName = Console.ReadLine();
Console.WriteLine("Enter the outputfile name");
var OutputFileName = Console.ReadLine();
StreamWriter streamWriter = new StreamWriter(OutputFileName);
string data = File.ReadAllText(InputFileName);
ArrayList al = new ArrayList();
//create two for loops to traverse through the arraylist and compare
for (int i = 0; i < data.Length; i++)
{
int k = 0;
int f = 0;
for (int j = 0; j < data.Length; j++)
{
if (data[i].Equals(data[j]))
{
f++;
}
}
if (!al.Contains(data[i]))
{
al.Add(data[i] + "(" + (int)data[i] + ")" + f + " ");
}
else
{
k++;
}
//i added the below if statement but it did not fix the issue
foreach (var item in al)
{
streamWriter.WriteLine(item);
}
}
streamWriter.Close();
}
}
The code compiles and runs perfectly fine, but the output file is not correct. It is adding letters that have already been reviewed. I've added an image with the output file showing the incorrect output it is creating. --> enter image description here
How do I check if a character already exists in the array list? The way I am using is not working properly and I have been working on this for a few weeks now to no success. I have tried using the debugger but this issue will not show up there as the code still runs and compiles correctly.
An ArrayList is not well suited for this task, and in fact ArrayLists are not really used anymore. If someone is telling you that you have to do this with an ArrayList
A dictionary would be a much better container for this data. You can use the character as the key, and the count as the value.
Here's one way to do this:
var inputPath = #"c:\temp\temp.txt";
var outputPath = #"c:\temp\results.txt";
var data = new Dictionary<char, int>();
// For each character in the file, add it to the dictionary
// or increment the count if it already exists
foreach (var character in File.ReadAllText(inputPath))
{
if (data.ContainsKey(character)) data[character]++;
else data.Add(character, 1);
}
// Create our results summary
var results = data.ToList()
.Select(item => $"{item.Key} ({(int) item.Key}) {item.Value}");
// Write results to output file
File.WriteAllLines(outputPath, results);
If you have to use an ArrayList (which no one ever uses anymore, but you say have you to for some reason), it would only be useful for storing the results but not keeping track of the counts.
One way to use an ArrayList would be in combination with the Linq extension methods Distinct and Count (first to find all distinct characters, and next to get the count of each one):
foreach (var chr in data.Distinct())
{
al.Add($"{chr} ({(int) chr}) {data.Count(c => c == chr)}");
}
Your algorithm works, but you are duplicating the output as you are writing to the file inside the loop, that is why you are seeing duplicates in the result. If you move the code outside the loop, it should be ok.
foreach (var item in al)
{
streamWriter.WriteLine(item);
}
I would suggest that your algorithm while correct will behave poorly for performance, you are doing too many unnecessary comparisons, perhaps you should read/check more about using dictionaries to store the results.
Explanation: The task itself is that we have 13 strings (stored in the sor[] array) like the one in the title or 'EEENKDDDDKKKNNKDK'
and we have to shorten it in a way that if there's two or more of the same letter next to eachother then we have to write it in the form of 'NumberoflettersLetter'
So by this rule, 'EEENKDDDDKKKNNKDK' would become '3ENK4D3K2NKDK'
using System;
public class Program
{
public static void Main(string[] args)
{
string[] sor = new string[] { "EEENKDDDDKKKNNKDK", "'EEDDDNE'" };
char holder;
int counter = 0;
string temporary;
int indexholder;
for (int i = 0; i < sor.Length; i++)
{
for (int q = 0; q < sor[i].Length; q++)
{
holder = sor[i][q];
indexholder = q;
counter = 0;
while (sor[i][q] == holder)
{
q++;
counter++;
}
if (counter > 1)
{
temporary = Convert.ToString(counter) + holder;
sor[i].Replace(sor[i].Substring(indexholder, q), temporary); // EX here
}
}
}
Console.ReadLine();
}
}
Sorry I didn't make the error clear, it says that :
"The value of index and length has to represent a place inside the string (System.ArgumentOutOfRangeException) - name of parameter: length"
...but I have no clue what's wrong with it, maybe it's a tiny little mistake, maybe the whole thing is messed up, so this is why I'd like someone to help me with this D:
(Ps 'indexholder' is there because i need it for another exercise)
EDIT:
'sor' is the string array that holds these strings (there are 13 of them) like the one mentioned in the title or in the example
You can use regex for this:
Regex.Replace("EEENKDDDDKKKNNKDK", #"(.)\1+", m => $"{m.Length}{m.Groups[1].Value}")
Explanation:
(.) matches any character and puts it in group #1
\1+ matches group #1 as many times can it can
Shortening the same string inplace is more difficult then construction a new one while iterating the old one char by char. If you plan to iteratively add to a string it is better to use the StringBuilder - class instead of adding directly to a string (performance reasons).
You can streamline your approach by using IEnumerable.Aggregate function wich does the iteration on one string for you automatically:
using System;
using System.Linq;
using System.Text;
public class Program
{
public static string RunLengthEncode(string s)
{
if (string.IsNullOrEmpty(s)) // avoid null ref ex and do simple case
return "";
// we need a "state" between the differenc chars of s that we store here:
char curr_c = s[0]; // our current char, we start with the 1st one
int count = 0; // our char counter, we start with 0 as it will be
// incremented as soon as it is processed by Aggregate
// ( and then incremented to 1)
var agg = s.Aggregate(new StringBuilder(), (acc, c) => // StringBuilder
// performs better for multiple string-"additions" then string itself
{
if (c == curr_c)
count++; // same char, increment
else
{
// other char
if (count > 1) // store count if > 1
acc.AppendFormat("{0}", count);
acc.Append(curr_c); // store char
curr_c = c; // set current char to new one
count = 1; // startcount now is 1
}
return acc;
});
// add last things
if (count > 1) // store count if > 1
agg.AppendFormat("{0}", count);
agg.Append(curr_c); // store char
return agg.ToString(); // return the "simple" string
}
Test with
public static void Main(string[] args)
{
Console.WriteLine(RunLengthEncode("'EEENKDDDDKKKNNKDK' "));
Console.ReadLine();
}
}
Output for "'EEENKDDDDKKKNNKDK' ":
'3ENK4D3K2NKDK'
Your approach without using the same string is more like this:
var data = "'EEENKDDDDKKKNNKDK' ";
char curr_c = '\x0'; // avoid unasssinged warning
int count = 0; // counter for the curr_c occurences in row
string result = string.Empty; // resulting string
foreach (var c in data) // process every character of data in order
{
if (c != curr_c) // new character found
{
if (count > 1) // more then 1, add count as string and the char
result += Convert.ToString(count) + curr_c;
else if (count > 0) // avoid initial `\x0` being put into string
result += curr_c;
curr_c = c; // remember new character
count = 1; // so far we found this one
}
else
count++; // not new, increment counter
}
// add the last counted char as well
if (count > 1)
result += Convert.ToString(count) + curr_c;
else
result += curr_c;
// output
Console.WriteLine(data + " ==> " + result);
Output:
'EEENKDDDDKKKNNKDK' ==> '3ENK4D3K2NKDK'
Instead of using the indexing operator [] on your string and have to struggle with indexes all over I use foreach c in "sometext" ... which will proceed char-wise through the string - much less hassle.
If you need to run-length encode an array/list (your sor) of strings, simply apply the code to each one (preferably by using foreach s in yourStringList ....
I have a text file that is divided up into many sections, each about 10 or so lines long. I'm reading in the file using File.ReadAllLines into an array, one line per element of the array, and I'm then I'm trying to parse each section of the file to bring back just some of the data. I'm storing the results in a list, and hoping to export the list to csv ultimately.
My for loop is giving me trouble, as it loops through the right amount of times, but only pulls the data from the first section of the text file each time rather than pulling the data from the first section and then moving on and pulling the data from the next section. I'm sure I'm doing something wrong either in my for loop or for each loop. Any clues to help me solve this would be much appreciated! Thanks
David
My code so far:
namespace ParseAndExport
{
class Program
{
static readonly string sourcefile = #"Path";
static void Main(string[] args)
{
string[] readInLines = File.ReadAllLines(sourcefile);
int counter = 0;
int holderCPStart = counter + 3;//Changed Paths will be an different number of lines each time, but will always start 3 lines after the startDiv
/*Need to find the start of the section and the end of the section and parse the bit in between.
* Also need to identify the blank line that occurs in each section as it is essentially a divider too.*/
int startDiv = Array.FindIndex(readInLines, counter, hyphens72);
int blankLine = Array.FindIndex(readInLines, startDiv, emptyElement);
int endDiv = Array.FindIndex(readInLines, counter + 1, hyphens72);
List<string> results = new List<string>();
//Test to see if FindIndexes work. Results should be 0, 7, 9 for 1st section of sourcefile
/*Console.WriteLine(startDiv);
Console.WriteLine(blankLine);
Console.WriteLine(endDiv);*/
//Check how long the file is so that for testing we know how long the while loop should run for
//Console.WriteLine(readInLines.Length);
//sourcefile has 5255 lines (elements) in the array
for (int i = 0; i <= readInLines.Length; i++)
{
if (i == startDiv)
{
results = (readInLines[i + 1].Split('|').Select(p => p.Trim()).ToList());
string holderCP = string.Join(Environment.NewLine, readInLines, holderCPStart, (blankLine - holderCPStart - 1)).Trim();
results.Add(holderCP);
string comment = string.Join(" ", readInLines, blankLine + 1, (endDiv - (blankLine + 1)));//in case the comment is more than one line long
results.Add(comment);
i = i + 1;
}
else
{
i = i + 1;
}
foreach (string result in results)
{
Console.WriteLine(result);
}
//csvcontent.AppendLine("Revision Number, Author, Date, Time, Count of Lines, Changed Paths, Comments");
/* foreach (string result in results)
{
for (int x = 0; x <= results.Count(); x++)
{
StringBuilder csvcontent = new StringBuilder();
csvcontent.AppendLine(results[x] + "," + results[x + 1] + "," + results[x + 2] + "," + results[x + 3] + "," + results[x + 4] + "," + results[x + 5]);
x = x + 6;
string csvpath = #"addressforcsvfile";
File.AppendAllText(csvpath, csvcontent.ToString());
}
}*/
}
Console.ReadKey();
}
private static bool hyphens72(String h)
{
if (h == "------------------------------------------------------------------------")
{
return true;
}
else
{
return false;
}
}
private static bool emptyElement(String ee)
{
if (ee == "")
{
return true;
}
else
{
return false;
}
}
}
}
It looks like you are trying to grab all of the lines in a file that are not "------" and put them into a list of strings.
You can try this:
var lineswithoutdashes = readInLines.Where(x => x != hyphens72).Select(x => x).ToList();
Now you can take this list and do the split with a '|' to extract the fields you wanted
The logic seems wrong. There are issues with the code in itself also. I am unsure what precisely you're trying to do. Anyway, a few hints that I hope will help:
The if (i == startDiv) checks to see if I equals startDiv. I assume the logic that happens when this condition is met, is what you refer to as "pulls the data from the first section". That's correct, given you only run this code when I equals startDiv.
You increase the counter I inside the for loop, which in itself also increases the counter i.
If the issue in 2. wouldn't exists then I'd suggest to not do the same operation "i = i + 1" in both the true and false conditions of the if (i == startDiv).
Given I assume this file might actually be massive, it's probably a good idea to not store it in memory, but just read the file line by line and process line by line. There's currently no obvious reason why you'd want to consume this amount of memory, unless it's because of the convenience of this API "File.ReadAllLines(sourcefile)". I wouldn't be too scared to read the file like this:
Try (BufferedReader br = new BufferedReader(new FileReader (file))) {
String line;
while ((line = br.readLine()) != null) {
// process the line.
}
}
You can skip the lines until you've passed where the line equals hyphens72.
Then for each line, you process the line with the code you provided in the true case of (i == startDiv), or at least, from what you described, this is what I assume you are trying to do.
int startDiv will return the line number that contains hyphens72.
So your current for loop will only copy to results for the single line that matches the calculated line number.
I guess you want to search the postion of startDiv in the current line?
const string hyphens72;
// loop over lines
for (var lineNumber = 0; lineNumber <= readInLines.Length; lineNumber++) {
string currentLine = readInLines[lineNumber];
int startDiv = currentLine.IndexOf(hyphens72);
// loop over characters in line
for (var charIndex = 0; charIndex < currentLine.Length; charIndex++) {
if (charIndex == startDiv) {
var currentCharacter = currentLine[charIndex];
// write to result ...
}
else {
continue; // skip this character
}
}
}
There are a several things which could be improved.
I would use ReadLines over File.ReadAllLines( because ReadAllLines reads all the lines at ones. ReadLines will stream it.
With the line results = (readInLines[i + 1].Split('|').Select(p => p.Trim()).ToList()); you're overwriting the previous results list. You'd better use results.AddRange() to add new results.
for (int i = 0; i <= readInLines.Length; i++) means when the length = 10 it will do 11 iterations. (1 too many) (remove the =)
Array.FindIndex(readInLines, counter, hyphens72); will do a scan. On large files it will take ages to completely read them and search in it. Try to touch a single line only ones.
I cannot test what you are doing, but here's a hint:
IEnumerable<string> readInLines = File.ReadLines(sourcefile);
bool started = false;
List<string> results = new List<string>();
foreach(var line in readInLines)
{
// skip empty lines
if(emptyElement(line))
continue;
// when dashes are found, flip a boolean to activate the reading mode.
if(hyphens72(line))
{
// flip state.. (start/end)
started != started;
}
if(started)
{
// I don't know what you are doing here precisely, do what you gotta do. ;-)
results.AddRange((line.Split('|').Select(p => p.Trim()).ToList()));
string holderCP = string.Join(Environment.NewLine, readInLines, holderCPStart, (blankLine - holderCPStart - 1)).Trim();
results.Add(holderCP);
string comment = string.Join(" ", readInLines, blankLine + 1, (endDiv - (blankLine + 1)));//in case the comment is more than one line long
results.Add(comment);
}
}
foreach (string result in results)
{
Console.WriteLine(result);
}
You might want to start with a class like this. I don't know whether each section begins with a row of hyphens, or if it's just in between. This should handle either scenario.
What this is going to do is take your giant list of strings (the lines in the file) and break it into chunks - each chunk is a set of lines (10 or so lines, according to your OP.)
The reason is that it's unnecessarily complicated to try to read the file, looking for the hyphens, and process the contents of the file at the same time. Instead, one class takes the input and breaks it into chunks. That's all it does.
Another class might read the file and pass its contents to this class to break them up. Then the output is the individual chunks of text.
Another class can then process those individual sections of 10 or so lines without having to worry about hyphens or what separates on chunk from another.
Now that each of these classes is doing its own thing, it's easier to write unit tests for each of them separately. You can test that your "processing" class receives an array of 10 or so lines and does whatever it's supposed to do with them.
public class TextSectionsParser
{
private readonly string _delimiter;
public TextSectionsParser(string delimiter)
{
_delimiter = delimiter;
}
public IEnumerable<IEnumerable<string>> ParseSections(IEnumerable<string> lines)
{
var result = new List<List<string>>();
var currentList = new List<string>();
foreach (var line in lines)
{
if (line == _delimiter)
{
if(currentList.Any())
result.Add(currentList);
currentList = new List<string>();
}
else
{
currentList.Add(line);
}
}
if (currentList.Any() && !result.Contains(currentList))
{
result.Add(currentList);
}
return result;
}
}
I have an input as
2:{{2,10},{6,4}}
I am reading this as
string input = Console.ReadLine();
Next this input has to be passed to a function
GetCount(int count, int[,] arr)
{
}
How can I do so using C#?
Thanks
You could use RegularExpressions for extracting in an easy way each token of your input string. In the following example, support for extra spaces is included also (the \s* in the regular expressions).
Remember that always is a great idea to give a class the responsibility of parsing (in this example) rather than taking an procedural approach.
All the relevant lines are commented for better understanding.
Finally, i tested this and worked with the provided sample input strings.
using System;
using System.Text.RegularExpressions;
namespace IntPairArrayParserDemo
{
class Program
{
static void Main(string[] args)
{
var input = "2:{{2,10},{6,4}}";
ParseAndPrintArray(input);
var anotherInput = "2 : { { 2 , 10 } , { 6 , 4 } }";
ParseAndPrintArray(anotherInput);
}
private static void ParseAndPrintArray(string input)
{
Console.WriteLine("Parsing array {0}...", input);
var array = IntPairArrayParser.Parse(input);
var pairCount = array.GetLength(0);
for (var i = 0; i < pairCount; i++)
{
Console.WriteLine("Pair found: {0},{1}", array[i, 0], array[i, 1]);
}
Console.WriteLine();
}
}
internal static class IntPairArrayParser
{
public static int[,] Parse(string input)
{
if (string.IsNullOrWhiteSpace(input)) throw new ArgumentOutOfRangeException("input");
// parse array length from string
var length = ParseLength(input);
// create the array that will hold all the parsed elements
var result = new int[length, 2];
// parse array elements from input
ParseAndStoreElements(input, result);
return result;
}
private static void ParseAndStoreElements(string input, int[,] array)
{
// get the length of the first dimension of the array
var expectedElementCount = array.GetLength(0);
// parse array elements
var elementMatches = Regex.Matches(input, #"{\s*(\d+)\s*,\s*(\d+)\s*}");
// validate that the number of elements present in input is corrent
if (expectedElementCount != elementMatches.Count)
{
var errorMessage = string.Format("Array should have {0} elements. It actually has {1} elements.", expectedElementCount, elementMatches.Count);
throw new ArgumentException(errorMessage, "input");
}
// parse array elements from input into array
for (var elementIndex = 0; elementIndex < expectedElementCount; elementIndex++)
{
ParseAndStoreElement(elementMatches[elementIndex], elementIndex, array);
}
}
private static void ParseAndStoreElement(Match match, int index, int[,] array)
{
// parse first and second element values from the match found
var first = int.Parse(match.Groups[1].Value);
var second = int.Parse(match.Groups[2].Value);
array[index, 0] = first;
array[index, 1] = second;
}
private static int ParseLength(string input)
{
// get the length from input and parse it as int
var lengthMatch = Regex.Match(input, #"(\d+)\s*:");
return int.Parse(lengthMatch.Groups[1].Value);
}
}
}
Not to do your work for you, you will first have to parse the whole string to find the individual integers, either using regular expressions or, as I would do it myself, the string.Split method. Then parse the substrings representing the individual integers with the int.Parse or the int.TryParse methods.
I doubt you're going to get a serious parsing answer for your custom format. If you NEED to have the value inputted that way, I'd look up some info on regular expressions. If that's not powerful enough for you, there are some fairly convienient parser-generators you can use.
Alternatively, the much more realistic idea would be something like this:
(NOTE: Haven't tried this at all... didn't even put it in VS... but this is the idea...)
int rows = 0;
string rowsInput = "";
do {
Console.Write("Number of rows:");
rowsInput = Console.ReadLine();
} while (!Int32.TryParse(rowsInput, out rows);
int columns = 0;
string columnsInput = "";
do {
Console.Write("Number of columns:");
string columnsInput = Console.ReadLine();
} while (!Int32.TryParse(columnsInput, out columns);
List<List<int>> values = new List<List<int>>();
for (int i = 0; i < rows; i++)
{
bool validInput = false;
do {
Console.Write(String.Format("Enter comma-delimited integers for row #{0}:", i.ToString()));
string row = Console.ReadLine();
string[] items = row.split(',');
int temp;
validInput = (items.Length == columns) && (from item in items where !Int32.TryParse(item, out temp) select item).count() == 0;
if (validInput)
{
values.add(
(from item in items select Convert.ToInt32(item)).ToList()
);
}
} while (!validInput);
}