Reading multiple records kept in a text file - c#

Basically i have the user open a text document that is formatted like this currently.
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
it then splits the info into an array then into variables and then into text boxes.
obviously if i wanted multiple records i may have to format it differently, (thats what i need help with)
But if i had it like this what would be the most efficient way of taking these records from the text file and storing them separately so i can flick through them. For example with a combo box on my form. When the record is selected the form populates with that records data.
multiple records:
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
Person.jpg,Smile,People,23/06/95,This is a Person
Here is my code currently for this part.
private void LoadFile()
{
StreamReader reader = new StreamReader(fileName);
content = reader.ReadLine();
doc = content.Split(',');
filename = Convert.ToString(doc[0]);
fileNameTextBox.Text = doc[0];
description = doc[1];
descriptionTextBox.Text = doc[1];
category = doc[2];
categoryComboBox.Text = doc[2];
//dateTaken = Convert.ToDouble(doc[3]);
dateTakenTextBox.Text = doc[3];
comments = doc[4];
commentsTextBox.Text = doc[4];
}
This code currently works but only for the first record as it is using one array, and i obviously will need multiple ways of storing the other lines.
I Think the best option if i was going to give it a guess would be to use a List of some sort with a Class that generates Records, but that is where i am stuck and need help.
(usually my questions on here get downvoted as i am not concise enough if that is the case comment and i will try to alter my question.
Thanks everyone.

I would create a class that holds the information of a record
public class ImageInfo
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime Date { get; set; }
public string Comments { get; set; }
public override string ToString()
{
return FileName;
}
}
Now you can write a method that returns the image infos
public List<ImageInfo> ReadImageInfos(string fileName)
{
string[] records = File.ReadAllLines(fileName);
var images = new List<ImageInfo>(records.Length);
foreach (string record in records) {
string[] columns = record.Split(',');
if (columns.Length >= 5) {
var imageInfo = new ImageInfo();
imageInfo.FileName = columns[0];
imageInfo.Description = columns[1];
imageInfo.Category = columns[2];
DateTime d;
if (DateTime.TryParseExact(columns[3], "dd/MM/yy",
CultureInfo.InvariantCulture, DateTimeStyles.None, out d))
{
imageInfo.Date = d;
}
imageInfo.Comments = columns[4];
images.Add(imageInfo);
}
}
return images;
}
Now you can fill the textboxes with one of these records like this
List<ImageInfo> images = ReadImageInfos(fileName);
if (images.Count > 0) {
ImageInfo image = images[0];
fileNameTextBox.Text = image.FileName;
descriptionTextBox.Text = image.Description;
categoryComboBox.Text = image.Category;
dateTakenTextBox.Text = image.Date.ToShortDateString();
commentsTextBox.Text = image.Comments;
}
The advantage of this approach is that the two operations of reading and displaying the records are separate. This makes it easier to understand and modify the code.
You can add ImageInfo objects to a ComboBox or ListBox directly instead of adding file names if you override the ToString method in the ImageInfo class.
public override string ToString()
{
return FileName;
}
Add the items to a combo box like this:
myComboBox.Items.Add(image); // Where image is of type ImageInfo.
You can retrieve the currently selected item with:
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
Most likely you will be doing this in the SelectedIndexChanged event handler.
void myComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
myTextBox.Text = image.FileName;
}

Create a class that resembles a row of your data, then iterate over the file, making your split and constructing a new class instance with your split data. Store this in a List<> (or some other appropriate structure) ensure you store it such that it can be referenced later. Don't change your UI as you are loading and parsing the file (as Mike has suggested), also as mike suggests you need to read until the EOF is reached (plenty of examples of this on the web) MSDN example.
Also, streamreader implements IDisposable, so you need to dispose of it, or wrap it in a using statement to clean up.
Example class, you could even pass the row in as a constructor argument:
public class LineItem
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime DateTaken { get; set; }
public string Comments { get; set; }
public LineItem(string textRow)
{
if (!string.IsNullOrEmpty(textRow) && textRow.Contains(','))
{
string[] parts = textRow.Split(',');
if (parts.Length == 5)
{
// correct length
FileName = parts[0];
Description = parts[1];
Category = parts[2];
Comments = parts[4];
// this needs some work
DateTime dateTaken = new DateTime();
if (DateTime.TryParse(parts[3], out dateTaken))
{
DateTaken = dateTaken;
}
}
}
}
}

Your code is not iterating through the records within the file.
You want to continue reading until the end of the file.
while (content != eof)
{
// split content
// populate text boxes
}
But this will overwrite your text boxes with each pass of the loop.
Also, you want to separate your code - do not mix I/O process with code that updates the UI.
The name of the method implies you are loading a file, but the method is doing far more than that. I would suggest changing the method to read the file, split each record into a class object which then gets stored into an array - and return that array.
A separate method will take that array and populate your table or grid or whatever is in the UI. Ideally, you have the gridview bind to the array.

If you keep all your entries the same:
name,food,type,blah blah
name,food,type,blah blah
you can add another split into your code:
line = content.Split('\n');
foreach (line in filename)
{
doc = line.Split(',');
//do stuff...
As for the option for string multiple entries, a method I have used is implementing a list of Models:
class ModelName
{
string Name { get; set; }
string foodType { get; set; }
//etc...
public void ModelName()
{
Name = null;
foodType = null;
//etc...
}
}
List<Model> ModelList;
foreach (line in filename)
{
doc = line.Split(',');
Model.Name = doc[1];
//etc...
And have a different list, and a different Model for each type of entry (person or food)

Related

How to create a Class List with different numbers of inputs in C#

I'm working on my first real c# project and I have faced a problem with my way of creating List based on a Class, which I have no idea how to solve.
I’m trying to write some code, which takes an input file (txt/csv) of multiple constructions with multiple layers, put it into my program, and later write the constructions into a new txt/csv file.
When having the same numbers of layers, it works fine. But when the constructions have different numbers of layers it causes trouble and I get a “System.IndexOutOfRangeException”.
My question is: Can I make the Class which I’m basing my List on, dynamic (I don’t know if it is the technical term), so it work with different numbers of inputs? Both when Adding the construction to the program and when I write it to a new file?
My code is:
class Program
{
static void Main(string[] args)
{
// Filepath for the input and output file
string filePathIn_constructions = #"C:\Library\Constructions.txt";
string filePathOut = #"C:\Library\EPlus_Inputfile.txt";
// Creating a list of constructions based on the class. The list is made from the file "filePathIn_constructions"
List<Construction> allConstructions = new List<Construction>();
List<string> lines_constructions = File.ReadAllLines(filePathIn_constructions).ToList(); // add it to a list
// Adding all the data from the fil to the variable "allConstructions"
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
newConstruction.Layer1 = entries[2];
newConstruction.Layer2 = entries[3];
newConstruction.Layer3 = entries[4];
newConstruction.Layer4 = entries[5];
newConstruction.Layer5 = entries[6];
allConstructions.Add(newConstruction); // Add it to our list of constructions
}
List<string> output = new List<string>();
foreach (var x in allConstructions) // Printing the new
{
output.Add($"{x.EIndex}, {x.Name}, {x.Layer1}, {x.Layer2}, {x.Layer3}, {x.Layer4}, {x.Layer5}");
}
File.WriteAllLines(txtFilePathOut, output);
}
}
My Class for the Constructions is
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public string Layer1 { get; set; }
public string Layer2 { get; set; }
public string Layer3 { get; set; }
public string Layer4 { get; set; }
public string Layer5 { get; set; }
}
An example of a input/output file could be
Construction,ConcreteWall,Concrete;
Construction,Brickwall1,Birck,Isulation,Brick;
Construction,Brickwall2,Birck,AirGap,Isulation,Brick;
Construction,Wood/Concrete Wall,Wood,Isulation,Concrete,Gypson;
Construction,Wood Wall,Wood,AirGap,Gypson,Isulaiton,Gypson;
I hope someone can help. Thanks.
Edit: I have to be able to excess the construction Name seperatly, because i'm using it to do some sorting of the.
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public List<string> Layers { get; set; } = new List<string>();
}
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
for (int i=2; i < entries.Length; i++) {
newConstruction.Layers.Add(entries[i]);
}
allConstructions.Add(newConstruction);
}
foreach(var x in allConstuctions) {
File.AppendAllText(output, $"{x.EIndex}, {x.Name}, {string.Join(", ", x.Layers)}");
}
It is because you are trying to reach a cell of an array that doesn't exist (documentation)
In your input/output file you have lines that have between 3 and 7 values, and you are building an array entries out of those values. This means that you will have arrays with between 3 and 7 cells
The problem is that right after creating those arrays you try to access on every array the cells 0, 1, 2... up to the 7th, even for arrays that have only 3 cells!
What you could do to fix this in a simple way is to add columns to have the same number of separator on each lines (you defined the separator of your lines as column with line.Split(',')). This way, every arrays that you will create will always have 7 cells, even if the value inside is null

Update column value of CSV in C#

In the coding, I want to replace the column value of CSV. However, it can`t replace the value in CSV.
CSV file:
"Name","Age"
"michael","16"
"miko","15"
"Tom","24"
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string text = File.ReadAllText(#"C:\test.csv");
TestDataModel users = new TestDataModel();
text = users.Name.Replace("m", "n");
File.WriteAllText(#"C:\test.csv", text);
}
public class TestDataModel
{
public string Name { get; set; }
public int Age { get; set; }
}
}
}
there is a lot of misconception in code you provided, and some of the solutions for your problem might not be begginer friendly.
Especially when they are not 'global' solutions. For your case I tried to explain parts of code in comments
using System.Text.RegularExpressions;
var csvFilePath = #"C:\test.csv";
// Split csv file into lines instead of raw text.
string[] csvText = File.ReadAllLines(csvFilePath);
var models = new List<TestDataModel>();
// Regex that matches your CSV file.
// Explained here: https://regex101.com/r/t589CW/1
var csvRegex = new Regex("\"(.*)\",\"(.*)\"");
for (int i = 0; i < csvText.Length; i++)
{
// Skip headers of file.
// That is: "Name","Age"
if (i == 0)
{
continue;
}
// Check for potential white spaces at the end of the file.
if (string.IsNullOrWhiteSpace(csvText[i]))
{
continue;
}
models.Add(new TestDataModel
{
// Getting a name from regex group match.
Name = csvRegex.Match(csvText[i]).Groups[1].Value,
// Getting an age from regex group and parse it into integer.
Age = int.Parse(csvRegex.Match(csvText[i]).Groups[2].Value),
});
}
// Creating headers for altered CSV.
string alteredCsv = "\"Name\",\"Age\"\n";
// Loop through your models to modify them as you wish and add csv text in correct format.
foreach (var testDataModel in models)
{
testDataModel.Name = testDataModel.Name.Replace('m', 'n');
alteredCsv += $"\"{testDataModel.Name}\",\"{testDataModel.Age}\"\n";
}
var outputFilePath = #"C:\test2.csv";
File.WriteAllText(outputFilePath, alteredCsv);
public class TestDataModel
{
public string Name { get; set; }
public int Age { get; set; }
}
However this answer contains many topics that you might want to get familiar with such as:
Regex/Regex in C#
Data Serialization/Deserialization
Working with Linq
String templates
I/O Operations
Try this
public static void Replace()
{
string text = File.ReadAllText(#"C:\test.csv");
string _text = text.Replace("m", "n");
File.WriteAllText(#"C:\New_test.csv", _text);
}

Read csv file and output into a list box & list that can be edited

I'm trying to make a Windows Forms App that allows the user to load a chosen csv file (any csv file that has the same format) and be able to edit the list. The csv file has to be opened using OpenFileDialog and output into a list box in a formatted way. Once the user has loaded the csv file, the option to change the data of the list needs to be added.
Form code:
public partial class inventoryForm : Form
{
OpenFileDialog ipFile = new OpenFileDialog();
public inventoryForm()
{
InitializeComponent();
}
private void loadInvDataButton_Click(object sender, EventArgs e)
{
inventoryListBox.Items.Clear(); //clear listbox items
if (ipFile.ShowDialog() == DialogResult.OK) //show dialog box
{
Inventory inventory = new Inventory();
var inventories = inventory.Load(ipFile.FileName);
//sets the datasource of the list box to the collection of inventory
//by default it calls the ToString() method which which overrode
//to provide columar output
inventoryListBox.DataSource = inventories;
}
}
Class code:
public class Inventory
{
public string Id { get; set; }
public string ItemName { get; set; }
public int StartingQty { get; set; }
public int QtyMinRestck { get; set; }
public int QtySold { get; set; }
public int QtyRStcked { get; set; }
public decimal UnitPrice { get; set; }
public Inventory()
{
}
//this overrides the default .ToString() method to provide
//columnar output and formats the UnitPrice to currrency
//this requires the following: using System.Globalization;
public override string ToString()
{
return String.Format("{0}{1}{2}{3}{4}{5}{6}"
, Id.PadRight(20, ' ')
, ItemName.PadRight(20, ' ')
, StartingQty.ToString().PadLeft(20, ' ')
, QtyMinRestck.ToString().PadLeft(20, ' ')
, QtySold.ToString().PadLeft(20, ' ')
, QtyRStcked.ToString().PadLeft(20, ' ')
, UnitPrice.ToString("C", CultureInfo.CurrentCulture).PadLeft(20, ' '));
}
//this loads a collection of inventory objects from a file
//it would ignore any lines with errors
public IEnumerable<Inventory> Load(string InventoryFileName)
{
var inventories = new List<Inventory>();
using (var sr = new StreamReader(InventoryFileName))
{
sr.ReadLine(); //skip the first line
while (!sr.EndOfStream)
{
try
{
var fields = sr.ReadLine().Split(',');
inventories.Add(new Inventory
{
Id = fields[0]
,
ItemName = fields[1]
,
StartingQty = Int32.Parse(fields[2])
,
QtyMinRestck = Int32.Parse(fields[3])
,
QtySold = Int32.Parse(fields[4])
,
QtyRStcked = Int32.Parse(fields[5])
,
UnitPrice = Decimal.Parse(fields[6])
});
}
catch
{
//handle error here
}
}
}
return inventories;
}
}
I was told that this code I have needs to be serialized because it's currently deserialized. I'm not sure how to do this. I believe my current code does not allow the user to edit even if the code was added.
A ListBox can display your class but is ill-suited for all your other tasks.
Instead go for a DataGridView!
First look up how to 'read cvs to DataTable' and do it.
Next how to 'bind DataTable to DataGridView'.
Finally how to 'serialize a DataTable'.
While the display in a ListBox may be ok, as long as you restrict it to fixed font, you can't edit it.
Your class has only simple data types and can be made serializable by simply attatching the [Serializable] attribute. This would apply to a List<Inventory> as well but there is a better way.
DataTable is serializable out of the box and a DGV lets you edit all cells by default..
The resulting code will consist of one method to load cvs to xml (GetDataTableFromCsv) and one or two lines each to save and load the data to xml (Serialization). All are to be found in the first one or two google hits.
enter code here
head = reader.ReadLine();
while(!reader.EndOfStream)
{
string[] line = reader.ReadLine().Split(';');
Class rep;
if (line[2] == "2.0")
rep = new Class2(line);
else if (line[2] == "2.1")
rep = new Class21(line);
else
rep = new Class51(line);
listReproduktoru.Add(rep);
}
}
UpdateView();
enter code here

Load .txt File to GridControl (DevExpress)

I was searching in the NET but didn't found anything useful for my case .
I'am using DevExpress/GridControl ... and I have no idea how I can load .txt in GridControl with two Columns (Split on txt ==> '/t' )
DevExpress recommend using BindingList<T> to create binding data at runtime. You then assign this to the GridControl.DataSource property. See here for more info.
You would need to create an intermediate class (or struct) to hold a row of data from your text file:
public class TextFileData
{
public TextFileData(string columnA, string columnB)
{
ColumnA = columnA;
ColumnB = columnB;
}
public string ColumnA { get; set; }
public string ColumnB { get; set; }
}
Then implement a method to open the file, sequentially read and convert the line to TextFileData and add it to your BindingList<TextFileData> instance. Something like:
public static class TextFileReader
{
public static BindingList<TextFileData> Read(string path)
{
var list = new BindingList<TextFileData>();
using (StreamReader sr = new StreamReader(path))
{
while (sr.Peek() >=0)
{
String line = sr.ReadLine();
string[] columns = line.Split('\t')
list.Add(new TextFileData(columns[0], columns[1]));
}
}
return list;
}
}

C# Best way to store strings from an input file for manipulation and use?

I've got a file of blocks of strings, each which end with a certain keyword. I've currently got a stream reader setup which adds each line of the file to a list up until the end of the current block(line contains keyword indicating end of block).
listName.Add(lineFromFile);
Each block contains information e.g. Book bookName, Author AuthorName, Journal JournalName etc. So each block is hypothetically a single item (book, journal, conference etc)..
Now with around 50 or so blocks of information(items) i need some way to store the information so i can manipulate it and store each author(s), Title, pages etc. and know what information goes with what item etc.
While typing this I've come up with the idea of possibly storing each Item as an object of a class called 'Item', however with potentially several authors, I'm not sure how to achieve this, as i was thinking maybe using a counter to name a variable e.g.
int i = 0;
String Author[i] = "blahblah";
i++;
But as far as i know it's not allowed? So my question is basically what would be the simplest/easiest way to store each item so that i can manipulate the strings to store each item for use later.
#yamen here's an example of the file:
Author Bond, james
Author Smith John A
Year 1994
Title For beginners
Book Accounting
Editor Smith Joe
Editor Doe John
Publisher The University of Chicago Press
City Florida, USA
Pages 15-23
End
Author Faux, M
Author Sedge, M
Author McDreamy, L
Author Simbha, D
Year 2000
Title Medical advances in the modern world
Journal Canadian Journal of medicine
Volume 25
Pages 1-26
Issue 2
End
Author McFadden, B
Author Goodrem, G
Title Shape shifting dinosaurs
Conference Ted Vancouver
City Vancouver, Canada
Year 2012
Pages 2-6
End
Update in lieu of your sample
How to parse the string is beyond the scope of this answer - you might want to have a go at that yourself, and then ask another SO (I suggest reading the golden rules of SO: https://meta.stackexchange.com/questions/128548/what-stack-overflow-is-not).
So I'll present the solution assuming that you have a single string representing the full block of book/journal information (this data looks like citations). The main change from my original answer is that you have multiple authors. Also you might want to consider whether you want to transform the authors' names back to [first name/initial] [middle names] [surname].
I present two solutions - one using Dictionary and one using Linq. The Linq solution is a one-liner.
Define an Info class to store the item:
public class Info
{
public string Title { get; private set; }
public string BookOrJournal { get; private set; }
public IEnumerable<string> Authors { get; private set; }
//more members of pages, year etc.
public Info(string stringFromFile)
{
Title = /*read book name from stringFromFile */;
BookOrJournalName = /*read journal name from stringFromFile */;
Authors = /*read authors from stringFromFile */;
}
}
Note that the stringFromFile should be one block, including newlines, of citation information.
Now a dictionary to store each info by author:
Dictionary<string, List<Info>> infoByAuthor =
new Dictionary<string, List<Info>>(StringComparer.OrdinalIrgnoreCase);
Note the OrdinalIgnoreCase comparer - to handle situations where an author's name is printed in a different case.
Given a List<string> that you're adding to as per your listName.Add, this simple loop will do the trick:
List<Info> tempList;
Info tempInfo;
foreach(var line in listName)
{
if(string.IsNullOrWhiteSpace(line))
continue;
tempInfo = new Info(line);
foreach(var author in info.Authors)
{
if(!infoByAuthor.TryGetValue(author, out tempList))
tempInfo[author] = tempList = new List<Info>();
tempList.Add(tempInfo);
}
}
Now you can iterate through the dictionary, and each KeyValuePair<string, List<Info>> will have a Key equal to the author name and the Value will be the list of Info objects that have that author. Note that the casing of the AuthorName will be preserved from the file even though you're grouping case-insensitively such that two items with "jon skeet" and "Jon Skeet" will be grouped into the same list, but their original cases will be preserved on the Info.
Also the code is written to ensure that only one Info instance is created per citation, this is preferable for many reasons (memory, centralised updates etc).
Alternatively, with Linq, you can simply do this:
var grouped = listName.Where(s => !string.IsNullOrWhiteSpace(s))
.Select(s => new Info(s))
.SelectMany(i =>
s.Authors.Select(ia => new KeyValuePair<string, Info>(ia, i))
.GroupBy(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
Now you have enumerable of groups, where the Key is the Author Name and the inner enumerable is all the Info objects with that author name. The same case-preserving behaviour regarding 'the two Skeets' will be observed here, too.
Here is the complete code for this problem.
It is written with a simple, straight forward approach. It can be optimized, there's no error checking and the AddData Method can be written in a much more efficient way by using reflection. But it does the job in an elegant way.
using System;
using System.Collections.Generic;
using System.IO;
namespace MutiItemDict
{
class MultiDict<TKey, TValue> // no (collection) base class
{
private Dictionary<TKey, List<TValue>> _data = new Dictionary<TKey, List<TValue>>();
public void Add(TKey k, TValue v)
{
// can be a optimized a little with TryGetValue, this is for clarity
if (_data.ContainsKey(k))
_data[k].Add(v);
else
_data.Add(k, new List<TValue>() { v });
}
public List<TValue> GetValues(TKey key)
{
if (_data.ContainsKey(key))
return _data[key];
else
return new List<TValue>();
}
}
class BookItem
{
public BookItem()
{
Authors = new List<string>();
Editors = new List<string>();
}
public int? Year { get; set; }
public string Title { get; set; }
public string Book { get; set; }
public List<string> Authors { get; private set; }
public List<string> Editors { get; private set; }
public string Publisher { get; set; }
public string City { get; set; }
public int? StartPage { get; set; }
public int? EndPage { get; set; }
public int? Issue { get; set; }
public string Conference { get; set; }
public string Journal { get; set; }
public int? Volume { get; set; }
internal void AddPropertyByText(string line)
{
string keyword = GetKeyWord(line);
string data = GetData(line);
AddData(keyword, data);
}
private void AddData(string keyword, string data)
{
if (keyword == null)
return;
// Map the Keywords to the properties (can be done in a more generic way by reflection)
switch (keyword)
{
case "Year":
this.Year = int.Parse(data);
break;
case "Title":
this.Title = data;
break;
case "Book":
this.Book = data;
break;
case "Author":
this.Authors.Add(data);
break;
case "Editor":
this.Editors.Add(data);
break;
case "Publisher":
this.Publisher = data;
break;
case "City":
this.City = data;
break;
case "Journal":
this.Journal = data;
break;
case "Volume":
this.Volume = int.Parse(data);
break;
case "Pages":
this.StartPage = GetStartPage(data);
this.EndPage = GetEndPage(data);
break;
case "Issue":
this.Issue = int.Parse(data);
break;
case "Conference":
this.Conference = data;
break;
}
}
private int GetStartPage(string data)
{
string[] pages = data.Split('-');
return int.Parse(pages[0]);
}
private int GetEndPage(string data)
{
string[] pages = data.Split('-');
return int.Parse(pages[1]);
}
private string GetKeyWord(string line)
{
string[] words = line.Split(' ');
if (words.Length == 0)
return null;
else
return words[0];
}
private string GetData(string line)
{
string[] words = line.Split(' ');
if (words.Length < 2)
return null;
else
return line.Substring(words[0].Length+1);
}
}
class Program
{
public static BookItem ReadBookItem(StreamReader streamReader)
{
string line = streamReader.ReadLine();
if (line == null)
return null;
BookItem book = new BookItem();
while (line != "End")
{
book.AddPropertyByText(line);
line = streamReader.ReadLine();
}
return book;
}
public static List<BookItem> ReadBooks(string fileName)
{
List<BookItem> books = new List<BookItem>();
using (StreamReader streamReader = new StreamReader(fileName))
{
BookItem book;
while ((book = ReadBookItem(streamReader)) != null)
{
books.Add(book);
}
}
return books;
}
static void Main(string[] args)
{
string fileName = "../../Data.txt";
List<BookItem> bookList = ReadBooks(fileName);
MultiDict<string, BookItem> booksByAutor = new MultiDict<string, BookItem>();
bookList.ForEach(bk =>
bk.Authors.ForEach(autor => booksByAutor.Add(autor, bk))
);
string author = "Bond, james";
Console.WriteLine("Books by: " + author);
foreach (BookItem book in booksByAutor.GetValues(author))
{
Console.WriteLine(" Title : " + book.Title);
}
Console.WriteLine("");
Console.WriteLine("Click to continue");
Console.ReadKey();
}
}
}
And I also want to mention that all the parsing stuff can be avoided if you represent the Data in XML.
The Data then looks like:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfBookItem >
<BookItem>
<Year>1994</Year>
<Title>For beginners</Title>
<Book>Accounting</Book>
<Authors>
<string>Bond, james</string>
<string>Smith John A</string>
</Authors>
<Editors>
<string>Smith Joe</string>
<string>Doe John</string>
</Editors>
<Publisher>The University of Chicago Press</Publisher>
<City>Florida, USA</City>
<StartPage>15</StartPage>
<EndPage>23</EndPage>
</BookItem>
<BookItem>
<Year>2000</Year>
<Title>Medical advances in the modern world</Title>
<Authors>
<string>Faux, M</string>
<string>Sedge, M</string>
<string>McDreamy, L</string>
<string>Simbha, D</string>
</Authors>
<StartPage>1</StartPage>
<EndPage>26</EndPage>
<Issue>2</Issue>
<Journal>Canadian Journal of medicine</Journal>
<Volume>25</Volume>
</BookItem>
<BookItem>
<Year>2012</Year>
<Title>Shape shifting dinosaurs</Title>
<Authors>
<string>McFadden, B</string>
<string>Goodrem, G</string>
</Authors>
<City>Vancouver, Canada</City>
<StartPage>2</StartPage>
<EndPage>6</EndPage>
<Conference>Ted Vancouver</Conference>
</BookItem>
</ArrayOfBookItem>
And the code for reading it:
using (FileStream stream =
new FileStream(#"../../Data.xml", FileMode.Open,
FileAccess.Read, FileShare.Read))
{
List<BookItem> books1 = (List<BookItem>)serializer.Deserialize(stream);
}
You should create a class Book
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public string Journal { get; set; }
}
and maintain a List<Book>
var books = new List<Book>();
books.Add(new Book { Name = "BookName", Author = "Some Auther", Journal = "Journal" });
I would use a multi value dictionary for this:
public struct BookInfo
{
public string Title;
public string Journal;
}
Then create a dictionary object:
var dict = new Dictionary<Author, BookInfo>();
This way, if you do run into multiple authors, the data will be sorted by author, which makes writing future code to work with this data easy. Printing out a list of all books under some author will be dead easy and not require a cumbersome search process.
You can use a class with simple attributes like these:
class Book {
string Title;
int PageCount;
}
You can either initialize Book[] lines = Book[myFile.LineCount]; or maintain a List<Book>, but string[] is easier to access individual line numbers (lines[34] means 34'th book, and 34th line).
But basically a System.Data.DataTable may be better suited, because you have rows that contain multiple columns. With DataTable, you can access individual rows and access their columns by name.
Example:
DataTable dt = new DataTable();
DataTable.Columns.Add("bookName");
DataRow dr = dt.NewRow();
dr["bookName"] = "The Lost Island";
dt.Rows.Add(dr);
//You can access last row this way:
dt.Rows[dt.Rows.Count-1]["bookName"].
One more good thing about a DataTable is that you can use grouping and summing on its rows like on an ordinary SQL table.
Edit: Initially my answer used structs but as #AndrasZoltan pointed out, it may be better to use classes when you're not sure what the application will evolve in.
You are well on your way to inventing the relational database. Conveniently, these are already available. In addition to solving the problem of storing relationships between entities, they also handle concurrency issues and are supported by modelling techniques founded in provable mathematics.
Parsers are a subject unto themselves. Since SQL is out of the question, this being a contrived university assignment, I do have some observations.
The easy way is with a regex. However this is extremely inefficient and a poor solution for large input files.
In the absence of regexes, String.IndexOf() and String.Split() are your friends.
If your assessor can't cope with SQL then LINQ is going to be quite a shock, but I really really like Zoltan's LINQ solution, it's just plain elegant.
Its not quite clear what you need without a better example of the file or how you want to use the data but it sounds like you need to parse the string and put it into an entity. The following is an example using the fields you mentioned above.
public IList<Entry> ParseEntryFile(string fileName)
{
...
var entries = new List<Entry>();
foreach(var line in file)
{
var entry = new Entry();
...
entries.Add(entry);
}
return entries;
}
public class Entry
{
public Book BookEntry { get; set; }
public Author AuthorEntry { get; set; }
public Journal JournalEntry { get; set; }
}
public class Book
{
public string Name{ get; set; }
...
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
...
You can create a class for each item:
class BookItem
{
public string Name { get; set; }
public string Author { get; set; }
}
Read the data from each line into an instance of this class and store them in a temporary list:
var books = new List<BookItem>();
while (NotEndOfFile())
{
BookItem book= ReadBookItem(...)
books.Add(book);
}
After you have this list you can create Multi Value Dictionaries and have quick access to any item by any key. For example to find a book by its author:
var booksByAuthor = new MultiDict<string, BookItem>();
add the items to the Dictionary:
books.ForEach(bk => booksByAuthor.Add(bk.Author, bk));
and then you can iterate on it:
string autorName = "autor1";
Console.WriteLine("Books by: " + autorName);
foreach (BookItem bk1 in booksByAutor)
{
Console.WriteLine("Book: " + bk1.Name);
}
I got the basic Multi Item Dictionary from here:
Multi Value Dictionary?
This is my implementation:
class MultiDict<TKey, TValue> // no (collection) base class
{
private Dictionary<TKey, List<TValue>> _data = new Dictionary<TKey, List<TValue>>();
public void Add(TKey k, TValue v)
{
// can be a optimized a little with TryGetValue, this is for clarity
if (_data.ContainsKey(k))
_data[k].Add(v);
else
_data.Add(k, new List<TValue>() { v });
}
// more members
public List<TValue> GetValues(TKey key)
{
if (_data.ContainsKey(key))
return _data[key];
else
return new List<TValue>();
}
}

Categories