Getting a loop to build a string correctly - c#

I'm trying to pull the tournament age from this webpage:
http://www.reddishvulcans.com/uk_tournament_database.asp
I'm trying to create a string based on the valid ages for entry for each table.
For example, if the "Carling Cup" is enterable by 7 year olds, then a string would be generated like "U7", or if it's enterable by 7, 8, and 9 year olds, the resulting string will be "U7, U8, U9".
I've made a start, however my logic will break if the ages go like this "Under 7s, Gap here where no under 8s, Under 9s".
Here is my code:
public static List<Record> getRecords()
{
string url = "http://www.reddishvulcans.com/uk_tournament_database.asp";
var Webget = new HtmlWeb();
var doc = Webget.Load(url);
var root = doc.DocumentNode;
var ages = root.SelectNodes("//div[#class='infobox']/table/tr[5]/td/img");
List<String> tournamentAges = new List<String>();
String ageGroups = "";
List<String> ageString = new List<String>();
for (Int32 i = 0; i < ages.Count(); i++)
{
if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u6_Yes.gif")
{
if (!ageString.Contains(" U6 ")) {
ageString.Add(" U6 ");
continue;
}
}
else if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u6_.gif")
{
continue;
}
if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u7_Yes.gif")
{
if (!ageString.Contains(" U7 "))
{
ageString.Add(" U7 ");
continue;
}
}
else if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u7_.gif")
{
continue;
}
if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u8_Yes.gif")
{
if (!ageString.Contains(" U8 "))
{
ageString.Add(" U8 ");
continue;
}
}
else if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u8_.gif")
{
continue;
}
// Checks until u16.gif
foreach (String a in ageString)
{
if (a != "")
{
ageGroups += a;
}
}
ageString.Clear();
if (ageGroups != "")
{
tournamentAges.Add(ageGroups);
}
ageGroups = "";
}
}
}
To be clear, I'm having trouble with the loop logic.
The flow currently goes like this:
Loop through current list of images
If > u6_Yes.gif
Concatenate u6 to ageString
else
Continue
However it will continue back to the start and get stuck in an infinite loop, How can I make it gracefully handle when u6_.gif is gone, ignore it and go to the next?

why don't you just simplify your loop like this?
if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u6_Yes.gif")
{
if (!ageString.Contains(" U6 "))
{
ageString.Add(" U6 ");
continue;
}
}
if (ages[i].GetAttributeValue("src", "nope") == "images/2016/u7_Yes.gif")
{
if (!ageString.Contains(" U7 "))
{
ageString.Add(" U7 ");
continue;
}
}
}
(...)
just remove all those else if blocks...
Also, you should consider extraction src attribute from ages array. You can use Linq and make your loop a lot simplier. Something like this:
List<String> ageString = new List<String>();
List<string> imageSources = ages.Where(x => x.GetAttributeValue("src", "nope").StartsWith("images/2016/u") && x.GetAttributeValue("src", "nope").EndsWith("_Yes.gif")).ToList();
foreach (var src in imageSources)
{
ageString.Add(" " + src.Substring(11, 2).ToUpper() + " ");
}

Related

Split a string if delimiter is between single quotes [duplicate]

This question already has answers here:
How to split csv whose columns may contain comma
(9 answers)
Closed 4 years ago.
I have the following comma-separated string that I need to split. The problem is that some of the content is within quotes and contains commas that shouldn't be used in the split.
String:
111,222,"33,44,55",666,"77,88","99"
I want the output:
111
222
33,44,55
666
77,88
99
I have tried this:
(?:,?)((?<=")[^"]+(?=")|[^",]+)
But it reads the comma between "77,88","99" as a hit and I get the following output:
111
222
33,44,55
666
77,88
,
99
Depending on your needs you may not be able to use a csv parser, and may in fact want to re-invent the wheel!!
You can do so with some simple regex
(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)
This will do the following:
(?:^|,) = Match expression "Beginning of line or string ,"
(\"(?:[^\"]+|\"\")*\"|[^,]*) = A numbered capture group, this will select between 2 alternatives:
stuff in quotes
stuff between commas
This should give you the output you are looking for.
Example code in C#
static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);
public static string[] SplitCSV(string input)
{
List<string> list = new List<string>();
string curr = null;
foreach (Match match in csvSplit.Matches(input))
{
curr = match.Value;
if (0 == curr.Length)
{
list.Add("");
}
list.Add(curr.TrimStart(','));
}
return list.ToArray();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine(SplitCSV("111,222,\"33,44,55\",666,\"77,88\",\"99\""));
}
Warning As per #MrE's comment - if a rogue new line character appears in a badly formed csv file and you end up with an uneven ("string) you'll get catastrophic backtracking (https://www.regular-expressions.info/catastrophic.html) in your regex and your system will likely crash (like our production system did). Can easily be replicated in Visual Studio and as I've discovered will crash it. A simple try/catch will not trap this issue either.
You should use:
(?:^|,)(\"(?:[^\"])*\"|[^,]*)
instead
Fast and easy:
public static string[] SplitCsv(string line)
{
List<string> result = new List<string>();
StringBuilder currentStr = new StringBuilder("");
bool inQuotes = false;
for (int i = 0; i < line.Length; i++) // For each character
{
if (line[i] == '\"') // Quotes are closing or opening
inQuotes = !inQuotes;
else if (line[i] == ',') // Comma
{
if (!inQuotes) // If not in quotes, end of current string, add it to result
{
result.Add(currentStr.ToString());
currentStr.Clear();
}
else
currentStr.Append(line[i]); // If in quotes, just add it
}
else // Add any other character to current string
currentStr.Append(line[i]);
}
result.Add(currentStr.ToString());
return result.ToArray(); // Return array of all strings
}
With this string as input :
111,222,"33,44,55",666,"77,88","99"
It will return :
111
222
33,44,55
666
77,88
99
i really like jimplode's answer, but I think a version with yield return is a little bit more useful, so here it is:
public IEnumerable<string> SplitCSV(string input)
{
Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);
foreach (Match match in csvSplit.Matches(input))
{
yield return match.Value.TrimStart(',');
}
}
Maybe it's even more useful to have it like an extension method:
public static class StringHelper
{
public static IEnumerable<string> SplitCSV(this string input)
{
Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);
foreach (Match match in csvSplit.Matches(input))
{
yield return match.Value.TrimStart(',');
}
}
}
This regular expression works without the need to loop through values and TrimStart(','), like in the accepted answer:
((?<=\")[^\"]*(?=\"(,|$)+)|(?<=,|^)[^,\"]*(?=,|$))
Here is the implementation in C#:
string values = "111,222,\"33,44,55\",666,\"77,88\",\"99\"";
MatchCollection matches = new Regex("((?<=\")[^\"]*(?=\"(,|$)+)|(?<=,|^)[^,\"]*(?=,|$))").Matches(values);
foreach (var match in matches)
{
Console.WriteLine(match);
}
Outputs
111
222
33,44,55
666
77,88
99
None of these answers work when the string has a comma inside quotes, as in "value, 1", or escaped double-quotes, as in "value ""1""", which are valid CSV that should be parsed as value, 1 and value "1", respectively.
This will also work with the tab-delimited format if you pass in a tab instead of a comma as your delimiter.
public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
var currentString = new StringBuilder();
var inQuotes = false;
var quoteIsEscaped = false; //Store when a quote has been escaped.
row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
foreach (var character in row.Select((val, index) => new {val, index}))
{
if (character.val == delimiter) //We hit a delimiter character...
{
if (!inQuotes) //Are we inside quotes? If not, we've hit the end of a cell value.
{
Console.WriteLine(currentString);
yield return currentString.ToString();
currentString.Clear();
}
else
{
currentString.Append(character.val);
}
} else {
if (character.val != ' ')
{
if(character.val == '"') //If we've hit a quote character...
{
if(character.val == '\"' && inQuotes) //Does it appear to be a closing quote?
{
if (row[character.index + 1] == character.val) //If the character afterwards is also a quote, this is to escape that (not a closing quote).
{
quoteIsEscaped = true; //Flag that we are escaped for the next character. Don't add the escaping quote.
}
else if (quoteIsEscaped)
{
quoteIsEscaped = false; //This is an escaped quote. Add it and revert quoteIsEscaped to false.
currentString.Append(character.val);
}
else
{
inQuotes = false;
}
}
else
{
if (!inQuotes)
{
inQuotes = true;
}
else
{
currentString.Append(character.val); //...It's a quote inside a quote.
}
}
}
else
{
currentString.Append(character.val);
}
}
else
{
if (!string.IsNullOrWhiteSpace(currentString.ToString())) //Append only if not new cell
{
currentString.Append(character.val);
}
}
}
}
}
With minor updates to the function provided by "Chad Hedgcock".
Updates are on:
Line 26: character.val == '\"' - This can never be true due to the check made on Line 24. i.e. character.val == '"'
Line 28: if (row[character.index + 1] == character.val) added !quoteIsEscaped to escape 3 consecutive quotes.
public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
var currentString = new StringBuilder();
var inQuotes = false;
var quoteIsEscaped = false; //Store when a quote has been escaped.
row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
foreach (var character in row.Select((val, index) => new {val, index}))
{
if (character.val == delimiter) //We hit a delimiter character...
{
if (!inQuotes) //Are we inside quotes? If not, we've hit the end of a cell value.
{
//Console.WriteLine(currentString);
yield return currentString.ToString();
currentString.Clear();
}
else
{
currentString.Append(character.val);
}
} else {
if (character.val != ' ')
{
if(character.val == '"') //If we've hit a quote character...
{
if(character.val == '"' && inQuotes) //Does it appear to be a closing quote?
{
if (row[character.index + 1] == character.val && !quoteIsEscaped) //If the character afterwards is also a quote, this is to escape that (not a closing quote).
{
quoteIsEscaped = true; //Flag that we are escaped for the next character. Don't add the escaping quote.
}
else if (quoteIsEscaped)
{
quoteIsEscaped = false; //This is an escaped quote. Add it and revert quoteIsEscaped to false.
currentString.Append(character.val);
}
else
{
inQuotes = false;
}
}
else
{
if (!inQuotes)
{
inQuotes = true;
}
else
{
currentString.Append(character.val); //...It's a quote inside a quote.
}
}
}
else
{
currentString.Append(character.val);
}
}
else
{
if (!string.IsNullOrWhiteSpace(currentString.ToString())) //Append only if not new cell
{
currentString.Append(character.val);
}
}
}
}
}
For Jay's answer, if you use a 2nd boolean then you can have nested double-quotes inside single-quotes and vice-versa.
private string[] splitString(string stringToSplit)
{
char[] characters = stringToSplit.ToCharArray();
List<string> returnValueList = new List<string>();
string tempString = "";
bool blockUntilEndQuote = false;
bool blockUntilEndQuote2 = false;
int characterCount = 0;
foreach (char character in characters)
{
characterCount = characterCount + 1;
if (character == '"' && !blockUntilEndQuote2)
{
if (blockUntilEndQuote == false)
{
blockUntilEndQuote = true;
}
else if (blockUntilEndQuote == true)
{
blockUntilEndQuote = false;
}
}
if (character == '\'' && !blockUntilEndQuote)
{
if (blockUntilEndQuote2 == false)
{
blockUntilEndQuote2 = true;
}
else if (blockUntilEndQuote2 == true)
{
blockUntilEndQuote2 = false;
}
}
if (character != ',')
{
tempString = tempString + character;
}
else if (character == ',' && (blockUntilEndQuote == true || blockUntilEndQuote2 == true))
{
tempString = tempString + character;
}
else
{
returnValueList.Add(tempString);
tempString = "";
}
if (characterCount == characters.Length)
{
returnValueList.Add(tempString);
tempString = "";
}
}
string[] returnValue = returnValueList.ToArray();
return returnValue;
}
The original version
Currently I use the following regex:
public static Regex regexCSVSplit = new Regex(#"(?x:(
(?<FULL>
(^|[,;\t\r\n])\s*
( (?<QUODAT> (?<QUO>[""'])(?<DAT>([^,;\t\r\n]|(?<!\k<QUO>\s*)[,;\t\r\n])*)\k<QUO>) |
(?<QUODAT> (?<DAT> [^""',;\s\r\n]* )) )
(?=\s*([,;\t\r\n]|$))
) |
(?<FULL>
(^|[\s\t\r\n])
( (?<QUODAT> (?<QUO>[""'])(?<DAT> [^""',;\s\t\r\n]* )\k<QUO>) |
(?<QUODAT> (?<DAT> [^""',;\s\t\r\n]* )) )
(?=[,;\s\t\r\n]|$)
)
))", RegexOptions.Compiled);
This solution can handle pretty chaotic cases too like below:
This is how to feed the result into an array:
var data = regexCSVSplit.Matches(line_to_process).Cast<Match>().
Select(x => x.Groups["DAT"].Value).ToArray();
See this example in action HERE
Note: The regular expression contains two set of <FULL> block and each of them contains two <QUODAT> block separated by "or" (|). Depending on your task you may only need one of them.
Note: That this regular expression gives us one string array, and works on single line with or without <carrier return> and/or <line feed>.
Simplified version
The following regular expression will already cover many complex cases:
public static Regex regexCSVSplit = new Regex(#"(?x:(
(?<FULL>
(^|[,;\t\r\n])\s*
(?<QUODAT> (?<QUO>[""'])(?<DAT>([^,;\t\r\n]|(?<!\k<QUO>\s*)[,;\t\r\n])*)\k<QUO>)
(?=\s*([,;\t\r\n]|$))
)
))", RegexOptions.Compiled);
See this example in action: HERE
It can process complex, easy and empty items too:
This is how to feed the result into an array:
var data = regexCSVSplit.Matches(line_to_process).Cast<Match>().
Select(x => x.Groups["DAT"].Value).ToArray();
The main rule here is that every item may contain anything but the <quotation mark><separators><comma> sequence AND each item shall being and end with the same <quotation mark>.
<quotation mark>: <">, <'>
<comma>: <,>, <;>, <tab>, <carrier return>, <line feed>
Edit notes: I added some more explanation to make it easier to understand and replaces the text "CO" with "QUO".
Try this:
string s = #"111,222,""33,44,55"",666,""77,88"",""99""";
List<string> result = new List<string>();
var splitted = s.Split('"').ToList<string>();
splitted.RemoveAll(x => x == ",");
foreach (var it in splitted)
{
if (it.StartsWith(",") || it.EndsWith(","))
{
var tmp = it.TrimEnd(',').TrimStart(',');
result.AddRange(tmp.Split(','));
}
else
{
if(!string.IsNullOrEmpty(it)) result.Add(it);
}
}
//Results:
foreach (var it in result)
{
Console.WriteLine(it);
}
I know I'm a bit late to this, but for searches, here is how I did what you are asking about in C sharp
private string[] splitString(string stringToSplit)
{
char[] characters = stringToSplit.ToCharArray();
List<string> returnValueList = new List<string>();
string tempString = "";
bool blockUntilEndQuote = false;
int characterCount = 0;
foreach (char character in characters)
{
characterCount = characterCount + 1;
if (character == '"')
{
if (blockUntilEndQuote == false)
{
blockUntilEndQuote = true;
}
else if (blockUntilEndQuote == true)
{
blockUntilEndQuote = false;
}
}
if (character != ',')
{
tempString = tempString + character;
}
else if (character == ',' && blockUntilEndQuote == true)
{
tempString = tempString + character;
}
else
{
returnValueList.Add(tempString);
tempString = "";
}
if (characterCount == characters.Length)
{
returnValueList.Add(tempString);
tempString = "";
}
}
string[] returnValue = returnValueList.ToArray();
return returnValue;
}
Don't reinvent a CSV parser, try FileHelpers.
I needed something a little more robust, so I took from here and created this... This solution is a little less elegant and a little more verbose, but in my testing (with a 1,000,000 row sample), I found this to be 2 to 3 times faster. Plus it handles non-escaped, embedded quotes. I used string delimiter and qualifiers instead of chars because of the requirements of my solution. I found it more difficult than I expected to find a good, generic CSV parser so I hope this parsing algorithm can help someone.
public static string[] SplitRow(string record, string delimiter, string qualifier, bool trimData)
{
// In-Line for example, but I implemented as string extender in production code
Func <string, int, int> IndexOfNextNonWhiteSpaceChar = delegate (string source, int startIndex)
{
if (startIndex >= 0)
{
if (source != null)
{
for (int i = startIndex; i < source.Length; i++)
{
if (!char.IsWhiteSpace(source[i]))
{
return i;
}
}
}
}
return -1;
};
var results = new List<string>();
var result = new StringBuilder();
var inQualifier = false;
var inField = false;
// We add new columns at the delimiter, so append one for the parser.
var row = $"{record}{delimiter}";
for (var idx = 0; idx < row.Length; idx++)
{
// A delimiter character...
if (row[idx]== delimiter[0])
{
// Are we inside qualifier? If not, we've hit the end of a column value.
if (!inQualifier)
{
results.Add(trimData ? result.ToString().Trim() : result.ToString());
result.Clear();
inField = false;
}
else
{
result.Append(row[idx]);
}
}
// NOT a delimiter character...
else
{
// ...Not a space character
if (row[idx] != ' ')
{
// A qualifier character...
if (row[idx] == qualifier[0])
{
// Qualifier is closing qualifier...
if (inQualifier && row[IndexOfNextNonWhiteSpaceChar(row, idx + 1)] == delimiter[0])
{
inQualifier = false;
continue;
}
else
{
// ...Qualifier is opening qualifier
if (!inQualifier)
{
inQualifier = true;
}
// ...It's a qualifier inside a qualifier.
else
{
inField = true;
result.Append(row[idx]);
}
}
}
// Not a qualifier character...
else
{
result.Append(row[idx]);
inField = true;
}
}
// ...A space character
else
{
if (inQualifier || inField)
{
result.Append(row[idx]);
}
}
}
}
return results.ToArray<string>();
}
Some test code:
//var input = "111,222,\"33,44,55\",666,\"77,88\",\"99\"";
var input =
"111, 222, \"99\",\"33,44,55\" , \"666 \"mark of a man\"\", \" spaces \"77,88\" \"";
Console.WriteLine("Split with trim");
Console.WriteLine("---------------");
var result = SplitRow(input, ",", "\"", true);
foreach (var r in result)
{
Console.WriteLine(r);
}
Console.WriteLine("");
// Split 2
Console.WriteLine("Split with no trim");
Console.WriteLine("------------------");
var result2 = SplitRow(input, ",", "\"", false);
foreach (var r in result2)
{
Console.WriteLine(r);
}
Console.WriteLine("");
// Time Trial 1
Console.WriteLine("Experimental Process (1,000,000) iterations");
Console.WriteLine("-------------------------------------------");
watch = Stopwatch.StartNew();
for (var i = 0; i < 1000000; i++)
{
var x1 = SplitRow(input, ",", "\"", false);
}
watch.Stop();
elapsedMs = watch.ElapsedMilliseconds;
Console.WriteLine($"Total Process Time: {string.Format("{0:0.###}", elapsedMs / 1000.0)} Seconds");
Console.WriteLine("");
Results
Split with trim
---------------
111
222
99
33,44,55
666 "mark of a man"
spaces "77,88"
Split with no trim
------------------
111
222
99
33,44,55
666 "mark of a man"
spaces "77,88"
Original Process (1,000,000) iterations
-------------------------------
Total Process Time: 7.538 Seconds
Experimental Process (1,000,000) iterations
--------------------------------------------
Total Process Time: 3.363 Seconds
I once had to do something similar and in the end I got stuck with Regular Expressions. The inability for Regex to have state makes it pretty tricky - I just ended up writing a simple little parser.
If you're doing CSV parsing you should just stick to using a CSV parser - don't reinvent the wheel.
Here is my fastest implementation based upon string raw pointer manipulation:
string[] FastSplit(string sText, char? cSeparator = null, char? cQuotes = null)
{
string[] oTokens;
if (null == cSeparator)
{
cSeparator = DEFAULT_PARSEFIELDS_SEPARATOR;
}
if (null == cQuotes)
{
cQuotes = DEFAULT_PARSEFIELDS_QUOTE;
}
unsafe
{
fixed (char* lpText = sText)
{
#region Fast array estimatation
char* lpCurrent = lpText;
int nEstimatedSize = 0;
while (0 != *lpCurrent)
{
if (cSeparator == *lpCurrent)
{
nEstimatedSize++;
}
lpCurrent++;
}
nEstimatedSize++; // Add EOL char(s)
string[] oEstimatedTokens = new string[nEstimatedSize];
#endregion
#region Parsing
char[] oBuffer = new char[sText.Length];
int nIndex = 0;
int nTokens = 0;
lpCurrent = lpText;
while (0 != *lpCurrent)
{
if (cQuotes == *lpCurrent)
{
// Quotes parsing
lpCurrent++; // Skip quote
nIndex = 0; // Reset buffer
while (
(0 != *lpCurrent)
&& (cQuotes != *lpCurrent)
)
{
oBuffer[nIndex] = *lpCurrent; // Store char
lpCurrent++; // Move source cursor
nIndex++; // Move target cursor
}
}
else if (cSeparator == *lpCurrent)
{
// Separator char parsing
oEstimatedTokens[nTokens++] = new string(oBuffer, 0, nIndex); // Store token
nIndex = 0; // Skip separator and Reset buffer
}
else
{
// Content parsing
oBuffer[nIndex] = *lpCurrent; // Store char
nIndex++; // Move target cursor
}
lpCurrent++; // Move source cursor
}
// Recover pending buffer
if (nIndex > 0)
{
// Store token
oEstimatedTokens[nTokens++] = new string(oBuffer, 0, nIndex);
}
// Build final tokens list
if (nTokens == nEstimatedSize)
{
oTokens = oEstimatedTokens;
}
else
{
oTokens = new string[nTokens];
Array.Copy(oEstimatedTokens, 0, oTokens, 0, nTokens);
}
#endregion
}
}
// Epilogue
return oTokens;
}
Try this
private string[] GetCommaSeperatedWords(string sep, string line)
{
List<string> list = new List<string>();
StringBuilder word = new StringBuilder();
int doubleQuoteCount = 0;
for (int i = 0; i < line.Length; i++)
{
string chr = line[i].ToString();
if (chr == "\"")
{
if (doubleQuoteCount == 0)
doubleQuoteCount++;
else
doubleQuoteCount--;
continue;
}
if (chr == sep && doubleQuoteCount == 0)
{
list.Add(word.ToString());
word = new StringBuilder();
continue;
}
word.Append(chr);
}
list.Add(word.ToString());
return list.ToArray();
}
This is Chad's answer rewritten with state based logic. His answered failed for me when it came across """BRAD""" as a field. That should return "BRAD" but it just ate up all the remaining fields. When I tried to debug it I just ended up rewriting it as state based logic:
enum SplitState { s_begin, s_infield, s_inquotefield, s_foundquoteinfield };
public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
var currentString = new StringBuilder();
SplitState state = SplitState.s_begin;
row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
foreach (var character in row.Select((val, index) => new { val, index }))
{
//Console.WriteLine("character = " + character.val + " state = " + state);
switch (state)
{
case SplitState.s_begin:
if (character.val == delimiter)
{
/* empty field */
yield return currentString.ToString();
currentString.Clear();
} else if (character.val == '"')
{
state = SplitState.s_inquotefield;
} else
{
currentString.Append(character.val);
state = SplitState.s_infield;
}
break;
case SplitState.s_infield:
if (character.val == delimiter)
{
/* field with data */
yield return currentString.ToString();
state = SplitState.s_begin;
currentString.Clear();
} else
{
currentString.Append(character.val);
}
break;
case SplitState.s_inquotefield:
if (character.val == '"')
{
// could be end of field, or escaped quote.
state = SplitState.s_foundquoteinfield;
} else
{
currentString.Append(character.val);
}
break;
case SplitState.s_foundquoteinfield:
if (character.val == '"')
{
// found escaped quote.
currentString.Append(character.val);
state = SplitState.s_inquotefield;
}
else if (character.val == delimiter)
{
// must have been last quote so we must find delimiter
yield return currentString.ToString();
state = SplitState.s_begin;
currentString.Clear();
}
else
{
throw new Exception("Quoted field not terminated.");
}
break;
default:
throw new Exception("unknown state:" + state);
}
}
//Console.WriteLine("currentstring = " + currentString.ToString());
}
This is a lot more lines of code than the other solutions, but it is easy to modify to add edge cases.

Loop methodn not giving accurate resuts

I have created file during the runtime process John.txt and there are properties for John. The text of the file is "friend,banker" but when I execute and ask the AI "where is John"? it doesnt return any results despite I wrote the method MatchProffesionWithProperties. Where Am I wrong?
namespace Machine
{
class Program
{
static bool HavingGreeted = false;
static string person;
static Dictionary<string, string> KeyValuePairs;
static void MatchProffesionsWithProperties(string person,DayOfWeek dayOfWeek)
{
var strings = File.ReadAllLines(person + ".txt");
foreach(var stringi in strings)
{
var whitespacearray = stringi.Split(' ');
var commaarray = stringi.Split(',');
foreach(var property in whitespacearray)
{
if (KeyValuePairs.ContainsKey(property))
{
if (dayOfWeek == DayOfWeek.Monday || dayOfWeek == DayOfWeek.Tuesday || dayOfWeek == DayOfWeek.Wednesday || dayOfWeek == DayOfWeek.Thursday || dayOfWeek == DayOfWeek.Friday)
{
Console.WriteLine("{0} should be at the {1}", person, KeyValuePairs[property]);
}
}
}
foreach(var property in commaarray)
{
if (KeyValuePairs.ContainsKey(property))
{
if (dayOfWeek == DayOfWeek.Monday || dayOfWeek == DayOfWeek.Tuesday || dayOfWeek == DayOfWeek.Wednesday || dayOfWeek == DayOfWeek.Thursday || dayOfWeek == DayOfWeek.Friday)
{
Console.WriteLine("{0} should be at the {1}", person, KeyValuePairs[property]);
}
}
}
}
}
static void ProcessQuestionForPerson(string word)
{
var array = word.Split(' ');
if (word.Contains("Wh"))
{
person = array[2].Substring(0,array[2].Length-1);
}
else
{
person = array[1];
}
if (!File.Exists(person+".txt"))
{
Console.WriteLine("Who is {0}?", person);
string data = Console.ReadLine();
var filestream = File.Create(person + ".txt");
byte[] b = ASCIIEncoding.ASCII.GetBytes(data);
filestream.Write(b, 0, b.Length);
filestream.Close();
}
else
{
if (word.StartsWith("Who"))
{
string data = File.ReadAllText(person + ".txt");
Console.WriteLine("{0} is a {1}", person, data);
}
if (word.StartsWith("Where"))
{
MatchProffesionsWithProperties(person, DateTime.Today.DayOfWeek);
}
}
}
static void Greet(int hour)
{
if (hour > 0 && hour < 12)
{
Console.WriteLine("Good Morning");
}
if (hour > 12 && hour < 17)
{
Console.WriteLine("Good Afternoon");
}
if (hour > 17 && hour <= 23)
{
Console.WriteLine("Good Evening");
}
}
static void ProcessQuestionForMyself(string word)
{
var array = word.Split(' ');
//Process the components of the sentence
string type = array[1];
// get the type of the sentence
string value = array[3].Substring(0,array[3].Length-1);
// get the value of the sentence
string questiontype = array[3].Substring(0,array[3].Length-1);
//get the type of the question
if (word.EndsWith("."))
{
string correctvalue = File.ReadAllText(type+".txt");
// read from file the correct value
if (type == "name")
{
if (correctvalue == GetLowerUpper(value)[0] || correctvalue == GetLowerUpper(value)[1])
{
Console.WriteLine("Correct.My name is {0}.", value);
// print response
}
else
{
Console.WriteLine("{0} is not my name.", value);
// print response
}
}
if (type == "age")
{
if (correctvalue == value)
{
Console.WriteLine("Correct.I am {0} years old.", value);
// print response
}
else
{
Console.WriteLine("{0} is not my age.", value);
// print response
}
}
}
if (word.EndsWith("?"))
{
string answer = File.ReadAllText(questiontype+".txt");
Console.WriteLine(answer);
}
}
static string[] GetLowerUpper(string word)
{
string upperword = word.Replace(word[0].ToString(), word[0].ToString().ToUpper());
string lowerword = word.ToLower();
//Create strings for answer and question0
string[] array = new string[2] { upperword, lowerword };
return array;
}
static void MainThread()
{
if (HavingGreeted == false)
{
Greet(DateTime.Now.Hour);
HavingGreeted = true;
}
string data = Console.ReadLine();
if (data.Contains(GetLowerUpper("your")[0]) || data.Contains(GetLowerUpper("your")[1]))
{
//Append to me
ProcessQuestionForMyself(data);
}
if (!data.Contains("your"))
{
//Append to someone else
ProcessQuestionForPerson(data);
}
// Repeat thread
Thread repeatmainthread = new Thread(new ThreadStart(MainThread));
repeatmainthread.Start();
}
static void Main(string[] args)
{
KeyValuePairs = new Dictionary<string, string>();
KeyValuePairs.Add("Banker", "Bank");
Thread mainthread = new Thread(new ThreadStart(MainThread));
mainthread.Start();
}
}
}
The expected result would be the AI to tell me that John should be at the bank.
KeyValuePairs.ContainsKey(property) is case-sensitive, so "banker" will not match "Banker". You can allow case-insensitive searching by changing the initialisation of KeyValuePairs to:
KeyValuePairs = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
Changing ProcessQuestionForPerson to still ask a "Where is ..." question after creating a new file for that person:
static void ProcessQuestionForPerson(string word)
{
var array = word.Split(' ');
bool fileExisted = false;
if (word.Contains("Wh"))
{
person = array[2].Substring(0,array[2].Length-1);
}
else
{
person = array[1];
}
fileExisted = File.Exists(person+".txt");
if (!fileExisted)
{
Console.WriteLine("Who is {0}?", person);
string data = Console.ReadLine();
var filestream = File.Create(person + ".txt");
byte[] b = ASCIIEncoding.ASCII.GetBytes(data);
filestream.Write(b, 0, b.Length);
filestream.Close();
}
if (word.StartsWith("Who") && fileExisted) //We don't want to answer "Who" when we've just answered it ourselves (remove && fileExisted if you do)
{
string data = File.ReadAllText(person + ".txt");
Console.WriteLine("{0} is a {1}", person, data);
}
if (word.StartsWith("Where"))
{
MatchProffesionsWithProperties(person, DateTime.Today.DayOfWeek);
}
}

Reading in file and Printing price with Product ID

I have a file that has a product ID (can only be 8 characters) followed by a new line and different prices in ascending order with respect to time on new lines. I need to print the ProductID and the latest price.
Here is an example of the contents of the file:
A1234567
200.000
300.000
B1234567
200.000
400.000
C12345678
100.00
200.00
So in this example I need to print:
A1234567: 300.00
B1234567: 400.00
I've used a regex expression to print the product IDs. But I can't figure out how to get the latest price. I was thinking of getting the line before the product ID, but not sure how to do that.
Here is the code I have:
private static void OutputCusipPrice(string filePath)
{
using (StreamReader sr = File.OpenText(filePath))
{
String s = "";
Regex r = new Regex("^[a-zA-Z0-9]*$");
int pos = 0;
while ((s = sr.ReadLine()) != null)
{
if (r.IsMatch(s) && s.Length == 8)
{
Console.WriteLine(s);
}
else
{
double result;
if (Double.TryParse(s, out result))
{
Console.WriteLine(s);
}
}
}
}
}
Please try this, Its just an implementation mentioned by #Jason Boyd
private static void OutputCusipPrice(string filePath)
{
using (StreamReader sr = File.OpenText(filePath))
{
String row = "", current="",previous = "", price = "";
bool skipInvalid = false; // This is to remove the negative case
Regex r = new Regex("^[a-zA-Z0-9]*$");
int pos = 0;
while ((row = sr.ReadLine()) != null)
{
if (r.IsMatch(row) && row.Length == 8)
{
current = row;
if (previous == "") previous = current;
if (current != previous )
{
previous = row;
Console.WriteLine("Price : " + price);
}
skipInvalid = false;
Console.WriteLine(row);
}
else
{
if (skipInvalid) continue;
double result;
if (Double.TryParse(row, out result))
{
price = result.ToString(); // cache the previous
}
else
{
skipInvalid = true;
}
}
}
if (previous != "")
{
Console.WriteLine("Price : " + price);
}
}
}
This should give you the desired result.
var input = #"A1234567
200.000
300.000
B1234567
200.000
400.000
C1234567
100.00
200.00";
var productIds = Regex.Matches(input, #".{8}\r\n")
.Cast<Match>()
.Select((m, i) => new { index = i, value = m.Value });
var prices = Regex.Split(input, #".{8}\r\n").Where(r => !string.IsNullOrWhiteSpace(r))
.Select((r, i) => new { index = i, value = r });
var result = productIds.Join(prices, pro => pro.index, pri => pri.index, (pro, pri) => new {productid = pro.value,prices=Regex.Split(pri.value,#"\r\n")
.Where(r => !string.IsNullOrWhiteSpace(r))
.Reverse().ToArray()[0]});

Logic to determine a postal carrier based on the tracking number C#

working on some simple logic to determine a postal carrier based on the tracking number. I am trying to put the tracking number into an array called "trackingNumberArray" then have a few if statements that compare various items of that array to determine the carrier. This is the code I have but still cannot seem to make it work. Any tips/guideance would be greatly appreciated!
static void Main(string[] args)
{
string trackingNumber = "1Z204E380338943508";
string[] trackingNumberArray = new string[] {trackingNumber};
if (trackingNumberArray.Contains("1Z"))
{
string carrierName = "UPS";
Console.WriteLine($"Carrier Name" + carrierName);
}
else if (trackingNumberArray.Length >= 12 && trackingNumberArray.Length < 14 && !!trackingNumberArray.Contains("1Z"))
{
string carrierName = "Fedex";
Console.WriteLine($"Carrier Name" + carrierName);
}
else if (trackingNumberArray.Length >= 20 && trackingNumberArray.Length < 22 && !trackingNumberArray.Contains("1Z"))
{
string carrierName = "USPS";
Console.WriteLine($"Carrier Name" + carrierName);
}
else
{
string carrierName = null;
Console.WriteLine($"did not work" + carrierName);
}
}
Instead of putting the tracking number into an array, you can just leave it as a string. The rest of your code should then work with that string. You also don't need the redundant checks for "1Z", since that was in the first conditon:
static void Main()
{
string trackingNumber = "1Z204E380338943508";
string carrierName = null;
if (trackingNumber.Contains("1Z"))
{
carrierName = "UPS";
}
else if (trackingNumber.Length >= 12 && trackingNumber.Length < 14)
{
carrierName = "FedEx";
}
else if (trackingNumber.Length >= 20 && trackingNumber.Length < 22)
{
carrierName = "USPS";
}
if (carrierName == null)
{
Console.WriteLine("Did not work.");
}
else
{
Console.WriteLine($"Carrier name: {carrierName}");
}
GetKeyFromUser("\nPress any key to exit...");
}
You could then create a static method out of the code:
public static string GetCarrierName(string trackingNumber)
{
if (trackingNumber == null) return null;
if (trackingNumber.Contains("1Z")) return "UPS";
if (trackingNumber.Length >= 12 && trackingNumber.Length < 14) return "FedEx";
if (trackingNumber.Length >= 20 && trackingNumber.Length < 22) return "USPS";
return null;
}
And use it like:
static void Main()
{
string carrierName = GetCarrierName("1Z204E380338943508");
if (carrierName == null)
{
Console.WriteLine("Unknown tracking id format.");
}
else
{
Console.WriteLine($"Carrier name: {carrierName}");
}
GetKeyFromUser("\nPress any key to exit...");
}
trackingNumberArray.Contains("1Z")
This won't work because your array is made up of individual letters. If you want to look for a string, use the original string.
trackingNumber.Contains("1Z")
Also, there's no need for this bit, because it's in an 'else' that can only be reached when it's true.
&& !trackingNumberArray.Contains("1Z")

How can I compare each element of one string array to every element of another string array?

I've got two string arrays. I want to select one element from the first array and compare to each element of the second array. If element from first array exist in elements of second array i whant to write for example ("Element exist") or something like this.
This should be possible to do with two for loops ?
EDIT
Ok i finaly achived what i wanted usign this code:
string[] ArrayA = { "dog", "cat", "test", "ultra", "czkaka", "laka","kate" };
string[] ArrayB = { "what", "car", "test", "laka","laska","kate" };
bool foundSwith = false;
for (int i = 0; i < ArrayA.Length; i++)
{
for (int j = 0; j < ArrayB.Length; j++)
{
if (ArrayA[i].Equals(ArrayB[j]))
{
foundSwith = true;
Console.WriteLine("arrayA element: " + ArrayA[i] + " was FOUND in arrayB");
}
}
if (foundSwith == false)
{
Console.WriteLine("arrayA element: " + ArrayA[i] + " was NOT found in arrayB");
}
foundSwith = false;
}
I hope this will help others who will want to compare two arrays ;). its all about this foundSwitch. Thx for help once again.
foreach (string str in yourFirstArray)
{
if (yourSearchedArray.Contains(str))
{
Console.WriteLine("Exists");
}
}
foreach (string str in strArray)
{
foreach (string str2 in strArray2)
{
if (str == str2)
{
Console.WriteLine("element exists");
}
}
}
Updated to display when string does not exist in strArray2
bool matchFound = false;
foreach (string str in strArray)
{
foreach (string str2 in strArray2)
{
if (str == str2)
{
matchFound = true;
Console.WriteLine("a match has been found");
}
}
if (matchFound == false)
{
Console.WriteLine("no match found");
}
}
Or another way of doing this in less lines of code:
foreach (string str in strArray)
{
if(strArray2.Contains(str))
{
Console.WriteLine("a match has been found");
}
else
{
Console.WriteLine("no match found");
}
}
You can also try:
ArrayA.All(ArrayB.Contains);

Categories

Resources