Coding interviews
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
var rows = new[] { new Row("tea", 2), new Row("Tea", 1), new Row("tea", 3) };
var totals = Aggregate(rows);
foreach (var row in totals.OrderBy(x => x.Key, StringComparer.Ordinal)) Console.WriteLine(row.Key + ":" + row.Value);
bool invalid = false; try { Aggregate(new[] { new Row("tea", -1) }); } catch (ArgumentOutOfRangeException) { invalid = true; }
Console.WriteLine("negative rejected:" + invalid.ToString().ToLowerInvariant());
static Dictionary<string,long> Aggregate(IEnumerable<Row> rows) {
var totals = new Dictionary<string,long>(StringComparer.Ordinal);
foreach (var row in rows) {
if (string.IsNullOrEmpty(row.Key) || row.Quantity < 0) throw new ArgumentOutOfRangeException(nameof(rows));
totals.TryGetValue(row.Key, out long previous); totals[row.Key] = checked(previous + row.Quantity);
}
return totals;
}
sealed record Row(string Key, int Quantity);
Reveal answer and reasoning
The output lines, in order, are “Tea:1”; “tea:5”; “negative rejected:true”. The first tea contributes two, Tea creates another bucket with one, and the second tea adds three to the original bucket. Sorting ordinal keys displays Tea before tea. A separate negative row throws the documented argument exception, which the harness turns into negative rejected:true. The input row array is never modified.
A loop invariant makes the reasoning concise: after each accepted row, every stored total equals the sum of quantities for that exact key in the processed prefix. TryGetValue supplies zero only when no entry exists. Publishing the dictionary after the loop means a rejected row cannot expose a half-finished return value.
For n rows and u distinct keys, aggregation uses expected O(n) dictionary operations and O(u) stored totals; string hashing also examines key text. Display sorting costs O(u log u) comparisons and additional sorting storage. Checked long arithmetic rejects overflow rather than silently changing an otherwise valid total. Case normalization, negative adjustments and blank-key handling are business decisions, not harmless implementation details.
Returning a partially filled dictionary after an exception would create a different contract. Local construction makes failure discard the unpublished result; it does not provide a database transaction or a thread-safe aggregation service.
What a strong answer covers
- Exact output: Tea:1 | tea:5 | negative rejected:true
- The first tea contributes two, Tea creates another bucket with one, and the second tea adds three to the original bucket.
- Case normalization, negative adjustments and blank-key handling are business decisions, not harmless implementation details.
Common pitfall
Case normalization, negative adjustments and blank-key handling are business decisions, not harmless implementation details. Returning a partially filled dictionary after an exception would create a different contract. Local construction makes failure discard the unpublished result; it does not provide a database transaction or a thread-safe aggregation service.
Follow-up questions
- Add zero-quantity rows and an empty key as separate cases. Predict that zero preserves the total while an empty key fails. Hint: validity and contribution are different questions, so do not use quantity greater than zero as the only admission rule.
- How would ordinal-ignore-case grouping change the sample and the displayed representative key policy?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.