Coding interviews
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
Console.WriteLine(string.Join("|",Parse("a,\"b,c\",\"d\"\"e\",",100,10)));
bool rejected=false; try { Parse("\"unfinished",100,10); } catch(FormatException) { rejected=true; } Console.WriteLine("unclosed rejected:"+rejected.ToString().ToLowerInvariant());
static string[] Parse(string text,int maxLength,int maxFields) {
if(text.Length>maxLength || maxFields<1) throw new ArgumentOutOfRangeException();
var fields=new List<string>(); var value=new StringBuilder(); int state=0;
void End() { if(fields.Count>=maxFields) throw new FormatException("too many fields"); fields.Add(value.ToString()); value.Clear(); }
for(int i=0;i<text.Length;i++) { char c=text[i];
if(state==1) { if(c=='"') { if(i+1<text.Length && text[i+1]=='"') { value.Append('"'); i++; } else state=2; } else value.Append(c); }
else if(c==',') { End(); state=0; }
else if(state==2) throw new FormatException("after quote");
else if(c=='"') { if(value.Length!=0) throw new FormatException("quote position"); state=1; }
else value.Append(c);
}
if(state==1) throw new FormatException("unclosed quote"); End(); return fields.ToArray();
}
Reveal answer and reasoning
The output lines, in order, are “a|b,c|d"e|”; “unclosed rejected:true”. The unquoted a becomes the first field. The comma inside quoted b,c remains data, and doubled quotes inside d double-quote e decode into one literal quote. The final delimiter contributes an empty fourth field. A separate unclosed opening quote fails instead of silently accepting partial data. State zero is unquoted, one quoted and two after a closing quote.
End materializes the current field, clears its builder and enforces the field-count limit. The final End call is necessary even after a trailing delimiter because the grammar preserves a final empty field. Doubling a quote consumes the second quote immediately, ensuring it is data rather than a second closing transition.
Each input code unit is inspected a bounded number of times, giving O(n) time and O(n) output/buffer storage within the accepted input limit. The maxFields check bounds the number of fields. StringBuilder and final strings still allocate; this is not a zero-allocation parser. Splitting on commas first destroys the information needed to recognize quoted delimiters. Accepting arbitrary characters after a closing quote creates ambiguous recovery behavior.
Limits must apply before unbounded accumulation, and the parser should not return a partially accepted record after a grammar error.
What a strong answer covers
- Exact output: a|b,c|d"e| | unclosed rejected:true
- The unquoted a becomes the first field.
- Splitting on commas first destroys the information needed to recognize quoted delimiters.
Common pitfall
Splitting on commas first destroys the information needed to recognize quoted delimiters. Accepting arbitrary characters after a closing quote creates ambiguous recovery behavior. Limits must apply before unbounded accumulation, and the parser should not return a partially accepted record after a grammar error.
Follow-up questions
- Test an empty record, an empty quoted field, an embedded quote in an unquoted field and characters after a closing quote. Hint: specify the expected field count for an empty input before deciding whether it is an error.
- Which state must survive a chunk boundary immediately after the first quote of a doubled quote?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.