Efficient way to Sort CSV raw string data - c#
I have a raw csv data as mentioned below
James,Mary,Patricia,Anthony,Donald\n
145,10,100,39,101\n
21,212,313,28,1
In above mentioned string, columns are comma , separated, first line is column and after each \n a new row where the data is for each person. What I am trying to achieve here is it should be sorted as mentioned below.
Anthony,Donald,James,Mary,Patricia\n
39,101, 145,10,100\n
28,1,21,212,313
What I have tried so far is, splitting based on \n, further splitting based on comma , for each value, but in this case there will be no proper reference to sort value.
What Part I am Struggling with
string data = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
var rows = data.Split('\n');
var unorderedNames = rows[0].Split(',');
Split Array based on \n
Split Names based on , comma -
Now, If I implement sorting, I believe I will loose all references because names will be sorted but below in row 2 and 3, mentioned payments will not.
In my code mentioned above, The first line split the array into three based on \n. Then when I soft first line, I beleive I don't have reference of other values in the same array.
I appreciate if you can assist me to find some efficient method to convert this raw data into sored alphabetically with values in efficient way.
public class StackDemo
{
private string source = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
public string ProcessString()
{
var rows = source.Split('\n');
var row1Values = rows[0].Split(',');
var row2Values = rows[1].Split(',');
var row3Values = rows[2].Split(',');
List<Person> people = new List<Person>();
for (int index = 0; index < 5; index++)
{
people.Add(new Person()
{
Name = row1Values[index],
SomeValue = row2Values[index],
OtherValue = row3Values[index]
});
}
people.Sort((x, y) => x.Name.CompareTo(y.Name));
List<string> names = new List<string>();
List<string> someValues = new List<string>();
List<string> otherValues = new List<string>();
foreach (Person p in people)
{
names.Add(p.Name);
someValues.Add(p.SomeValue);
otherValues.Add(p.OtherValue);
}
string result = "";
result = BuildString(names, result);
result = BuildString(someValues, result);
result = BuildString(otherValues, result);
result = result.Remove(result.Length - 1, 1);
return result;
}
private static string BuildString(List<string> names, string result)
{
foreach (string s in names)
{
result += s + ",";
}
result = result.Remove(result.Length - 1, 1);
result += "\n";
return result;
}
}
public class Person
{
public string Name { get; set; }
public string SomeValue { get; set; }
public string OtherValue { get; set; }
}
This code is extremely basic, (rude) but it does what I think you want?)
Also it returns the string in the same format as it was received.
EDIT: Expanded on comment question!
Added some unit tests to help validate how I understood your question:
public class UnitTest1
{
[Fact]
public void TestWith5()
{
string input = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
string expected = "Anthony,Donald,James,Mary,Patricia\n39,101,145,10,100\n28,1,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith4()
{
string input = "James,Mary,Patricia,Anthony,\n145,10,100,39,\n21,212,313,28,";
string expected = ",Anthony,James,Mary,Patricia\n,39,145,10,100\n,28,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith3()
{
string input = "James,Mary,Patricia,,\n145,10,100,,\n21,212,313,,";
string expected = ",,James,Mary,Patricia\n,,145,10,100\n,,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith2()
{
string input = ",,James,Mary,\n,,145,10,\n,,21,212,";
string expected = ",,,James,Mary\n,,,145,10\n,,,21,212";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith1()
{
string input = "James,,,,\n145,,,,\n21,,,,";
string expected = "James,,,,\n145,,,,\n21,,,,";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith0()
{
string input = ",,,,\n,,,,\n,,,,";
string expected = ",,,,\n,,,,\n,,,,";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
}
Here is the actual implementation:
public interface IStringPeopleParser
{
List<Person> ConvertToPeople(string input);
}
public interface IPeopleStringParser
{
string ConvertPeopleToString(List<Person> people);
}
public class PeopleStringParser : IPeopleStringParser
{
public string ConvertPeopleToString(List<Person> people)
{
List<string> names = new List<string>();
List<string> someValues = new List<string>();
List<string> otherValues = new List<string>();
foreach (Person p in people)
{
names.Add(p.Name);
someValues.Add(p.SomeValue);
otherValues.Add(p.OtherValue);
}
string output = "";
output += string.Join(",", names);
output += "\n";
output += string.Join(",", someValues);
output += "\n";
output += string.Join(",", otherValues);
return output;
}
}
public class StringPeopleParser : IStringPeopleParser
{
public List<Person> ConvertToPeople(string source)
{
var rows = source.Split('\n');
string[] row1Values = rows[0].Split(',');
string[] row2Values = rows[1].Split(',');
string[] row3Values = rows[2].Split(',');
List<Person> people = new List<Person>();
for (int index = 0; index < row1Values.Length; index++)
{
people.Add(new Person()
{
Name = row1Values[index],
SomeValue = row2Values[index],
OtherValue = row3Values[index]
});
}
return people;
}
}
public class StackDemo3
{
IStringPeopleParser stringPeopleParser = new StringPeopleParser();
IPeopleStringParser peopleStringParser = new PeopleStringParser();
public string ProcessString(string s) {
List<Person> people = stringPeopleParser.ConvertToPeople(s);
int validCount = people.Where(x => x.IsValid()).Count();
switch (validCount)
{
case 0:
case 1:
{
return peopleStringParser.ConvertPeopleToString(people);
}
case 2:
case 3:
case 4:
case 5:
{
people = people.OrderBy(x => x.Name).ToList();
return peopleStringParser.ConvertPeopleToString(people);
}
default:
{
return "";//outside bounds of reality. Should never happen.
}
}
}
}
public class Person
{
public string Name { get; set; }
public string SomeValue { get; set; }
public string OtherValue { get; set; }
public bool IsValid() {
if (string.IsNullOrWhiteSpace(Name) || string.IsNullOrWhiteSpace(SomeValue) || string.IsNullOrWhiteSpace(OtherValue))
{
return false;
}
return true;
}
}
Also I don't really know why you don't want the person class?
You need to have a reference between the 3 values possible in each row (the index value is the key) by creating the Person class, the class instance becomes said reference.
I think the problem is you want to sort the headers of your CSV into some "whatever" order, and have the data "go with it"
Come up with some way to represent your data as a 2D array:
var lines = File.ReadAllLines("path");
var data = lines.Skip(1).Select(line => line.Split(',')).ToArray(); //nasty way of parsing a CSV but it's accessory to this discussion..
var head = lines[0]
.Split(',')
.Select((s,i) => new { Name = s, Index = i })
.OrderBy(at => at.Name)
.ToArray();
head is now the sorted headers, but it has an additional property that tells you what column in data holds that person's data. Anthony is first in heaD, but their Index is 3 so we should get Anthony's data from data[3]
foreach(var person in head){
Console.WriteLine($"Now printing {person.name} data from column {person.Index}");
foreach(var line in data){
Console.Writeline(line[person.Index]);
}
}
We didn't bother sorting the data (it's more efficient not to), we just stored what column it's in as part of the object that does get sorted, and then regardless of person sort order, we access the data via that column. Sorting head is very fast, because it's just a few names. It always maintains its map of "where is the data" because Index doesn't change regardless of the sort order of head
Related
How to build a dynamic string from a List of Objects?
Lets say that I have a list of class called Filter public class Filter { [JsonProperty("field")] public string ColumnName { get; set; } [JsonProperty("value")] public string ColumnValue { get; set; } [JsonProperty("operator")] public object Operator {get; set;} public override string ToString() { return String.Format("{0} in ('{1}')", this.ColumnName, this.ColumnValue); } } I need I could have a list of Filters where the column name could be the same. So I could have an instance of ColumnName "LoanNumber" multiple times one for each instance of the class in the List. Each LoanNumber will have a completely different value. So I need to gather all of the values and put them under one single LoanNumber for an In clause string. How can I loop through the list of filters and build a string that looks like this string where = "LoanNum in (1234,456, 55676) and Dates in (01/01/2019, 01/02/2019)"; So far I am having trouble getting all of the data to look like above private string CreateWhereClause(List<Filter> filter) { StringBuilder sb = new StringBuilder(); foreach(var f in filter) { if (!sb.ToString().Contains(f.ColumnName)) { sb.Append(f.ToString() + " AND "); }else { sb.Append(f.ToString() + " AND "); } } sb.Remove(sb.Length - 4, 4); return sb.ToString(); } The problem with above is I get a string that looks like this LoanNum in (1234) and LoanNum in (3456) and Dates in (...) and Dates in (...),
I hope I understood you correctly. You could do var list = new List<Filter> { new Filter{ColumnName="LoanNum",ColumnValue="1234"}, new Filter{ColumnName="Dates",ColumnValue="01/01/2019"}, new Filter{ColumnName="LoanNum",ColumnValue="456"}, new Filter{ColumnName="Dates",ColumnValue="01/02/2019"}, new Filter{ColumnName="LoanNum",ColumnValue="55676"}, }; var subResult = list .GroupBy(filter => filter.ColumnName) .Select(group => string.Format("{0} in ({1})", group.Key, string.Join(",", group.Select(filter => filter.ColumnValue)))); var where = string.Join(" and ", subResult); Output LoanNum in (1234,456,55676) and Dates in (01/01/2019,01/02/2019)
I believe GroupBy and Select is the best way to go, but in case you were wondering how to do it in a loop as you first started doing, one way would be to use a dictionary, where the Key is the ColumnName, and the Value is a comma-separated list of the associated ColumnValue strings: private static string CreateWhereClause(List<Filter> filters) { if (filters == null) return null; if (!filters.Any()) return string.Empty; var results = new Dictionary<string, string>(); foreach (var filter in filters) { if (results.ContainsKey(filter.ColumnName)) { results[filter.ColumnName] += $",'{filter.ColumnValue}'"; } else { results.Add(filter.ColumnName, $"'{filter.ColumnValue}'"); } } return string.Join(" AND ", results.Select(result => $"{result.Key} IN ({result.Value})")); } Putting it to use might look like: private static void Main() { var filters = new List<Filter> { new Filter {ColumnName = "LoanNum", ColumnValue = "1234"}, new Filter {ColumnName = "Dates", ColumnValue = "01/01/2019"}, new Filter {ColumnName = "LoanNum", ColumnValue = "456"}, new Filter {ColumnName = "Dates", ColumnValue = "01/02/2019"}, new Filter {ColumnName = "LoanNum", ColumnValue = "55676"}, }; var query = $"SELECT LOANS WHERE {CreateWhereClause(filters)}"; Console.WriteLine(query); Console.ReadKey(); } Output
Issues with creating a JSON string
My brain is not working and I am trying to make something harder than I think it really should be and I need another set of eyes. I have the following in a text file |TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37 |TS|170703/0505|170703/0905|40.22 -97.30 38.63 -97.30 38.19 -101.03 39.78 -101.03 what the above means...(|watchtype|watchstart|watchend| lat/long pairs) The problem I'm having is that I need to take EACH ROW (could be 0 or could be 100+) and create a polygon on a map to mark the location of these storm watches. I currently have the following. MODEL public class WatchPolygons { public string WatchType { get; set; } public string WatchStart { get; set; } public string WatchEnd { get; set; } public List<lat_longPairs> Lat_Long_Pairs {get; set;} } public class lat_longPairs { public decimal latitude { get; set; } public decimal longitude { get; set; } } CONTROLLER public JsonResult GetWatchPath() { var watchFilePaths = ConfigurationManager.AppSettings["watchFilePath"]; return Json(Directory.GetFiles(Server.MapPath(watchFilePaths), "current*.txt"), JsonRequestBehavior.AllowGet); } [HttpGet] public ActionResult GetWatchData(string watchPath) { var stringData = new List<string>(); using (var reader = new StreamReader(watchPath)) { while (!reader.EndOfStream) { var data = reader.ReadLine().Trim(); if (!string.IsNullOrEmpty(data)) stringData.Add(data); } } return Json((from item in stringData select item.Split(new char [] { '|' }, StringSplitOptions.RemoveEmptyEntries) into rawData select new WatchPolygons { WatchType = rawData[0], WatchStart = rawData[1], WatchEnd = rawData[2] }).ToList(), JsonRequestBehavior.AllowGet); } I know I am missing the latlong pairs = rawData. I don't have it in the code because in the model it's a list and I can't easily convert the list to string the way I need it. What am I missing? I believe I need to read over each line then read over each group to get the lat/long pairs. Just not sure.
You just need to parse rawData[3], containing the string with space separated lat/lon pairs. This is a naive implementation that will break when the input string does not contain pairs of numbers or when the current locale doesn't use a dot as a decimal separator: private static List<lat_longPairs> ParseLatLon(string input) { var numbers = input.Split(new [] { " " }, StringSplitOptions.RemoveEmptyEntries) .Select(i => decimal.Parse(i)) .ToArray(); var latLonPairs = new List<lat_longPairs>(); for (int i = 0; i < numbers.Length; i += 2) { latLonPairs.Add(new lat_longPairs { latitude = numbers[i], longitude = numbers[i + 1], }); } return latLonPairs; } Then call it from where you're projecting the polygons: select new WatchPolygons { WatchType = rawData[0], WatchStart = rawData[1], WatchEnd = rawData[2], Lat_Long_Pairs = ParseLatLon(rawData[3]) } You may want to move the parsing code away from the controller as well, into its own class.
You can use GroupBy since two set make a coordinate. var str = "|TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37"; int itemsInGroup = 2; var pairs = str.Split('|')[4].Split(' '). // Give each set of coordinate a group number. Select((n, i) => new { GroupNumber = i / itemsInGroup, Number = n }). GroupBy(n => n.GroupNumber). Select(g => { var coordinate = g.Select(n => n.Number).ToList(); return new lat_longPairs { latitude = decimal.Parse(coordinate[0], NumberFormatInfo.InvariantInfo), longitude = decimal.Parse(coordinate[1], NumberFormatInfo.InvariantInfo), }; });
Custom File Parser
I am building a parser for a custom pipe delimited file format and I am finding my code to be very bulky, could someone suggest better methods of parsing this data? The file's data is broken down by a line delimited by a pipe (|), each line starts with a record type, followed by an ID, followed by different number of columns after. Ex: CDI|11111|OTHERDATA|somemore|other CEX001|123131|DATA|data CCC|123131|DATA|data1|data2|data3|data4|data5|data6 . I am splitting by pipe, then grabbing the first two columns, and then using a switch checking the first line and calling a function that will parse the remaining into an object purpose built for that record type. I would really like a more elegant method. public Dictionary<string, DataRecord> Parse() { var data = new Dictionary<string, DataRecord>(); var rawDataDict = new Dictionary<string, List<List<string>>>(); foreach (var line in File.ReadLines(_path)) { var split = line.Split('|'); var Id = split[1]; if (!rawDataDict.ContainsKey(Id)) { rawDataDict.Add(Id, new List<List<string>> {split.ToList()}); } else { rawDataDict[Id].Add(split.ToList()); } } rawDataDict.ToList().ForEach(pair => { var key = pair.Key.ToString(); var values = pair.Value; foreach (var value in values) { var recordType = value[0]; switch (recordType) { case "CDI": var cdiRecord = ParseCdi(value); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord } }); } else { data[key].CdiRecords.Add(cdiRecord); } break; case "CEX015": var cexRecord = ParseCex(value); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CexRecords = new List<Cex015Record>() { cexRecord } }); } else { data[key].CexRecords.Add(cexRecord); } break; case "CPH": CphRecord cphRecord = ParseCph(value); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CphRecords = new List<CphRecord>() { cphRecord } }); } else { data[key].CphRecords.Add(cphRecord); } break; } } }); return data; }
Try out FileHelper, here is your exact example - http://www.filehelpers.net/example/QuickStart/ReadFileDelimited/ Given you're data of CDI|11111|OTHERDATA|Datas CEX001|123131|DATA CCC|123131 You could create a class to model this to allow FileHelpers to parse the delimited file: [DelimitedRecord("|")] public class Record { public string Type { get; set; } public string[] Fields { get; set; } } Then we could allow FileHelpers to parse in to this object type: var engine = new FileHelperEngine<Record>(); var records = engine.ReadFile("Input.txt"); After we've got all the records loaded in to Record objects we can use a bit of linq to pull them in to their given types var cdis = records.Where(x => x.Type == "CDI") .Select(x => new Cdi(x.Fields[0], x.Fields[1], x.Fields[2]) .ToArray(); var cexs = records.Where(x => x.Type == "CEX001") .Select(x => new Cex(x.Fields[0], x.Fields[1) .ToArray(); var cccs = records.Where(x => x.Type == "CCC") .Select(x => new Ccc(x.Fields[0]) .ToArray(); You could also simplify the above using something like AutoMapper - http://automapper.org/ Alternatively you could use ConditionalRecord attributes which will only parse certain lines if they match a given criteria. This will however be slower the more record types you have but you're code will be cleaner and FileHelpers will be doing most of the heavy lifting: [DelimitedRecord("|")] [ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CDI")] public class Cdi { public string Type { get; set; } public int Number { get; set; } public string Data1 { get; set; } public string Data2 { get; set; } public string Data3 { get; set; } } [DelimitedRecord("|")] [ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CEX001")] public class Cex001 { public string Type { get; set; } public int Number { get; set; } public string Data1 { get; set; } } [DelimitedRecord("|")] [ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CCC")] public class Ccc { public string Type { get; set; } public int Number { get; set; } } var input = #"CDI|11111|Data1|Data2|Data3 CEX001|123131|Data1 CCC|123131"; var CdiEngine = new FileHelperEngine<Cdi>(); var cdis = CdiEngine.ReadString(input); var cexEngine = new FileHelperEngine<Cex001>(); var cexs = cexEngine.ReadString(input); var cccEngine = new FileHelperEngine<Ccc>(); var cccs = cccEngine.ReadString(input);
Your first loop isn't really doing anything other than organizing your data differently. You should be able to eliminate it and use the data as it is from the file. Something like this should give you what you want: foreach (var line in File.ReadLines(_path)) { var split = line.Split('|'); var key = split[1]; var value = split; var recordType = value[0]; switch (recordType) { case "CDI": var cdiRecord = ParseCdi(value.ToList()); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord } }); } else { data[key].CdiRecords.Add(cdiRecord); } break; case "CEX015": var cexRecord = ParseCex(value.ToList()); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CexRecords = new List<Cex015Record>() { cexRecord } }); } else { data[key].CexRecords.Add(cexRecord); } break; case "CPH": CphRecord cphRecord = ParseCph(value.ToList()); if (!data.ContainsKey(key)) { data.Add(key, new DataRecord { Id = key, CphRecords = new List<CphRecord>() { cphRecord } }); } else { data[key].CphRecords.Add(cphRecord); } break; } }; Caveat: This is just put together here and hasn't been properly checked for syntax.
Creating a dynamic sort for a data class
I am attempting to create a dynamic sort for my basic data class (just a bunch of properties). The following code demonstrates what I am trying to do. It almost works. The only thing I don't understand is why ThenBy seems to completely resort the list. Can anyone explain why? private void SortList(string[] sortCols) { bool first = true; IOrderedEnumerable<Data> returnVal = null; Console.WriteLine("Sorting test data"); if (sortCols.Length < 1) return; foreach (string col in sortCols) { if (first) { returnVal = _testData.OrderBy(p => typeof(Data).GetProperty(col).GetValue(p, null)); //Or OrderByDescending first = false; } else { returnVal = returnVal.ThenBy(p => typeof(Data).GetProperty(col).GetValue(p, null)); //Or ThenByDescending } } _testData = new List<Data>(returnVal); } _testData is just a List The Data class looks like this: public class Data { public string Name { get; set; } public int Age { get; set; } public double Income { get; set; } public bool Married { get; set; } public override string ToString() { return Name + "; age=" + Age.ToString() + "; income=" + Income.ToString() + "; married=" + Married.ToString(); } }
Unable to duplicate your problem. Everything seems to sort properly: public static void Main() { var data1 = new Data() { Name = "Albert", Age = 99 }; var data2 = new Data() { Name = "Zebra", Age = 1}; var data3 = new Data() { Name = "Zebra", Age = 99}; var data4 = new Data() { Name = "Albert", Age = 1 }; _testData.Add(data1); _testData.Add(data2); _testData.Add(data3); _testData.Add(data4); SortList(new string[] { "Name", "Age" }); foreach(var data in _testData) { Console.WriteLine(data.ToString()); } Console.WriteLine(string.Empty); SortList(new string[] { "Age", "Name" }); foreach(var data in _testData) { Console.WriteLine(data.ToString()); } } Results: Sorting test data Albert; age=1; income=0; married=False Albert; age=99; income=0; married=False Zebra; age=1; income=0; married=False Zebra; age=99; income=0; married=False . Sorting test data Albert; age=1; income=0; married=False Zebra; age=1; income=0; married=False Albert; age=99; income=0; married=False Zebra; age=99; income=0; married=False Update (For .Net 4.0) Changing your code slightly solves the issue (example): private static void SortList(string[] sortCols) { Console.WriteLine("Sorting test data"); if (sortCols.Length < 1) return; IOrderedEnumerable<Data> returnVal = _testData.OrderBy(p => typeof(Data).GetProperty(sortCols[0]).GetValue(p, null)); foreach (string col in sortCols.Skip(1)) { returnVal = returnVal.ThenBy(p => typeof(Data).GetProperty(col).GetValue(p, null)); //Or ThenByDescending } _testData = returnVal.ToList(); }
You're better off creating an IComparer that explicitly does a comparison for each property (so you could have a public property on it that specifies the property order). You could then just sort once. Something like so public class DataComparer : Comparer<Data> { private readonly IList<string> _sortedProperties; public DataComparer(IEnumerable<string> sortedProperties) { _sortedProperties = new List<string>(sortedProperties); } public override int Compare(Data x, Data y) { int result = 0; foreach (var property in _sortedProperties) { if (property == "Name") { result = String.Compare(x.Name, y.Name, StringComparison.Ordinal); } else if (property == "Age") { result = x.Age.CompareTo(y.Age); } // Do other comparisons here if (result != 0) return result; } return 0; } } This implementation will create a DataComparer with the given collection of properties that act as the properties that need to be compared. Then the Compare() will loop through the properties in the desired sort order until it finds a non-matching comparison and return. The end result will be a faster sort operation that outputs something like // "Jim Frost" 13 // "Jim Frost" 24 // "Jim Smith" 11 Where we want to sort on the Name property first, then the Age etc. Edit: You can sort it like so, _testData.Sort(new DataComparer(sortCols));
Combining numbers and names collections
I have 2 List collections. One contains numbers, the other names. There are twice as many numbers as names(always). I want to take the first name from the collection and the first two numbers from the other collection then put them together in a 3rd user collection of (VentriloUser). Then the second name needs to be matched with the 3rd and 4th numbers and so on. I was thinking something with a for or foreach loop, but I can't wrap my head around it right now. public class VentriloUser { public VentriloUser(string name, int seconds, int ping) { Name = name; Seconds = seconds; Ping = ping; } public string Name { get; set; } public int Ping { get; set; } public int Seconds { get; set; } } public class Ventrilo { public Ventrilo(string statusurl) { StatusURL = statusurl; } public string StatusURL { get; set; } public string HTML { get; set; } public List<VentriloUser> Users { get; set; } private Regex findNumbers = new Regex("\\<td width=\"10%\" bgcolor=\"#\\w{6}\"\\>\\<font color=\"#000000\">\\<div style=\"overflow:hidden;text-overflow:ellipsis\"\\>-?\\d+\\<"); private Regex findNames = new Regex("\\<td width=\"20%\" bgcolor=\"#\\w{6}\"\\>\\<font color=\"#000000\">\\<div style=\"overflow:hidden;text-overflow:ellipsis\"\\>\\b\\w+\\<"); private WebClient wClient = new WebClient(); public void DownloadHTML() { HTML = wClient.DownloadString(StatusURL); } public List<int> GetNumbers() { var rawnumbers = findNumbers.Matches(HTML); var numbers = new List<int>(); foreach (var rawnumber in rawnumbers) { var match = Regex.Match(rawnumber.ToString(), "\\>\\-?\\d+\\<"); string number = Regex.Replace(match.ToString(), "\\<|\\>", ""); numbers.Add(Convert.ToInt32(number)); } return numbers; } public List<string> GetNames() { var rawnames = findNames.Matches(HTML); var names = new List<string>(); foreach (var rawname in rawnames) { var match = Regex.Match(rawname.ToString(), "\\>\\w+<"); string name = Regex.Replace(match.ToString(), "\\<|\\>", ""); names.Add(name); } return names; } public List<VentriloUser> GenerateUsers() { var numbers = GetNumbers(); var names = GetNames(); var users = new List<VentriloUser>(); } } I am a hobbyist, but hope to pursue a career one day. Any help is much appreciated. Thank you for your time.
Using LINQ: var users = names.Select((name,idx) => new VentriloUser(name, numbers[idx*2], numbers[idx*2+1])) .ToList(); Using loops: var users = new List<VentriloUser>(); for (int i = 0; i < names.Count; i++) { var name = names[i]; int j = i * 2; if (j >= numbers.Count - 1) break; // to be safe... users.Add(new VentriloUser(name, numbers[j], numbers[j + 1])); }