Best way to group strings according to action - c#

With "Best Way" I mean, maybe, without many If, clean code.
I have a function that receives as parameters (string currentVersion, string action)
and it should return a string versionToBe = "";
For action = "installOldVersion"
-------------if "currentVersion"----------------: -------------OldversionToInstall--------------
"windows10(pro)", "windows10(pro)(education)" : "windows81(pro)"
"windows10(enterprise)", "windows10(enterpise)(lstb)" : "windows81(enterprise)"
"windows7(home)", "windows7(home)(basic)", "windows7(basic)", "windows7": "windowsVista(starter)"
"windowsXP(starter)", "windowsXP(starter)(home)", "windowsXP(home)", "windowsXP": "windows2000(professional)"
"windowsNT(workstation)", "windowsNT": "windows95(sp1)"
For action = "installNewVersion"
-------------if "currentVersion"----------------: -------------NewVersionToInstall--------------
"windows81(pro)", "windows81(pro)(education)" : "windows10(pro)"
"windows81(enterprise)", "windows81(enterprise)(education)" : "windows10(enterprise)"
"windowsVista(starter)", "windowsVista(starter)(package)", "windowsVista(package)", "windowsVista": "windows7(home)"
"windowsVista(starter)", "windowsVista(starter)(praok)", "windowsVista(praok)", "windowsVista": "windowsXP(starter)"
"windows95(sp1)", "windows95(sp1)(versionE)", "windows95": "windowsNT(workstation)"
So,for example, everytime the string name comes like: "windows10(pro)" or "windows10(pro)(education)" it should return: "windows81(pro)".
I know this can get done with lots of if like:
if (version.Equals("windows10(pro)") || version.Equals("windows10(pro)(education)"))
{
versionToBe = "windows81(pro)";
}
and the same for the rest of them, anding with 10 If statements in Total.
But If there's a better way to do it, I'd want to know.
Another restriction, or other thing to consider:
if the action is "installOldVersion", versionToBe is OldversionToInstall,
and if the action is "installNewVersion", versionTobe would be NewVersionToInstall.

You could create a list of objects with CurrentVersion, Old Version and New Version and then extract the one you want from the list.
Example Instruction Class Definition
public class VersionInformation
{
public string CurrentVersion {get; set;}
public string NewVersion {get; set;}
public string OldVersion {get; set;}
}
then in your program, have a list of them, either hard coded or loaded from file or whatever datastore you want and do your version check as follows:
private List<VersionInformation> _versionInformation = //Load your list from wherever;
public void DoVersionCheck(string version)
{
var currentversionInfo = _versionInformation.Single(x=> x.CurrentVersion == version);
//Do Whatever you want with the upgrades and downgrades here based on whatever action you are doing
}

Set yourself up a dictionary and perform a lookup.
As an exercise for the reader:
You could drive the dictionary contents from some configuration or other...even from a database if you want.
You'd presumably want to set up your dictionary as a static and initialize it only once.
You'll want some handling for when there is no dictionary entry - you don't specify a default in your question.
Dictionary, string> ActionMatrix = new Dictionary, string>();
ActionMatrix.Add(Tuple.Create ("windows10(pro)", "installOldVersion"), "windows81(pro)");
ActionMatrix.Add(Tuple.Create ("windows10(pro)(education)", "installOldVersion"), "windows81(pro)");
ActionMatrix.Add(Tuple.Create ("windows10(enterprise)", "installOldVersion"), "windows81(enterprise)");
ActionMatrix.Add(Tuple.Create ("windows10(enterpise)(lstb)", "installOldVersion"), "windows81(enterprise)");
// etc
ActionMatrix.Add(Tuple.Create("windows81(pro)", "installNewVersion"), "windows10(pro)");
ActionMatrix.Add(Tuple.Create("windows81(pro)(education)", "installNewVersion"), "windows10(pro)");
ActionMatrix.Add(Tuple.Create("windows81(enterprise)", "installNewVersion"), "windows10(enterprise)");
ActionMatrix.Add(Tuple.Create("windows10(enterpise)(education)", "installNewVersion"), "windows10(enterprise)");
// etc
public string VersionToBe (string currentVersion, string action)
{
return ActionMatrix[Tuple.Create(currentVersion, action)];
}

A simple object with it's own list should do the trick and is visually better to follow.
public class VersionData
{
private static List<VersionData> VersionDatas { get; set; } = new List<VersionData>()
{
new VersionData( "OldversionToInstall", new [] {"windows10(pro)", "windows10(pro)(education)" }.ToList(), "windows81(pro)" ),
new VersionData( "OldversionToInstall", new [] {"windows10(enterprise)", "windows10(enterpise)(lstb)" }.ToList(), "windows81(enterprise)" )
};
public string Action { get; set; } = "";
public List<string> CurrentVersions { get; set; } = new List<string>();
public string Version { get; set; } = "";
public VersionData(string action, List<string> currentVersions, string version)
{
Action = action;
CurrentVersions = currentVersions;
Version = version;
}
public static string GetVersion(string action, string currentVersion)
{
return VersionDatas.FirstOrDefault(o => o.Action == action && o.CurrentVersions.Any(x => x == currentVersion)).Version;
}
}
and to call it's as simple as :
var oldVersion = VersionData.GetVersion("OldversionToInstall", "windows10(enterpise)(lstb)");

Related

C# / Sorting a text file / IComparer / Custom sort

I have a text file that I want to be sorted.
Each line has a package name, a pipe and a version number.
Examples:
AutoFixture|4.15.0
Castle.Windsor.Lifestyles|0.3.0
I tried to use the default list.Sort() method but I obtained:
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Instead of
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
As shown, I would like "Castle.Windsor" to appear before "Castle.Windsor.Lifestyles".
I'm pretty sure I have to use the IComparer but I can't find a way to get the shorter name first.
So far, I created a custom sort like this which is not working..
public class PackageComparer : IComparer<string>
{
// Assume that each line has the format: name|number
private readonly Regex packageRegEx = new Regex(#"[\w.]+\|[\d.]+", RegexOptions.Compiled);
public int Compare(string x, string y)
{
var firstPackage = this.packageRegEx.Match(x);
var firstLeft = firstPackage.Groups[1].Value;
var firstRight = firstPackage.Groups[2].Value;
var secondPackage = this.packageRegEx.Match(y);
var secondLeft = secondPackage.Groups[1].Value;
var secondRight = secondPackage.Groups[2].Value;
if (firstLeft < secondLeft)
{
return -1;
}
if (firstRight > secondLeft)
{
return 1;
}
return string.CompareOrdinal(firstSceneAlpha, secondSceneAlpha);
}
}
Well, you can use Linq, split by the pipe and order by the package name then by the versioning:
var input = #"AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Castle.Core|3.1.0";
var list = input.Split(new string[]{"\r\n","\n"},StringSplitOptions.None).ToList();
list = list
.OrderBy(x => x.Split('|')[0])
.ThenBy(x => new Version(x.Split('|')[1]))
.ToList();
Outputs:
AutoFixture|4.15.0
Castle.Core|3.1.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
You can do something like this:
public class YourClassName
{
public string PackageName { get; set; }
public string Pipe { get; set; }
public string Version { get; set; }
}
Load your data into list to sort
List<YourClassName> list = souce of data;
list = SortList<YourClassName>(list, "PackageName");
SortList Method:
public List<YourClassName> SortList<TKey>(List<YourClassName> list, string sortBy)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].GetProperty(sortBy);
return list.OrderBy(e => property.GetValue(e, null)).ToList<YourClassName>();
}

Create Intersect of HashSet and remove Items from class structure with extracted ID's

lets assume I have the following classes:
public class ServiceStatistics
{
public string LocalId { get; set; }
public string OrganizationId { get; set; }
public List<StatisticElements> Elements { get; } = new List<StatisticElements>();
}
public class StatisticElements
{
public string StatisticId { get; set; }
public string Type { get; set; }
public string ServiceName { get; set; }
}
I retrieve such ServiceStatistics by a soap service and I use serialization/deserialization.
Each ServiceStatistics contains a set of StatisticElements. I also have a static list of StatisticElements-ID's which are relevant for calculation. All other incoming StatisticElements-ID's can be dropped. I need to do this on my side
because the SOAP Service does not support selecting specific StatisticElements-ID's
So I have generated a static Class with a HashSet:
public static class RelevantDutyPlans
{
private static HashSet<int> relevantDutyPlans;
static RelevantDutyPlans()
{
// only a subset of the original ID's
relevantDutyPlans = new HashSet<int>()
{
530,
1150,
1095,
};
}
public static HashSet<int> GetRelevantDutyPlans()
{
return relevantDutyPlans;
}
public static bool Contains(int planId)
{
return relevantDutyPlans.Contains(planId);
}
// Extracts all DutyPlans which are relevant (HashSet) for validation from
// the incoming data
public static List<int> ExtractRelevantDutyPlans(List<int> planIds)
{
var relevantPlans = new HashSet<int>(planIds);
relevantPlans.IntersectWith(relevantDutyPlans);
return relevantDutyPlans.ToList();
}
}
So my thought was, to create an Intersect like this:
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
List<int> incomingIds = new List<int>();
foreach(var item in statistics)
{
foreach(var element in item.Statistic)
{
incomingIds.Add(int.Parse(element.StatisticId));
}
}
List<int> extract = RelevantDutyPlans.ExtractRelevantDutyPlans(incomingIds);
So now I have a List of ID's which are relevant for further processing. What I want to achieve is to remove all class elements "StatisticElements" with "StatisticId" not contained in the the extract list generated above.
Any ideas?
Any help is very appreciated
How about a little bit different approach. Simply remove irrelevant plans right away!
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
foreach(var item in statistics)
{
item.Elements.RemoveAll(x => !RelevantDutyPlans.Contains(int.Parse(x.StatisticId)));
}
Now you are only left with the relevant once.
Hope you can use selectMany to flatten the collection and proceed the filter.
var filteredItems = statistics.SelectMany(s => s.Elements)
.Where(s => extract.Contains(Convert.ToInt32(s.StatisticId)))
.ToList();
You could also use LINQ to create a new List<> if you need to keep the original statistcs intact - e.g. if you might run multiple plans against it.
var relevantStatistics = statistics.Select(s => new {
LocalId = s.LocalId,
OrganizationId = s.OrganizationId,
Elements = s.Elements.Where(e => !RelevantDutyPlans.Contains(Convert.ToInt32(e.StatisticId))).ToList()
});
Since ServiceStatistics doesn't provide for construction, I return an anonymous object instead, but you could create an appropriate DTO class.

C# Lists - do I use Class Methods (Get/ Set etc) again once the data is in a list?

A quick question on OOP. I am using a list together with a class and class constructor. So I use the class constructor to define the data set and then add each record to my list as the user creates them.
My questions is once the data is in the list and say I want to alter something is it good practice to find the record, create an instance using that record and then use my class methods to do whatever needs doing - and then put it back in the list?
For example below I have my class with constructor. Lets say I only want the system to release strCode if the Privacy field is set to public. Now just using Instances I would use for example Console.WriteLine(whateverproduct.ProductCode) but if the record is already in a list do i take it out of the list - create an instance and then use this method?
class Product
{
private String strCode;
private Double dblCost;
private Double dblNet;
private String strPrivacy;
public Product(String _strCode, Double _dblCost, Double _dblNet, String _strPrivacy)
{
strCode = _strCode;
dblCost = _dblCost;
dblNet = _dblNet;
strPrivacy = _strPrivacy;
}
public string ProductCode
{
get
{
if (strPrivacy == "Public")
{
return strCode;
}
else
{
return "Product Private Can't release code";
}
}
}
Lets say we have the following:
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
private string _test = "Some constant value at this point";
public string GetTest()
{
return _test;
}
public void SetTest()
{
//Nothing happens, you aren't allow to alter it.
//_test = "some constant 2";
}
}
public class Program
{
public static void Main(string[] args)
{
List<Test> listOfTest = new List<Test>()
{
new Test() {Id = 0, Name = "NumberOne", Amount = 1.0M},
new Test() {Id = 1, Name = "NumberTwo", Amount = 2.0M}
};
Test target = listOfTest.First(x => x.Id == 0);
Console.WriteLine(target.Name);
target.Name = "NumberOneUpdated";
Console.WriteLine(listOfTest.First(x => x.Id == 0).Name);
Console.WriteLine(listOfTest.First(x => x.Id == 0).GetTest());//This will alsways be "Some constant value at this point";
Console.ReadLine();
}
}
Technically you could do away with the SetTest method entirely. However, I included it to demonstrate, what it would look like, if you wanted to alter _test.
You don't want to ever create a new instance of a class, you already have an instance of. you can just alter the class where it is allowed by the author of the class, where you need to. And keep that class reference for as long as you need it. Once you are done, the reference will be garbage collected, once the program finds no active reference to your object(instance).

Assign bool value to a property when name of that property is present in another array

This is best I could make the question statement. Please be kind.
Here is the situation:
I have a string "InputValues" which contains values in comma seperated format:
chkAwareness1,chkAwareness2,chkAwareness6,chkAwareness9,chkAwareness13...
I need to fill an object with bool value if the name matches with what I have in above string variable.
example:
if InputValues contains "chkAwareness1" then "public bool chkAwareness1" should set to true, otherwise false.
public class SurveyCheckBox
{
public bool chkAwareness1 { get; set; }
public bool chkAwareness2 { get; set; }
public bool chkAwareness3 { get; set; }
public bool chkAwareness4 { get; set; }
public bool chkAwareness5 { get; set; }
public bool chkAwareness6 { get; set; }
public bool chkAwareness7 { get; set; }
.
.
.
}
public void createObjectSurveyCheckBox(string InputValues)
{
string[] ChkValues = InputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
for (int i = 0; i < NumberOfPropertyInSurveyCheckBox ;i++ )
{
// typeof(SurveyCheckBox).GetProperties()[i].Name
}
}
I searched and I found GetProperties method through which I can get the name of property, but I am unable to figure out the logic.. how to search through the values and assign them to bool properties.
Please help.
You're very close. You just need to change your loop, really. The whole method should look like this:
public void CreateObjectSurveyCheckBox(string inputValues)
{
string[] chkValues = inputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
foreach (string value in chkValues)
{
PropertyInfo propInfo = typeof(SurveyCheckBox).GetProperty(value);
if (propInfo != null)
propInfo.SetValue(surveyChkBoxObj, true);
}
}
P.S. You'll notice I took the liberty of changing your capitalization to something much more standard. If you use capitalization like you had, you're likely to get lynched.
I agree with Tim; I would not use something like this in production code.
public void createObjectSurveyCheckBox(string InputValues)
{
var instance = new SurveyCheckBox();
foreach (var property in typeof(SurveyCheckBox).GetProperties().Where(x => x.Name.Contains("chkAwareness")))
{
if (InputValues.Contains(property.Name))
property.SetValue(instance, true);
}
}
I would write the loop from the other direction, from 0 to MaxchkAwareness;
Sort the input first, before going into the loop.
You would also need an index to the next item in your input array (ChkValues), lets call that chkValueIndex;
If the next item in your input array, ChkValues[chkValueIndex], is "chkAwareness"+i.ToString()
then your property is true, and you increment your array pointer .
otherwise your property is false.
But I think you have to use reflection to set the properties in a loop like that, something like this:
Getting a property reference using reflection
I am sure there are better ways to restructure this and do it entirely different, but it sounds to me like you are trying to do the best you can with the system that was given you.
You can try this:
public static void createObjectSurveyCheckBox(string InputValues)
{
string[] ChkValues = InputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
foreach (var prop in typeof(SurveyCheckBox).GetProperties())
{
if (ChkValues.Contains(prop.Name))
prop.SetValue(surveyChkBoxObj, true);
}
}

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