I am trying to do the crazy formatting instructions my teacher gave me. After perusing for probably an hour (This is my first C# program), I came up with this line of code.
`Console.WriteLine(String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating));`
longestX is an int containing the number of characters of the longestX (where x = title, album, etc).
The output I would like looks something like this:
Stuff | morestuff | extrastuff | 5.92 | 1992:R |
Stuf | est | sfafe | 232.44 | 2001:PG |
S uf | e | sfe | .44 | 2001:G |
(Where all padding is determined dynamically based on the longest title input by the user or file).
The output I get looks like this:
Program_Example.ClassName
Program_Example.ClassName
(or, specifically, Tyler_Music_Go.Song)
I have printed songArray[arraySearcher].title in this same method, and it works fine.
Could someone please help me?
Full relevant code:
class Song {
public string title, albumTitle, yearAndRating, artist;
public float length;
public Song(string titl, string albumTitl, string art, float leng, string yrNRating)
{
title = titl;
albumTitle = albumTitl;
yearAndRating = yrNRating;
length = leng;
artist = art;
}
}
//This class contains a Song array (with all Songs contained within), an array index, a search index, and ints to determine the longest of each category.
class SongList
{
Song[] songArray;
private int arrayKeeper, longestTitle, longestArtist, longestAlbumTitle, longestYearAndRating, checker;
int arraySearcher = 0;
public SongList()
{
songArray = new Song[10000];
arrayKeeper = 0;
longestTitle = 0;
longestArtist = 0;
longestAlbumTitle = 0;
longestYearAndRating = 0;
}
public void AddSong(string title, string albumTitle, string artist, float length, string yearAndRating)
{
songArray[arrayKeeper] = new Song(title, albumTitle, artist, length, yearAndRating);
arrayKeeper++;
checker = 0;
//This section of code is responsible for formatting the output. Since the longest values are already known, the list can be displayed quickly.
//Once a song is deleted, however, previously calculated longest lengths still stand.
foreach (char check in title)
{
checker++;
}
if (checker > longestTitle)
{
longestTitle = checker;
}
foreach (char check in albumTitle)
{
checker++;
}
if (checker > longestAlbumTitle)
{
longestAlbumTitle = checker;
}
foreach (char check in artist)
{
checker++;
}
if (checker > longestArtist)
{
longestArtist = checker;
}
foreach (char check in yearAndRating)
{
checker++;
}
if (checker > longestYearAndRating)
{
longestYearAndRating = checker;
}
}
//public bool RemoveSong(string title)
// {
//}
public void DisplayData()
{
Console.WriteLine("| Title | Album Title | Artist | Length | Year and Rating |");
for (arraySearcher = 0; arraySearcher < arrayKeeper; arraySearcher++)
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
Console.WriteLine(songArray[arraySearcher].ToString());
}
}
public override string ToString()
{
//This line for testing purposes. (works)
Console.WriteLine(songArray[arraySearcher].title);
return String.Format("{0," + -longestTitle + "} | {1," + -longestAlbumTitle + "} | {2," + -longestArtist + "} | {3:0.00, 8} | {4," + -longestYearAndRating + "} |", songArray[arraySearcher].title, songArray[arraySearcher].albumTitle, songArray[arraySearcher].artist, songArray[arraySearcher].length, songArray[arraySearcher].yearAndRating);
}
}
`
EDIT:
Well, now I feel all manor of stupid. I was overwriting the tostring() method for the SongList, and then calling the tostring method for Song. Guy who answered made me realize it. Thanks to everyone who gave me advice, though.
You have to either access a property directly (songVariable.Title) or override ToString() in your song class to have that output the title.
public class Song
{
public string Title {get; set;}
public override string ToString()
{
return Title;
}
}
Related
I am trying to get invisible text that is on a given page using C# Selenium EdgeDriver. I am able to do it, i.e. I get the elements (such as within span, p or b tags, then I am filtering out the elements into a list based on Displayed property, and finally I am calling GetAttribute("textContent") to get the text. The problem I am having is that is slow, about 10 seconds for the page I am doing that on, do you think there is any better way, or making this faster?
Thanks,
public static string GetInvisibleText()
{
Stopwatch s500 = new Stopwatch();
s500.Start();
string returnable = "\r\n";
var elements = driver.FindElements(By.XPath("//b | //span | //p | //a | //h1 | //h2 | //h3 | //h4 | //h5 | //h6 | //div"));
List<string> list = new List<string>();
var displayed_elements = elements.Where(e => !e.Displayed);
foreach(var el in displayed_elements)
{
try
{
string val = el.GetAttribute("textContent");
val = val.Trim();
val = Regex.Replace(val, #"\s+", " ");
list.Add(val);
}
catch (Exception ex)
{
}
}
list = list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
foreach (string line in list)
{
returnable = returnable + line + "\r\n";
}
s500.Stop();
return returnable;
}
I am trying to create a POST endpoint where on success, it will send an HTML email with the POST data. I am struggling trying to iterate over the JSON array of objects and appending that data to a HTML table.
JSON data:
{
"Submitter":[
{
"Obj1":[
"test",
"test2"
]
},
{
"Obj2":[
"test3",
"test4"
]
}
]
}
TestingController:
public class Testing2Controller : ControllerBase
{
public class Submitter
{
public List<string> Obj1 { get; set; }
public List<string> Obj2 { get; set; }
}
public class MyData
{
public List<Submitter> Submitter { get; set; }
}
public string POST([FromBody] MyData data)
{
string composeTableObj1 = #"";
string composeTableObj2 = #"";
foreach (var item in data.Submitter)
{
composeTableObj1 += $"<tr>"; //start row tag
//Column 1 Obj1 data
if (item.Obj1 != null)
{
foreach (var objItem in item.Obj1)
{
composeTableObj1 += $"<td>{objItem}</td>";
}
}
//Column 2 Obj2 data
if (item.Obj2 != null)
{
foreach (var objItem in item.Obj2)
{
composeTableObj1 += $"<td>{objItem}</td>";
}
}
composeTableObj1 += $"</tr>"; //end row tag
}
string body = #$"<table>
<thead>
<tr>
<th>Object 1</th>
<th>Object 2</th>
</tr>
</thead>
<tbody>
{composeTableObj1}
</tbody>
</table>
";
return body;
}
}
The above code gives me the following undesired result:
| Object 1 | Object 2 |
|----------|----------|
| test | test2 |
| test3 | test4 |
This is the desired result I'm after:
| Object 1 | Object 2 |
|----------|----------|
| test | test3 |
| test2 | test4 |
Been stuck on this for quite some time, TIA!
Looks like your HTML is a bit off - writing HTML like this can be tedious. I am guessing based off of the hard coded table headers in the HTML and the structure of the Submitter class that you will only ever have two Objn items in your JSON. If this is true, you can ditch the foreach loop for a for loop, and get the value out of data.Submitter[n] using the loops iterator:
public string Post([FromBody] MyData data)
{
string composeTableObj = "";
for (int i = 0; i < data.Submitter.Count(); i++)
{
composeTableObj += $"<tr>"; //start row tag
composeTableObj += $"<td>{data.Submitter[0].Obj1[i]}</td>";
composeTableObj += $"<td>{data.Submitter[1].Obj2[i]}</td>";
composeTableObj += $"</tr>"; //end row tag
}
return #$"<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody>{composeTableObj}</tbody></table>";
}
Rendered:
Returned HTML:
<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody><tr><td>test</td><td>test3</td></tr><tr><td>test2</td><td>test4</td></tr></tbody></table>
Obviously, this isn't very dynamic. If you find you need more Objn, you'll need to update the Submitter class, and you will have to add to the code within the loop (and add another header for your HTML).
If you have more items in the Obj arrays, you can do the following:
public string Post([FromBody] MyData data)
{
string composeTableObj = "";
int obj1Count = data.Submitter[0].Obj1.Count;
int obj2Count = data.Submitter[1].Obj2.Count;
int loopCount = obj1Count >= obj2Count ? obj1Count : obj2Count;
for (int i = 0; i < loopCount; i++)
{
string obj1String = obj1Count <= i ? " " : data.Submitter[0].Obj1[i];
string obj2String = obj2Count <= i ? " " : data.Submitter[1].Obj2[i];
composeTableObj += $"<tr>"; //start row tag
composeTableObj += $"<td>{obj1String}</td>";
composeTableObj += $"<td>{obj2String}</td>";
composeTableObj += $"</tr>"; //end row tag
}
return #$"<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody>{composeTableObj}</tbody></table>";
}
This first gets the greater of the two .Counts between your Obj lists to set the loops condition, and then performs an index bounds check. If the objnCount is less than or equal to i, set it to a space, otherwise take the value for its respective Obj list. With 6 items in Obj2 and only 2 items in Obj1, the HTML looked like this:
Returned HTML:
<table><thead><tr><th>Object 1</th><th>Object 2</th></tr></thead><tbody><tr><td>test</td><td>test3</td></tr><tr><td>test2</td><td>test4</td></tr><tr><td> </td><td>test5</td></tr><tr><td> </td><td>test6</td></tr><tr><td> </td><td>test7</td></tr><tr><td> </td><td>test8</td></tr></tbody></table>
I need to take data that I am reading in from a WIKI markup page and store it as a table structure. I am trying to figure out how to properly parse the below markup syntax into some table data structure in C#
Here is an example table:
|| Owner || Action || Status || Comments ||
| Bill | Fix the lobby | In Progress | This is easy |
| Joe | Fix the bathroom | In Progress | Plumbing \\
\\
Electric \\
\\
Painting \\
\\
\\ |
| Scott | Fix the roof | Complete | This is expensive |
and here is how it comes in directly:
|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|
So as you can see:
The column headers have "||" as the separator
A row columns have a separator or "|"
A row might span multiple lines (as in the second data row example above) so i would have to keep reading until I hit the same number of "|" (cols) that I have in the header row.
I tried reading in line by line and then concatenating lines that had "\" in between then but that seemed a bit hacky.
I also tried to simply read in as a full string and then just parse by "||" first and then keep reading until I hit the same number of "|" and then go to the next row. This seemed to work but it feel like there might be a more elegant way using regular expressions or something similar.
Can anyone suggest the correct way to parse this data?
I have largely replaced the previous answer, due to the fact that the format of the input after your edit is substantially different from the one posted before. This leads to a somewhat different solution.
Because there are no longer any line breaks after a row, the only way to determine for sure where a row ends, is to require that each row has the same number of columns as the table header. That is at least if you don't want to rely on some potentially fragile white space convention present in the one and only provided example string (i.e. that the row separator is the only | not preceded by a space). Your question at least does not provide this as the specification for a row delimiter.
The below "parser" provides at least the error handling validity checks that can be derived from your format specification and example string and also allows for tables that have no rows. The comments explain what it is doing in basic steps.
public class TableParser
{
const StringSplitOptions SplitOpts = StringSplitOptions.None;
const string RowColSep = "|";
static readonly string[] HeaderColSplit = { "||" };
static readonly string[] RowColSplit = { RowColSep };
static readonly string[] MLColSplit = { #"\\" };
public class TableRow
{
public List<string[]> Cells;
}
public class Table
{
public string[] Header;
public TableRow[] Rows;
}
public static Table Parse(string text)
{
// Isolate the header columns and rows remainder.
var headerSplit = text.Split(HeaderColSplit, SplitOpts);
Ensure(headerSplit.Length > 1, "At least 1 header column is required in the input");
// Need to check whether there are any rows.
var hasRows = headerSplit.Last().IndexOf(RowColSep) >= 0;
var header = headerSplit.Skip(1)
.Take(headerSplit.Length - (hasRows ? 2 : 1))
.Select(c => c.Trim())
.ToArray();
if (!hasRows) // If no rows for this table, we are done.
return new Table() { Header = header, Rows = new TableRow[0] };
// Get all row columns from the remainder.
var rowsCols = headerSplit.Last().Split(RowColSplit, SplitOpts);
// Require same amount of columns for a row as the header.
Ensure((rowsCols.Length % (header.Length + 1)) == 1,
"The number of row colums does not match the number of header columns");
var rows = new TableRow[(rowsCols.Length - 1) / (header.Length + 1)];
// Fill rows by sequentially taking # header column cells
for (int ri = 0, start = 1; ri < rows.Length; ri++, start += header.Length + 1)
{
rows[ri] = new TableRow() {
Cells = rowsCols.Skip(start).Take(header.Length)
.Select(c => c.Split(MLColSplit, SplitOpts).Select(p => p.Trim()).ToArray())
.ToList()
};
};
return new Table { Header = header, Rows = rows };
}
private static void Ensure(bool check, string errorMsg)
{
if (!check)
throw new InvalidDataException(errorMsg);
}
}
When used like this:
public static void Main(params string[] args)
{
var wikiLine = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
var table = TableParser.Parse(wikiLine);
Console.WriteLine(string.Join(", ", table.Header));
foreach (var r in table.Rows)
Console.WriteLine(string.Join(", ", r.Cells.Select(c => string.Join(Environment.NewLine + "\t# ", c))));
}
It will produce the below output:
Where "\t# " represents a newline caused by the presence of \\ in the input.
Here's a solution which populates a DataTable. It does require a litte bit of data massaging (Trim), but the main parsing is Splits and Linq.
var str = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
var headerStop = str.LastIndexOf("||");
var headers = str.Substring(0, headerStop).Split(new string[1] { "||" }, StringSplitOptions.None).Skip(1).ToList();
var records = str.Substring(headerStop + 4).TrimEnd(new char[2] { ' ', '|' }).Split(new string[1] { "| |" }, StringSplitOptions.None).ToList();
var tbl = new DataTable();
headers.ForEach(h => tbl.Columns.Add(h.Trim()));
records.ForEach(r => tbl.Rows.Add(r.Split('|')));
This makes some assumptions but seems to work for your sample data. I'm sure if I worked at I could combine the expressions and clean it up but you'll get the idea.
It will also allow for rows that do not have the same number of cells as the header which I think is something confluence can do.
List<List<string>> table = new List<List<string>>();
var match = Regex.Match(raw, #"(?:(?:\|\|([^|]*))*\n)?");
if (match.Success)
{
var headersWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);
List<String> headerRow = headersWithExtra.Take(headersWithExtra.Count()-1).ToList();
if (headerRow.Count > 0)
{
table.Add(headerRow);
}
}
match = Regex.Match(raw + "\r\n", #"[^\n]*\n" + #"(?:\|([^|]*))*");
var cellsWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);
List<string> row = new List<string>();
foreach (string cell in cellsWithExtra)
{
if (cell.Trim(' ', '\t') == "\r\n")
{
if (!table.Contains(row) && row.Count > 0)
{
table.Add(row);
}
row = new List<string>();
}
else
{
row.Add(cell);
}
}
This ended up very similar to Jon Tirjan's answer, although it cuts the LINQ to a single statement (the code to replace that last one was horrifically ugly) and is a bit more extensible. For example, it will replace the Confluence line breaks \\ with a string of your choosing, you can choose to trim or not trim whitespace from around elements, etc.
private void ParseWikiTable(string input, string newLineReplacement = " ")
{
string separatorHeader = "||";
string separatorRow = "| |";
string separatorElement = "|";
input = Regex.Replace(input, #"[ \\]{2,}", newLineReplacement);
string inputHeader = input.Substring(0, input.LastIndexOf(separatorHeader));
string inputContent = input.Substring(input.LastIndexOf(separatorHeader) + separatorHeader.Length);
string[] headerArray = SimpleSplit(inputHeader, separatorHeader);
string[][] rowArray = SimpleSplit(inputContent, separatorRow).Select(r => SimpleSplit(r, separatorElement)).ToArray();
// do something with output data
TestPrint(headerArray);
foreach (var r in rowArray) { TestPrint(r); }
}
private string[] SimpleSplit(string input, string separator, bool trimWhitespace = true)
{
input = input.Trim();
if (input.StartsWith(separator)) { input = input.Substring(separator.Length); }
if (input.EndsWith(separator)) { input = input.Substring(0, input.Length - separator.Length); }
string[] segments = input.Split(new string[] { separator }, StringSplitOptions.None);
if (trimWhitespace)
{
for (int i = 0; i < segments.Length; i++)
{
segments[i] = segments[i].Trim();
}
}
return segments;
}
private void TestPrint(string[] lst)
{
string joined = "[" + String.Join("::", lst) + "]";
Console.WriteLine(joined);
}
Console output from your direct input string:
[Owner::Action::Status::Comments]
[Bill::fix the lobby::In Progress::This is eary]
[Joe::fix the bathroom::In progress::plumbing Electric Painting]
[Scott::fix the roof::Complete::this is expensive]
A generic regex solution that populate a datatable and is a little flexible with the syntax.
var text = #"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
// Get Headers
var regHeaders = new Regex(#"\|\|\s*(\w[^\|]+)", RegexOptions.Compiled);
var headers = regHeaders.Matches(text);
//Get Rows, based on number of headers columns
var regLinhas = new Regex(String.Format(#"(?:\|\s*(\w[^\|]+)){{{0}}}", headers.Count));
var rows = regLinhas.Matches(text);
var tbl = new DataTable();
foreach (Match header in headers)
{
tbl.Columns.Add(header.Groups[1].Value);
}
foreach (Match row in rows)
{
tbl.Rows.Add(row.Groups[1].Captures.OfType<Capture>().Select(col => col.Value).ToArray());
}
Here's a solution involving regular expressions. It takes a single string as input and returns a List of headers and a List> of rows/columns. It also trims white space, which may or may not be the desired behavior, so be aware of that. It even prints things nicely :)
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace parseWiki
{
class Program
{
static void Main(string[] args)
{
string content = #"|| Owner || Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
content = content.Replace(#"\\", "");
string headerContent = content.Substring(0, content.LastIndexOf("||") + 2);
string cellContent = content.Substring(content.LastIndexOf("||") + 2);
MatchCollection headerMatches = new Regex(#"\|\|([^|]*)(?=\|\|)", RegexOptions.Singleline).Matches(headerContent);
MatchCollection cellMatches = new Regex(#"\|([^|]*)(?=\|)", RegexOptions.Singleline).Matches(cellContent);
List<string> headers = new List<string>();
foreach (Match match in headerMatches)
{
if (match.Groups.Count > 1)
{
headers.Add(match.Groups[1].Value.Trim());
}
}
List<List<string>> body = new List<List<string>>();
List<string> newRow = new List<string>();
foreach (Match match in cellMatches)
{
if (newRow.Count > 0 && newRow.Count % headers.Count == 0)
{
body.Add(newRow);
newRow = new List<string>();
}
else
{
newRow.Add(match.Groups[1].Value.Trim());
}
}
body.Add(newRow);
print(headers, body);
}
static void print(List<string> headers, List<List<string>> body)
{
var CELL_SIZE = 20;
for (int i = 0; i < headers.Count; i++)
{
Console.Write(headers[i].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + " ");
}
Console.WriteLine("\n" + "".PadRight( (CELL_SIZE + 2) * headers.Count, '-'));
for (int r = 0; r < body.Count; r++)
{
List<string> row = body[r];
for (int c = 0; c < row.Count; c++)
{
Console.Write(row[c].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + " ");
}
Console.WriteLine("");
}
Console.WriteLine("\n\n\n");
Console.ReadKey(false);
}
}
public static class StringExt
{
public static string Truncate(this string value, int maxLength)
{
if (string.IsNullOrEmpty(value) || value.Length <= maxLength) return value;
return value.Substring(0, maxLength - 3) + "...";
}
}
}
Read the input string one character at a time and use a state-machine to decide what should be done with each input character. This approach probably needs more code, but it will be easier to maintain and to extend than regular expressions.
I want to compare 2 fields. i.e Machine and Pass.
I want to concatenate 'Color' Depending upon the value of Machine and pass.
--------------|------------------|------------
Color1 | Machine 1 | Pass 1
--------------|------------------|------------
Color2 | Machine 2 | Pass 1
--------------|------------------|------------
Color3 | Machine 1 | Pass 1
--------------|------------------|------------
Color4 | Machine 1 | Pass 2
--------------|------------------|------------
Color5 | Machine 2 | Pass 1
--------------|------------------|------------
Color6 | Machine 2 | Pass 2
--------------|------------------|------------
I want results as follows.
Color1/Color3 for Machine1 & Pass1
Color2/Color5 for Machine2 & Pass2
Color4 for Machine1 & Pass2
Color6 for Machine2 & Pass2
I have no clue how to get this results. tried many ways but not getting it the way I want it
First, what you really have here, conceptually, is a single dimension of a complex object that has both a color, machine, and pass value. Storing that in a 2 dimensional array isn't a good idea. A better representation is a List of some custom type, so let's do that conversion first.
First, we'll define a custom type to represent the input data:
public class MachinePass//TODO consider renaming
{
public string Color { get; set; }
public string Machine { get; set; }
public string Pass { get; set; }
}
then we'll parse our input and put it into the appropriate structure:
var list = new List<MachinePass>();
for (int i = 0; i < data.GetLength(0); i++)
{
var next = new MachinePass();
next.Color = data[i, 0];
next.Machine = data[i, 1];
next.Pass = data[i, 2];
list.Add(next);
}
Now that we have this, we can apply our business logic. In this case what you're wanting to do is group these rows by both machine and pass, and then get the colors for those groups. The GroupBy LINQ operator makes this super easy:
var query = list.GroupBy(row => new { row.Machine, row.Pass }
, row => row.Color);
Then we can just print out the results of this query in the defined format:
foreach(var group in query)
Console.WriteLine("{0} for {1} & {2}",
string.Join("/", group),
group.Key.Machine,
group.Key.Pass);
This is your answer,
private void button1_Click(object sender, EventArgs e)
{
var ary = new[]
{
"Color1 | Machine 1 | Pass 1 ",
"Color2 | Machine 2 | Pass 1 ",
"Color3 | Machine 1 | Pass 1 ",
"Color4 | Machine 1 | Pass 2 ",
"Color5 | Machine 2 | Pass 1 ",
"Color6 | Machine 2 | Pass 2 "
};
var seprated = from x in ary.Select(x => x.Split('|'))
select new
{
key = x[1].Trim() + "&" + x[2].Trim(),
value = x[0]
};
var sb = new StringBuilder();
foreach (var key in seprated.Select(x => x.key).Distinct())
{
var colors = seprated.Where(x => x.key == key).Select(x => x.value.Trim()).ToArray();
sb.AppendLine(string.Format("{0} for {1}", string.Join("/", colors), key));
}
textBox1.Text = sb.ToString();
}
sb.toString() has result:
Color1/Color3 for Machine 1&Pass 1
Color2/Color5 for Machine 2&Pass 1
Color4 for Machine 1&Pass 2
Color6 for Machine 2&Pass 2
You did not provide a class definition so I am going to assume it is the following
class PaintMachineInfo
{
public string ColorName {get; set;}
public string MachineName {get; set;}
public string Pass {get; set;}
}
You can easily get the results you want by using .ToLookup(, what it will allow you to do is to provide a key and it will give you a IEnumerable of results that match that key.
I like to use a custom class for the lookup key instead of a Tuple because it makes it much more obvious what you are looking at and it is not too much code to create the class.
private static void Main(string[] args)
{
List<PaintMachineInfo> info = GenerateInfo();
var filteredResults = info.ToLookup(line => new PaintMachineLookup(line.MachineName, line.Pass), line => line.ColorName);
//Contains a IEnumerable<string> containing the elements "Color1" and "Color3"
var result1 = filteredResults[new PaintMachineLookup("Machine 1", "Pass 1")];
}
private static List<PaintMachineInfo> GenerateInfo()
{
//...
}
class PaintMachineInfo
{
public string ColorName { get; set; }
public string MachineName { get; set; }
public string Pass { get; set; }
}
internal class PaintMachineLookup
{
public PaintMachineLookup(string machineName, string pass)
{
MachineName = machineName;
Pass = pass;
}
public string MachineName { get; private set; }
public string Pass { get; private set; }
public override int GetHashCode()
{
unchecked
{
int x = 27;
x = x * 11 + MachineName.GetHashCode();
x = x * 11 + Pass.GetHashCode();
return x;
}
}
public override bool Equals(object obj)
{
var other = obj as PaintMachineLookup;
if (other == null)
return false;
return MachineName.Equals(other.MachineName) && Pass.Equals(other.Pass);
}
}
var multidimensionalArray = new[,]
{
{"Color1", "Machine 1", "Pass 1"},
{"Color2", "Machine 2", "Pass 1"},
{"Color3", "Machine 1", "Pass 1"},
{"Color4", "Machine 1", "Pass 2"},
{"Color5", "Machine 2", "Pass 1"},
{"Color6", "Machine 2", "Pass 2"}
};
var tuple = new List<Tuple<string, string, string>>();
for (var i = 0; i < multidimensionalArray.Length/3-1; i++)
{
tuple.Add(new Tuple<string, string, string>(multidimensionalArray[i, 0], multidimensionalArray[i, 1], multidimensionalArray[i, 2]));
}
foreach (var el in tuple.GroupBy(x => String.Format("{0} & {1}", x.Item2, x.Item3), y => y.Item1))
{
Console.WriteLine(String.Join("/", el) + " for " + el.Key);
}
I have a [textual] tree like this:
+---step-1
| +---step_2
| | +---step3
| | \---step4
| +---step_2.1
| \---step_2.2
+---step1.2
Tree2
+---step-1
| \---step_2
| | +---step3
| | \---step4
+---step1.2
This is just a small example, tree can be deeper and with more children and etc..
Right now I'm doing this:
for (int i = 0; i < cmdOutList.Count; i++)
{
string s = cmdOutList[i];
String value = Regex.Match(s, #"(?<=\---).*").Value;
value = value.Replace("\r", "");
if (s[1].ToString() == "-")
{
DirectoryNode p = new DirectoryNode { Name = value };
//p.AddChild(f);
directoryList.Add(p);
}
else
{
DirectoryNode f = new DirectoryNode { Name = value };
directoryList[i - 1].AddChild(f);
directoryList.Add(f);
}
}
But this doesn't handle the "step_2.1" and "step_2.2"
I think I'm doing this totally wrong, maybe someone can help me out with this.
EDIT:
Here is the DirectoryNode class to make that a bit more clear..
public class DirectoryNode
{
public DirectoryNode()
{
this.Children = new List<DirectoryNode>();
}
public DirectoryNode ParentObject { get; set; }
public string Name;
public List<DirectoryNode> Children { get; set; }
public void AddChild(DirectoryNode child)
{
child.ParentObject = this;
this.Children.Add(child);
}
}
If your text is that simple (just either +--- or \--- preceded by a series of |), then a regex might be more than you need (and what's tripping you up).
DirectoryNode currentParent = null;
DirectoryNode current = null;
int lastStartIndex = 0;
foreach(string temp in cmdOutList)
{
string line = temp;
int startIndex = Math.Max(line.IndexOf("+"), line.IndexOf(#"\");
line = line.Substring(startIndex);
if(startIndex > lastStartIndex)
{
currentParent = current;
}
else if(startIndex < lastStartIndex)
{
for(int i = 0; i < (lastStartIndex - startIndex) / 4; i++)
{
if(currentParent == null) break;
currentParent = currentParent.ParentObject;
}
}
lastStartIndex = startIndex;
current = new DirectoryNode() { Name = line.Substring(4) };
if(currentParent != null)
{
currentParent.AddChild(current);
}
else
{
directoryList.Add(current);
}
}
Regex definitely looks unnecessary here, since the symbols in your markup language (that's what it is, after all) are both static and few. That is: Although the label names may vary, the tokens you need to look for when trying to parse them into relevant pieces will never be anything other than +---, \---, and ..
From a question I answered yesterday: "Regexes are extremely useful for describing a whole class of needles in a largely unknown haystack, but they're not the right tool for input that's in a very static format."
String manipulation is what you want for parsing this, especially since you're dealing with a recursive markup language, which can't be fully understood by regex anyway. I'd also suggest creating a tree-type data structure to store the data (which, surprisingly, doesn't seem to be included in the framework unless they added it after 2.0).
As an aside, your regex above seems to have an unnecessary \ in it, but that doesn't matter in most regex flavors.