Print all possible paths in a labyrinth using recursive DFS - c#

This is the task: You are given a labyrinth, which consists of N x N squares, each of it can be passable or not. Passable cells consist of lower Latin letter between "a" and "z", and the non-passable – '#'. In one of the squares is Jack. It is marked with "*".
Two squares are neighbors, if they have common wall. At one step Jack can pass from one passable square to its neighboring passable square. When Jack passes through passable squares, he writes down the letters from each square. At each exit he gets a word. Write a program, which from a given labyrinth prints the words, which Jack gets from all the possible exits.
The input data is read from a text file named Labyrinth.in. At the first line in the file there is the number N (2 < N < 10). At each of the next N lines there are N characters, each of them is either Latin letter between "a" and "z" or "#" (impassable wall) or "*" (Jack). The output must be printed in the file Labyrinth.out.
Input:
6
a##km#
z#ada#
a*m###
#d####
rifid#
#d#d#t
So far I've done that :
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
public class Maze
{
private const string InputFileName = "Labyrinth.in";
private const string OutputFileName = "Labyrinth.out";
StringBuilder path = new StringBuilder();
public class Cell
{
public int Row { get; set; }
public int Column { get; set; }
public Cell(int row, int column)
{
this.Row = row;
this.Column = column;
}
}
private char[,] maze;
private int size;
private Cell startCell = null;
public void ReadFromFile(string fileName)
{
using (StreamReader reader = new StreamReader(fileName))
{
// Read maze size and create maze
this.size = int.Parse(reader.ReadLine());
this.maze = new char[this.size, this.size];
// Read the maze cells from the file
for (int row = 0; row < this.size; row++)
{
string line = reader.ReadLine();
for (int col = 0; col < this.size; col++)
{
this.maze[row, col] = line[col];
if (line[col] == '*')
{
this.startCell = new Cell(row, col);
}
}
}
}
}
public void FindAllPathsAndPrintThem()
{
if (this.startCell == null)
{
// Start cell is missing -> no path
SaveResult(OutputFileName, "");
return;
}
VisitCell(this.startCell.Row,
this.startCell.Column, path);
if (path.Length == 0)
{
// We didn't reach any cell at the maze border -> no path
SaveResult(OutputFileName, "");
return;
}
}
private void VisitCell(int row, int column, StringBuilder path)
{
if (row < 0 || row > maze.GetLength(0) - 1 ||
column < 0 || column > maze.GetLength(1) - 1)
{
SaveResult(OutputFileName, path.ToString());
return;
}
if (this.maze[row, column] != 'x' && this.maze[row, column] != '#')
{
// The cell is free --> visit it
if (this.maze[row, column] != '*')
{
path.Append(this.maze[row, column]);
this.maze[row, column] = 'x';
}
VisitCell(row, column + 1, path);
VisitCell(row, column - 1, path);
VisitCell(row + 1, column, path);
VisitCell(row - 1, column, path);
}
}
public void SaveResult(string fileName, string result)
{
using (StreamWriter writer = new StreamWriter(fileName))
{
writer.WriteLine(result);
}
}
static void Main()
{
Maze maze = new Maze();
maze.ReadFromFile(InputFileName);
maze.FindAllPathsAndPrintThem();
}
}
Sorry for long question. There need to be a small bug but I don't get it where.
The output is madifiddrdzaadamk. Thanks in advance.

Here's a solution I came up with. It keeps track of what cells have been visited during one go through the maze, but not for all attempts. This can be done by making the function return an IEnumerable<string> that will represent the paths to an exit from the current point in the maze. First check if you have gone off the edge of the maze and return nothing. Then check if you're at a wall and if so no path is returned. Otherwise you check if you are at the edge and if so you return the path that is represented by the current cell only. Then you have to mark the current cell as visited, then attempt to find all paths in each of the 4 directions and concatenate the current cell to any that you find and yield them. Then at the end you mark the cell as not visited so it can be used for other attempts.
private static IEnumerable<string> VisitCell(int row, int column, bool[,] visited)
{
if (row < 0 || column < 0 || row >= maze.GetLength(0) || column >= maze.GetLength(1))
yield break;
if (maze[row, column] == '#' || visited[row, column])
yield break;
if (row == 0 || row == maze.GetLength(0) - 1 ||
column == 0 || column == maze.GetLength(1) - 1)
{
yield return maze[row, column].ToString();
}
visited[row, column] = true;
foreach (var path in VisitCell(row, column + 1, visited))
{
yield return maze[row, column] + path;
}
foreach(var path in VisitCell(row, column - 1, visited))
{
yield return maze[row, column] + path;
}
foreach (var path in VisitCell(row + 1, column, visited))
{
yield return maze[row, column] + path;
}
foreach (var path in VisitCell(row - 1, column, visited))
{
yield return maze[row, column] + path;
}
visited[row, column] = false;
}
Then you can run this code
private static char[,] maze =
{
{ 'a', '#', '#', 'k', 'm', '#' },
{ 'z', '#', 'a', 'd', 'a', '#' },
{ 'a', '*', 'm', '#', '#', '#' },
{ '#', 'd', '#', '#', '#', '#' },
{ 'r', 'i', 'f', 'i', 'd', '#' },
{ '#', 'd', '#', 'd', '#', 't' }
};
private static void Main(string[] args)
{
foreach(var path in VisitCell(2, 1, new bool[6, 6]))
Console.WriteLine(path);
}
to get this result
*madam
*madamk
*madk
*madkm
*a
*az
*aza
*difid
*dir
*did
You can tweak it to remove the star from the beginning of each path.

Here is the correct code:
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
public class Maze
{
private const string InputFileName = "Labyrinth.in";
private const string OutputFileName = "Labyrinth.out";
public class Cell
{
public int Row { get; set; }
public int Column { get; set; }
public Cell(int row, int column)
{
this.Row = row;
this.Column = column;
}
}
private char[,] maze;
private int size;
private Cell startCell = null;
public void ReadFromFile(string fileName)
{
using (StreamReader reader = new StreamReader(fileName))
{
// Read maze size and create maze
this.size = int.Parse(reader.ReadLine());
this.maze = new char[this.size, this.size];
// Read the maze cells from the file
for (int row = 0; row < this.size; row++)
{
string line = reader.ReadLine();
for (int col = 0; col < this.size; col++)
{
this.maze[row, col] = line[col];
if (line[col] == '*')
{
this.startCell = new Cell(row, col);
}
}
}
}
}
public void FindAllPathsAndPrintThem()
{
if (this.startCell == null)
{
// Start cell is missing -> no path
SaveResult(OutputFileName, "");
return;
}
VisitCell(this.startCell.Row,
this.startCell.Column, "");
}
private void VisitCell(int row, int column, string path)
{
if (row < 0 || column < 0 || row >= maze.GetLength(0) || column >= maze.GetLength(1))
{
return;
}
if (row < 0 || row > maze.GetLength(0) - 1 ||
column < 0 || column > maze.GetLength(1) - 1)
{
SaveResult(OutputFileName, path);
return;
}
if (this.maze[row, column] != '#')
{
// The cell is free --> visit it
char x = this.maze[row, column];
this.maze[row, column] = '#';
VisitCell(row, column + 1, path + x);
VisitCell(row, column - 1, path + x);
VisitCell(row + 1, column, path + x);
VisitCell(row - 1, column, path + x);
this.maze[row, column] = x;
}
}
public void SaveResult(string fileName, string result)
{
using (StreamWriter writer = new StreamWriter(fileName, true))
{
writer.WriteLine(result);
}
}
static void Main()
{
Maze maze = new Maze();
maze.ReadFromFile(InputFileName);
maze.FindAllPathsAndPrintThem();
}
}
The comments on the question where correct I was modifying the string, so now it works, the string is not modified in process.

Related

How to increment mixed Alphanumeric in C#?

I have requirements in a project to generate sequential rows and columns which are alphanumeric values.
The end user will pass the start value of row and column he would like to start from, and how many rows and columns he wants to generate.
For letters the max value is Z
For numbers the max values is 9
If the end user passed these parameters:
StartRow = 0A
StartColumn = A9Z
rowsCount = 2
columnsCount = 5
I would like to get this result:
You might want to reconsider your approach. Rather than maintaining an alphanumeric value and trying to increment it, maintain the value as a class containing Row and Column values, and then use ToString to convert it to the alphanumeric representation. Like this:
class RowCol
{
private int _row;
private int _col;
public int Row
{
get { return _row; }
set
{
// Row is of the form <digit><letter
// giving you 260 possible values.
if (value < 0 || value > 259)
throw new ArgumentOutOfRangeException();
_row = value;
}
}
public int Col
{
get { return _col; }
set
{
// Col is <letter><digit><letter>,
// giving you 6,760 possible values
if (value < 0 || value > 6759)
throw new ArgumentOutOfRangeException();
_col = value;
}
}
public string RowString
{
get
{
// convert Row value to string
int r, c;
r = Math.DivMod(_row, 26, out c);
r += '0';
c += 'A';
return string.Concat((char)r, (char)c);
}
set
{
// convert string to number.
// String is of the form <letter><digit>
if (string.IsNullOrEmpty(value) || value.Length != 2
|| !Char.IsDigit(value[0] || !Char.IsUpper(value[1]))
throw new ArgumentException();
_row = 26*(value[0]-'0') + (value[1]-'A');
}
}
public string ColString
{
get
{
int left, middle, right remainder;
left = Math.DivRem(_col, 260, out remainder);
middle = Math.DivRem(remainder, 26, out right);
left += 'A';
middle += '0';
right += 'A';
return string.Concat((char)left, (char)middle, (char)right);
}
set
{
// Do standard checking here to make sure it's in the right form.
if (string.IsNullOrEmpty(value) || value.Length != 3
|| !Char.IsUpper(value[0] || !Char.IsDigit(value[1]) || !Char.IsUpper(value[2]))
throw new ArgumentException();
_col = 260*(value[0] - 'A');
_col += 26*(value[1] - '0');
_col += value[2] - 'A';
}
}
public override string ToString()
{
return RowString + '-' + ColString;
}
public RowCol(int row, int col)
{
Row = _row;
Col = _col;
}
public RowCol(string row, string col)
{
RowString = row;
RowString = col;
}
}
(Code not yet tested, but that's the general idea.)
That's a bit more code than you have, it hides the complexity in the RowCol class rather than forcing you to deal with it in your main program logic. The point here is that you just want to increment the row or column; you don't want to have to think about how that's done. It makes your main program logic easier to understand. For example:
string startRow = "0A";
string startCol = "B0A";
RowCol rc = new RowCol("0A", "B0A");
for (int r = 0; r < rowsCount; r++)
{
rc.ColString = "B0A";
for (int c = 0; c < columnsCount; c++)
{
Console.WriteLine(rc);
rc.Row = rc.Row + 1;
}
rc.Col = rc.Col + 1;
}
By casting this as a simple conversion problem and encapsulating it in a class, I've made the code more robust and flexible, and easier to test, understand, and use.
I have come up with very simple solution to implement that and I would like to share this Console application :
class Program
{
static void Main(string[] args)
{
var row = "0A";
var column = "A9Z";
var rowsCount = 2;
var columnsCount = 5;
var rowCharArray =row.ToArray().Reverse().ToArray();
var columnCharArray = column.ToArray().Reverse().ToArray();
for (int i = 0; i < rowsCount; i++)
{
for (int j = 0; j < columnsCount; j++)
{
columnCharArray = incrementChar(columnCharArray);
var currentColumn = string.Join("", columnCharArray.Reverse().ToArray());
var currentRow= string.Join("", rowCharArray.Reverse().ToArray());
Console.WriteLine(currentRow + "-" + currentColumn);
}
columnCharArray = column.ToArray().Reverse().ToArray();
rowCharArray= incrementChar(rowCharArray);
Console.WriteLine("-------------------------------");
}
Console.ReadLine();
}
static char[] incrementChar(char[] charArray,int currentIndex=0)
{
char temp = charArray[currentIndex];
if (charArray.Length -1 == currentIndex && (temp == '9' || temp == 'Z'))
throw new Exception();
temp++;
if(Regex.IsMatch(temp.ToString(),"[A-Z]"))
{
charArray[currentIndex] = temp;
}
else
{
if (Regex.IsMatch(temp.ToString(), "[0-9]"))
{
charArray[currentIndex] = temp;
}
else
{
currentIndex++;
incrementChar(charArray, currentIndex);
}
}
if (currentIndex != 0)
charArray = resetChar(charArray, currentIndex);
return charArray;
}
static char[] resetChar(char[] charArray,int currentIndex)
{
for (int i = 0; i < currentIndex; i++)
{
if (charArray[i] == 'Z')
charArray[i] = 'A';
else if (charArray[i] == '9')
charArray[i] = '0';
}
return charArray;
}
}

Selecting Hexagons around a radius

I have a map made of hexagons. When a hexagon is clicked on which a troop resides, other hexagons around the clicked one should be highlighted based on the troop's range variable.
I have tried quite a few methods on my own, but failed. I can't seem to get a list with the correct ones highlighted. In this example I will be using a troop of a range of 2 tiles.
The method I have tried to implement is as following:
There are 3 lists.
Open list contains nodes to be expanded by 1
Close list contains nodes that will be highlighted
remove list contains nodes that have already been expanded
I followed the following algorithm:
Add the start node to all lists.
Expand the nodes in open list by 1 tile.
Remove the nodes within open list using remove list.
add open list nodes to closed list.
clear remove list.
copy open list contents to remove list.
Repeat for whatever the range of the troop is.
My goal is to highlight a hex tiles of radius r, around the clicked tile; r = troop range.
Im quite lost, and I was probably doing some obvious mistakes. Any help will be appreciated!
(Below is the code that I made)
private List<string> expand(List<string> open, bool odd)
{
List<string> newopen = new List<string>();
int oddEven = 1;
if (odd == false)
{
oddEven = -1;
}
foreach (string s in open)
{
string[] rc = s.Split('.'); //row + col of the current hex
int Row = int.Parse(rc[0]);
int Col = int.Parse(rc[1]);
for (int r = 1; r >= -1; r--)
{
for (int c = -1; c <= 1; c++)
{
if (!((r == oddEven && c != 0) || (r== 0 && c == 0)))
{
if (((Row + r) > -1 && (Col + c) > -1) && ((Row + r) < 9 && (Col + c) < 18)) //mapcheck
{
if (Hexmap[Row + r, Col + c] == '-') //mapcheck
{
newopen.Add((Row + r).ToString() + "." + (Col + c).ToString());
}
}
}
}
}
}
return newopen;
}
private void Remove(ref List<string> rem, ref List<string> open)
{
foreach (string s in rem)
{
open.Remove(s);
}
}
private void Add(ref List<string> closed, ref List<string> open)
{
foreach (string s in open)
{
closed.Add(s);
}
}
private void TroopSelect(int row, int col, int range)
{
bool odd = true;
if ((row % 2) == 0)
{
odd = false;
}
List<string> open = new List<string>();
List<string> close = new List<string>();
List<string> remove = new List<string>();
open.Add(row.ToString() + "." + col.ToString());
close.Add(row.ToString() + "." + col.ToString());
remove.Add(row.ToString() + "." + col.ToString());
for (int i = 0; i < range; i++)
{
open = expand(open, odd); //row and col of the current point being checked --- REMOVE ROW COL, find it out from the list within the function
Remove(ref remove, ref open); //remove from open the remove hex values
Add(ref close, ref open); //add to closed what's in open
remove.Clear(); //clear remove
remove = open; //set remove the same as open
}
foreach (string s in close)
{
string[] rc = s.Split('.'); //row + col of the current hex
int Row = int.Parse(rc[0]);
int Col = int.Parse(rc[1]);
Hexagons.Add(new PointF(Row, Col));
}
}
private void CheckTroop(int row, int col)
{
if (Hexmap[row, col] == 'o') //it's a friendly troop
{
foreach (Troops t in playertroops)
{
if (t.row == row && t.column == col)
{
TroopSelect(row, col, t.range);
this.battlegrid.Invalidate();
}
}
}
}
private void battlegrid_MouseClick(object sender, MouseEventArgs e)
{
int row, col;
PointToHex(e.X, e.Y, HexHeight, out row, out col); //gets the hex in the x/y position
CheckTroop(row, col);
//this.Invalidate();
}
Below are also two images: (Ignore the rock in the background!)
First of troop range = 1, works ok
Second image of troop range = 2, doesn't work ok
Thanks once again for any help.

Creating a Score Method for an Array

My goal is to create a score method for a simple game based in an array. The game consists of putting 'R's or 'B's in one of 11 positions in an array. Once the array is full, the score method will execute as follows:
Any single 'R' or 'B' is worth 0 points
Any pair of 'R's or 'B's is worth 4 points
Any triple of 'R's or 'B's is worth 6 points
... and so on.
I'm having trouble calculating the score and I feel like I am missing something obvious so I'm coming here. The code I have looks for pairs and adds 2 to the score, but I end up missing 2 points (since the first pair is worth 4 and each additional "pair" is worth another 2).
public int score(char color)
{
int score = 0;
for (int i = 0; i < gameBoard.Length - 1; i++)
{
if (gameBoard[i] == color && gameBoard[i + 1] == color)
score += 2;
else
score += 0;
}
return score;
}
Your problem can be solved by regular expressions.
public int CalculateScore(char color)
{
var boardStr = String.Join("", gameBoard);
return GetCharacterSequences(color, boardStr)
.Sum(str => str.Length * 2);
}
//Returns same character sequences of length more than two.
private IEnumerable<string> GetCharacterSequences(char color, string boardStr)
{
return Regex.Matches(boardStr, $#"({color})\1+").OfType<Match>()
.SelectMany(match => match.Groups.OfType<Group>())
.Select(#group => #group.Value)
.Where(str => str.Length > 1);
}
Idea of sum function: Each pair costs 4 points, each triple costs 6 points hence each single char costs 2 points.
GetCharacterSequences looks little bit complex but it makes CalculateScore method very simple.
Well you could create a boolean variable and use that to determine the number of points added.
Note: This code doesn't account for triple matches as you've mentioned in your question.
public int score(char color)
{
int score = 0;
bool firstMatch = true;
for (int i = 0; i < gameBoard.Length - 1; i++)
{
if (gameBoard[i] == color && gameBoard[i + 1] == color) {
if (firstMatch == true) {
score += 4;
firstMatch = false;
} else {
score += 2;
}
}
else
{
score += 0;
}
}
return score;
}
Here's another approach, based on a pair being 4 points, 3 being 6 points, and assuming 4 being 8 points and so forth (basically number of adjacent colors * 2 if there is at least a pair).
public int score(char color)
{
int adj = 0;
int score = 0;
for (int i = 0; i < gameBoard.Length; i++)
{
if (gameBoard[i] == color)
{
adj++;
}
else
{
if (adj > 1)
{
score += adj * 2;
}
adj = 0;
}
}
}
This code loops through the array, and keeps a count of the number of adjacent spots marked by the specified color. When it comes to an element of the array that is not the right color, it checks to see how many adjacent elements had the correct color.
If it's greater than 1, then it multiplies the number of adjacent elements by 2 and resets the adjacent elements counter to 0 and continues through the array.
Given an array of 'R', 'R', 'B', 'B', 'B', 'R', 'R', 'R', 'B', 'R', 'B', it produces a score of 10 for 'R' (4 + 6) and 6 for 'B' (6), as follows:
'R', 'R' = 4
'R', 'R', 'R' = 6
'R' = 0
'B', 'B', 'B' = 6
'B' = 0
Here is what i came up with
namespace TestApp1
{
class Program
{
static void Main(string[] args)
{
string[] test = GetValues();
string testView = String.Join(String.Empty, test);
string score = GetScore(test).ToString();
Console.WriteLine(testView);
Console.WriteLine(score);
Console.ReadLine();
}
public static int GetScore(string[] test)
{
int score = 0;
int occurence = 0;
string LastChar = string.Empty;
for (int i = 0; i < test.Length; i++)
{
if(LastChar == string.Empty)
{
LastChar = test[i];
occurence += 1;
continue;
}
if(LastChar == test[i])
{
occurence += 1;
if(i == test.Length - 1)
{
if (occurence > 1)
{
score += occurence * 2;
}
}
}
else
{
if(occurence > 1)
{
score += occurence * 2;
}
LastChar = test[i];
occurence = 1;
}
}
return score;
}
public static string[] GetValues()
{
List<string> values = new List<string>();
for (int i = 0; i < 12; i++)
{
var rnd = new Random(DateTime.Now.Millisecond);
int ticks = rnd.Next(0, 2);
values.Add(ticks == 1 ? "R" : "B");
System.Threading.Thread.Sleep(2);
}
return values.ToArray();
}
}
}
To calculate each score independently
namespace TestApp1
{
class Program
{
static void Main(string[] args)
{
string[] test = GetValues();
string testView = String.Join(String.Empty, test);
int rScore = 0;
int bScore = 0;
GetScore(test,out rScore, out bScore);
string score = "R: " + rScore.ToString() + " B: " + bScore.ToString();
Console.WriteLine(testView);
Console.WriteLine(score);
Console.ReadLine();
}
public static void GetScore(string[] test, out int rScore, out int bScore)
{
int occurence = 0;
string LastChar = string.Empty;
rScore = 0;
bScore = 0;
for (int i = 0; i < test.Length; i++)
{
if(LastChar == string.Empty)
{
LastChar = test[i];
occurence += 1;
continue;
}
if(LastChar == test[i])
{
occurence += 1;
if(i == test.Length - 1)
{
if (occurence > 1)
{
if(LastChar == "R")
{
rScore += occurence * 2;
}
else
{
bScore += occurence * 2;
}
}
}
}
else
{
if(occurence > 1)
{
if (LastChar == "R")
{
rScore += occurence * 2;
}
else
{
bScore += occurence * 2;
}
}
LastChar = test[i];
occurence = 1;
}
}
}
public static string[] GetValues()
{
List<string> values = new List<string>();
for (int i = 0; i < 12; i++)
{
var rnd = new Random(DateTime.Now.Millisecond);
int ticks = rnd.Next(0, 2);
values.Add(ticks == 1 ? "R" : "B");
System.Threading.Thread.Sleep(2);
}
return values.ToArray();
}
}
}

Extract Field Names and max lengths from a text file using C#

I have a file that is a SQL Server result set saved as a text file.
Here is a sample of what the file looks like:
RWS_DMP_ID RV1_DMP_NUM CUS_NAME
3192 3957 THE ACME COMPANY
3192 3957 THE ACME COMPANY
3192 3957 THE ACME COMPANY
I want to create a C# program that reads this file and creates the following table of data:
Field MaxSize
----- -------
RWS_DMP_ID 17
RV1_DMP_NUM 17
CUS_NAME 42
This is a list of the field names and their max length. The max length is the beginning of the field to the space right before the beginning of the next field.
By the way I don't care about code performance. This is seldom used file processing utility.
I solved this with the following code:
objFile = new StreamReader(strPath + strFileName);
strLine = objFile.ReadLine();
intLineCnt = 0;
while (strLine != null)
{
intLineCnt++;
if (intLineCnt <= 3)
{
if (intLineCnt == 1)
{
strWords = SplitWords(strLine);
intNumberOfFields = strWords.Length;
foreach (char c in strLine)
{
if (bolNewField == true)
{
bolFieldEnd = false;
bolNewField = false;
}
if (bolFieldEnd == false)
{
if (c == ' ')
{
bolFieldEnd = true;
}
}
else
{
if (c != ' ')
{
if (intFieldCnt < strWords.Length)
{
strProcessedData[intFieldCnt, 0] = strWords[intFieldCnt];
strProcessedData[intFieldCnt, 1] = (intCharCnt - 1).ToString();
}
intFieldCnt++;
intCharCnt = 1;
bolNewField = true;
}
}
if (bolNewField == false)
{
intCharCnt++;
}
}
strProcessedData[intFieldCnt, 0] = strWords[intFieldCnt];
strProcessedData[intFieldCnt, 1] = intCharCnt.ToString();
}
else if (intLineCnt == 3)
{
intLine2Cnt= 0;
intTotalLength = 0;
while(intLine2Cnt < intNumberOfFields)
{
intSize = Convert.ToInt32(strProcessedData[intLine2Cnt, 1]);
if (intSize + intTotalLength > strLine.Length)
{
intSize = strLine.Length - intTotalLength;
}
strField = strLine.Substring(intTotalLength, intSize);
strField = strField.Trim();
strProcessedData[intLine2Cnt, intLineCnt - 1] = strField;
intTotalLength = intTotalLength + intSize + 1;
intLine2Cnt++;
}
}
}
strLine = objFile.ReadLine();
}`enter code here`
I'm aware that this code is a complete hack job. I'm looking for a better way to solve this problem.
Is there a better way to solve this problem?
THanks
I'm not sure how memory efficient this is, but I think it's a bit cleaner (assuming your fields are tab-delimited):
var COL_DELIMITER = new[] { '\t' };
string[] lines = File.ReadAllLines(strPath + strFileName);
// read the field names from the first line
var fields = lines[0].Split(COL_DELIMITER, StringSplitOptions.RemoveEmptyEntries).ToList();
// get a 2-D array of the columns (excluding the header row)
string[][] columnsArray = lines.Skip(1).Select(l => l.Split(COL_DELIMITER)).ToArray();
// dictionary of columns with max length
var max = new Dictionary<string, int>();
// for each field, select all columns, and take the max string length
foreach (var field in fields)
{
max.Add(field, columnsArray.Select(row => row[fields.IndexOf(field)]).Max(col => col.Trim().Length));
}
// output per requirment
Console.WriteLine(string.Join(Environment.NewLine,
max.Keys.Select(field => field + " " + max[field])
));
void MaximumWidth(StreamReader reader)
{
string[] columns = null;
int[] maxWidth = null;
string line;
while ((line = reader.ReadLine()) != null)
{
string[] cols = line.Split('\t');
if (columns == null)
{
columns = cols;
maxWidth = new int[cols.Length];
}
else
{
for (int i = 0; i < columns.Length; i++)
{
int width = cols[i].Length;
if (maxWidth[i] < width)
{
maxWidth[i] = width;
}
}
}
}
// ...
}
Here is what I came up with. The big takeaway is to use the IndexOf string function.
class Program
{
static void Main(string[] args)
{
String strFilePath;
String strLine;
Int32 intMaxLineSize;
strFilePath = [File path and name];
StreamReader objFile= null;
objFile = new StreamReader(strFilePath);
intMaxLineSize = File.ReadAllLines(strFilePath).Max(line => line.Length);
//Get the first line
strLine = objFile.ReadLine();
GetFieldNameAndFieldLengh(strLine, intMaxLineSize);
Console.WriteLine("Press <enter> to continue.");
Console.ReadLine();
}
public static void GetFieldNameAndFieldLengh(String strLine, Int32 intMaxSize)
{
Int32 x;
string[] fields = null;
string[,] strFieldSizes = null;
Int32 intFieldSize;
fields = SplitWords(strLine);
strFieldSizes = new String[fields.Length, 2];
x = 0;
foreach (string strField in fields)
{
if (x < fields.Length - 1)
{
intFieldSize = strLine.IndexOf(fields[x + 1]) - strLine.IndexOf(fields[x]);
}
else
{
intFieldSize = intMaxSize - strLine.IndexOf(fields[x]);
}
strFieldSizes[x, 0] = fields[x];
strFieldSizes[x, 1] = intFieldSize.ToString();
x++;
}
Console.ReadLine();
}
static string[] SplitWords(string s)
{
return Regex.Split(s, #"\W+");
}
}

Splitting Comma Separated Values (CSV)

How to split the CSV file in c sharp? And how to display this?
I've been using the TextFieldParser Class in the Microsoft.VisualBasic.FileIO namespace for a C# project I'm working on. It will handle complications such as embedded commas or fields that are enclosed in quotes etc. It returns a string[] and, in addition to CSV files, can also be used for parsing just about any type of structured text file.
Display where? About splitting, the best way is to use a good library to that effect.
This library is pretty good, I can recommend it heartily.
The problems using naïve methods is that the usually fail, there are tons of considerations without even thinking about performance:
What if the text contains commas
Support for the many existing formats (separated by semicolon, or text surrounded by quotes, or single quotes, etc.)
and many others
Import Micorosoft.VisualBasic as a reference (I know, its not that bad) and use Microsoft.VisualBasic.FileIO.TextFieldParser - this handles CSV files very well, and can be used in any .Net language.
read the file one line at a time, then ...
foreach (String line in line.Split(new char[] { ',' }))
Console.WriteLine(line);
This is a CSV parser I use on occasion.
Usage: (dgvMyView is a datagrid type.)
CSVReader reader = new CSVReader("C:\MyFile.txt");
reader.DisplayResults(dgvMyView);
Class:
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
public class CSVReader
{
private const string ESCAPE_SPLIT_REGEX = "({1}[^{1}]*{1})*(?<Separator>{0})({1}[^{1}]*{1})*";
private string[] FieldNames;
private List<string[]> Records;
private int ReadIndex;
public CSVReader(string File)
{
Records = new List<string[]>();
string[] Record = null;
StreamReader Reader = new StreamReader(File);
int Index = 0;
bool BlankRecord = true;
FieldNames = GetEscapedSVs(Reader.ReadLine());
while (!Reader.EndOfStream)
{
Record = GetEscapedSVs(Reader.ReadLine());
BlankRecord = true;
for (Index = 0; Index <= Record.Length - 1; Index++)
{
if (!string.IsNullOrEmpty(Record[Index])) BlankRecord = false;
}
if (!BlankRecord) Records.Add(Record);
}
ReadIndex = -1;
Reader.Close();
}
private string[] GetEscapedSVs(string Data)
{
return GetEscapedSVs(Data, ",", "\"");
}
private string[] GetEscapedSVs(string Data, string Separator, string Escape)
{
string[] Result = null;
int Index = 0;
int PriorMatchIndex = 0;
MatchCollection Matches = Regex.Matches(Data, string.Format(ESCAPE_SPLIT_REGEX, Separator, Escape));
Result = new string[Matches.Count];
for (Index = 0; Index <= Result.Length - 2; Index++)
{
Result[Index] = Data.Substring(PriorMatchIndex, Matches[Index].Groups["Separator"].Index - PriorMatchIndex);
PriorMatchIndex = Matches[Index].Groups["Separator"].Index + Separator.Length;
}
Result[Result.Length - 1] = Data.Substring(PriorMatchIndex);
for (Index = 0; Index <= Result.Length - 1; Index++)
{
if (Regex.IsMatch(Result[Index], string.Format("^{0}[^{0}].*[^{0}]{0}$", Escape))) Result[Index] = Result[Index].Substring(1, Result[Index].Length - 2);
Result[Index] = Result[Index].Replace(Escape + Escape, Escape);
if (Result[Index] == null) Result[Index] = "";
}
return Result;
}
public int FieldCount
{
get { return FieldNames.Length; }
}
public string GetString(int Index)
{
return Records[ReadIndex][Index];
}
public string GetName(int Index)
{
return FieldNames[Index];
}
public bool Read()
{
ReadIndex = ReadIndex + 1;
return ReadIndex < Records.Count;
}
public void DisplayResults(DataGridView DataView)
{
DataGridViewColumn col = default(DataGridViewColumn);
DataGridViewRow row = default(DataGridViewRow);
DataGridViewCell cell = default(DataGridViewCell);
DataGridViewColumnHeaderCell header = default(DataGridViewColumnHeaderCell);
int Index = 0;
ReadIndex = -1;
DataView.Rows.Clear();
DataView.Columns.Clear();
for (Index = 0; Index <= FieldCount - 1; Index++)
{
col = new DataGridViewColumn();
col.CellTemplate = new DataGridViewTextBoxCell();
header = new DataGridViewColumnHeaderCell();
header.Value = GetName(Index);
col.HeaderCell = header;
DataView.Columns.Add(col);
}
while (Read())
{
row = new DataGridViewRow();
for (Index = 0; Index <= FieldCount - 1; Index++)
{
cell = new DataGridViewTextBoxCell();
cell.Value = GetString(Index).ToString();
row.Cells.Add(cell);
}
DataView.Rows.Add(row);
}
}
}
I had got the result for my query. its like simple like i had read a file using io.file. and all the text are stored into a string. After that i splitted with a seperator. The code is shown below.
using System;
using System.Collections.Generic;
using System.Text;
namespace CSV
{
class Program
{
static void Main(string[] args)
{
string csv = "user1, user2, user3,user4,user5";
string[] split = csv.Split(new char[] {',',' '});
foreach(string s in split)
{
if (s.Trim() != "")
Console.WriteLine(s);
}
Console.ReadLine();
}
}
}
The following function takes a line from a CSV file and splits it into a List<string>.
Arguments:
string line = the line to split
string textQualifier = what (if any) text qualifier (i.e. "" or "\"" or "'")
char delim = the field delimiter (i.e. ',' or ';' or '|' or '\t')
int colCount = the expected number of fields (0 means don't check)
Example usage:
List<string> fields = SplitLine(line, "\"", ',', 5);
// or
List<string> fields = SplitLine(line, "'", '|', 10);
// or
List<string> fields = SplitLine(line, "", '\t', 0);
Function:
private List<string> SplitLine(string line, string textQualifier, char delim, int colCount)
{
List<string> fields = new List<string>();
string origLine = line;
char textQual = '"';
bool hasTextQual = false;
if (!String.IsNullOrEmpty(textQualifier))
{
hasTextQual = true;
textQual = textQualifier[0];
}
if (hasTextQual)
{
while (!String.IsNullOrEmpty(line))
{
if (line[0] == textQual) // field is text qualified so look for next unqualified delimiter
{
int fieldLen = 1;
while (true)
{
if (line.Length == 2) // must be final field (zero length)
{
fieldLen = 2;
break;
}
else if (fieldLen + 1 >= line.Length) // must be final field
{
fieldLen += 1;
break;
}
else if (line[fieldLen] == textQual && line[fieldLen + 1] == textQual) // escaped text qualifier
{
fieldLen += 2;
}
else if (line[fieldLen] == textQual && line[fieldLen + 1] == delim) // must be end of field
{
fieldLen += 1;
break;
}
else // not a delimiter
{
fieldLen += 1;
}
}
string escapedQual = textQual.ToString() + textQual.ToString();
fields.Add(line.Substring(1, fieldLen - 2).Replace(escapedQual, textQual.ToString())); // replace escaped qualifiers
if (line.Length >= fieldLen + 1)
{
line = line.Substring(fieldLen + 1);
if (line == "") // blank final field
{
fields.Add("");
}
}
else
{
line = "";
}
}
else // field is not text qualified
{
int fieldLen = line.IndexOf(delim);
if (fieldLen != -1) // check next delimiter position
{
fields.Add(line.Substring(0, fieldLen));
line = line.Substring(fieldLen + 1);
if (line == "") // final field must be blank
{
fields.Add("");
}
}
else // must be last field
{
fields.Add(line);
line = "";
}
}
}
}
else // if there is no text qualifier, then use existing split function
{
fields.AddRange(line.Split(delim));
}
if (colCount > 0 && colCount != fields.Count) // count doesn't match expected so throw exception
{
throw new Exception("Field count was:" + fields.Count.ToString() + ", expected:" + colCount.ToString() + ". Line:" + origLine);
}
return fields;
}
Problem: Convert a comma separated string into an array where commas in "quoted strings,,," should not be considered as separators but as part of an entry
Input:
String: First,"Second","Even,With,Commas",,Normal,"Sentence,with ""different"" problems",3,4,5
Output:
String-Array: ['First','Second','Even,With,Commas','','Normal','Sentence,with "different" problems','3','4','5']
Code:
string sLine;
sLine = "First,\"Second\",\"Even,With,Commas\",,Normal,\"Sentence,with \"\"different\"\" problems\",3,4,5";
// 1. Split line by separator; do not split if separator is within quotes
string Separator = ",";
string Escape = '"'.ToString();
MatchCollection Matches = Regex.Matches(sLine,
string.Format("({1}[^{1}]*{1})*(?<Separator>{0})({1}[^{1}]*{1})*", Separator, Escape));
string[] asColumns = new string[Matches.Count + 1];
int PriorMatchIndex = 0;
for (int Index = 0; Index <= asColumns.Length - 2; Index++)
{
asColumns[Index] = sLine.Substring(PriorMatchIndex, Matches[Index].Groups["Separator"].Index - PriorMatchIndex);
PriorMatchIndex = Matches[Index].Groups["Separator"].Index + Separator.Length;
}
asColumns[asColumns.Length - 1] = sLine.Substring(PriorMatchIndex);
// 2. Remove quotes
for (int Index = 0; Index <= asColumns.Length - 1; Index++)
{
if (Regex.IsMatch(asColumns[Index], string.Format("^{0}[^{0}].*[^{0}]{0}$", Escape))) // If "Text" is sourrounded by quotes (but ignore double quotes => "Leave ""inside"" quotes")
{
asColumns[Index] = asColumns[Index].Substring(1, asColumns[Index].Length - 2); // "Text" => Text
}
asColumns[Index] = asColumns[Index].Replace(Escape + Escape, Escape); // Remove double quotes ('My ""special"" text' => 'My "special" text')
if (asColumns[Index] == null) asColumns[Index] = "";
}
The output array is asColumns

Categories