Quick Overview

Group encoded event rows by a caller-specified ordered list of attributes and return the nested counts as canonical compact JSON. This challenge tests dynamic map depth, field-position resolution, deterministic key sorting, repeated combinations, portable serialization, empty input, and output-aware complexity.

Group Events by an Ordered Attribute List

Company: Mixpanel

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

## Group Events by an Ordered Attribute List ### Problem Implement `groupEventCounts(fieldNames, events, groupBy) -> resultJson`. Each row in `events` is a portable array encoding of one event object: `events[i][j]` is the value of field `fieldNames[j]`. Group the events first by `groupBy[0]`, then by `groupBy[1]`, and so on. The conceptual result is a nested map whose depth equals `groupBy.length` and whose leaves are occurrence counts. Return that nested map as one canonical compact JSON string. This string representation preserves the requested dynamic map depth while giving Python, JavaScript, Java, and C++ the same exact return type. ### Portable Contract - `fieldNames` contains between 1 and 12 unique strings. - `events` contains between 0 and 8,000 rows. Every row has exactly `fieldNames.length` string values. - `groupBy` contains between 1 and `fieldNames.length` unique names, and every name occurs in `fieldNames`. - Field names and event values contain 1 to 24 characters drawn only from lowercase ASCII letters, decimal digits, and underscore. They therefore require no escaping inside JSON object keys. - Every event contributes exactly one to the leaf reached by its values in `groupBy` order. - The returned JSON contains no whitespace. Object keys are sorted in ascending ASCII lexicographic order at every level. Counts are unquoted decimal integers. Empty `events` returns `"{}"`. - Do not modify the inputs. - Let `B` be the compact UTF-8 JSON byte length of `[fieldNames,events,groupBy]`, counting every bracket, comma, quote, and character byte. Inputs satisfy `B <= 96,000`. - Let `R` be the compact UTF-8 JSON byte length of the returned string, including the outer serialization's required escaping of its quotes. Inputs guarantee `R <= 96,000`, so serialized input plus result is at most `192,000` bytes. - For `e = events.length`, `g = groupBy.length`, and `u` total distinct nested map entries, target `O(e * g + u log u)` time and `O(u + g)` auxiliary space, excluding input storage. All containers are homogeneous strings: - Python: `def groupEventCounts(fieldNames: list[str], events: list[list[str]], groupBy: list[str]) -> str` - JavaScript: `function groupEventCounts(fieldNames, events, groupBy)` returns a string. - Java: `String groupEventCounts(List<String> fieldNames, List<List<String>> events, List<String> groupBy)` - C++: `string groupEventCounts(const vector<string>& fieldNames, const vector<vector<string>>& events, const vector<string>& groupBy)` ```hint Resolve requested fields once Map every grouping name to its row position before visiting the event rows, rather than searching `fieldNames` for every value. ``` ```hint Separate counting from encoding Build the nested counts first, then serialize each map level by visiting its keys in the required order. ``` ### Examples ```text fieldNames = ["type", "country"] events = [["type1", "us"], ["type1", "uk"], ["type1", "us"], ["type2", "fr"]] groupBy = ["type", "country"] resultJson contents = {"type1":{"uk":1,"us":2},"type2":{"fr":1}} ``` ```text fieldNames = ["browser", "country"] events = [] groupBy = ["country"] resultJson contents = {} ``` ### Discussion Requirements 1. State how an encoded event row is mapped back to its named fields. 2. Explain why the order of `groupBy` determines the nested-map shape and why repeated event combinations increment one leaf. 3. Cover empty events, one grouping attribute, several attributes, repeated values, and invalid field names even though valid inputs exclude them. 4. Explain how sorted keys and the restricted character set make the returned JSON deterministic across all four languages.

Quick Answer: Group encoded event rows by a caller-specified ordered list of attributes and return the nested counts as canonical compact JSON. This challenge tests dynamic map depth, field-position resolution, deterministic key sorting, repeated combinations, portable serialization, empty input, and output-aware complexity.

Every event in a telemetry stream carries the same set of named string fields. To keep the payload portable across languages, each event object is encoded as an array of its field values: `events[i][j]` holds the value of field `fieldNames[j]` for event `i`. Implement `groupEventCounts(fieldNames, events, groupBy)`. Group the events by `groupBy[0]` first, then by `groupBy[1]`, and so on. The conceptual result is a nested map whose depth equals `groupBy.length` and whose leaves are occurrence counts: every event contributes exactly 1 to exactly one leaf, the one reached by following its own values in `groupBy` order. Because `groupBy` may name a reordered subset of `fieldNames`, the nesting order follows `groupBy`, not `fieldNames`. Return that nested map serialized as a single canonical compact JSON string. Returning a string, rather than a map of dynamic depth, is what lets Python, JavaScript, Java, and C++ share one identical signature. ### Output format The returned string must be byte-for-byte canonical: - No whitespace anywhere. - At every level, object keys appear in ascending ASCII byte order. - Counts are unquoted decimal integers. - When `events` is empty the result is exactly `{}`, whatever `groupBy.length` is, never a chain of empty objects. **ASCII order is not alphabetical order.** The permitted characters order as digits `0`-`9` (0x30-0x39) < underscore `_` (0x5F) < lowercase `a`-`z` (0x61-0x7A). So the keys `7`, `12`, `2`, `007`, `_a`, `ab`, `ab_` must be emitted in the order `007`, `12`, `2`, `7`, `_a`, `ab`, `ab_`. The restricted alphabet means a key never requires JSON escaping. Do not modify the inputs. ### Examples **Example 1** ``` Input: fieldNames = ["type", "country"] events = [["type1", "us"], ["type1", "uk"], ["type1", "us"], ["type2", "fr"]] groupBy = ["type", "country"] Output: {"type1":{"uk":1,"us":2},"type2":{"fr":1}} ``` Two events share the pair `("type1", "us")`, so that leaf holds 2. Inside `type1` the key `uk` precedes `us`, and at the top level `type1` precedes `type2`. **Example 2** ``` Input: fieldNames = ["browser", "country"] events = [] groupBy = ["country"] Output: {} ``` No event exists, so no key exists at any level and the whole result is the empty object. ### Signatures - Python: `def groupEventCounts(fieldNames: list[str], events: list[list[str]], groupBy: list[str]) -> str` - JavaScript: `function groupEventCounts(fieldNames, events, groupBy)`, returning a string - Java: `String groupEventCounts(List<String> fieldNames, List<List<String>> events, List<String> groupBy)` - C++: `string groupEventCounts(const vector<string>& fieldNames, const vector<vector<string>>& events, const vector<string>& groupBy)` Target `O(e * g + u log u)` time and `O(u + g)` auxiliary space, excluding input storage and the returned string, where `e` is `events.length`, `g` is `groupBy.length`, and `u` is the total number of distinct nested map entries.

Constraints

  • 1 <= fieldNames.length <= 12, and the strings in fieldNames are unique
  • 0 <= events.length <= 8000
  • events[i].length == fieldNames.length for every row i
  • 1 <= groupBy.length <= fieldNames.length, the strings in groupBy are unique, and every one of them occurs in fieldNames
  • Every field name and every event value has length 1 to 24 and is drawn only from lowercase ASCII letters (a-z), decimal digits (0-9), and underscore (_), so no key ever requires JSON escaping
  • Let B be the compact UTF-8 JSON byte length of [fieldNames, events, groupBy], counting every bracket, comma, quote, and character byte; B <= 96000
  • Let R be the compact UTF-8 JSON byte length of the returned string, including the outer serialization's required escaping of its quotes; R <= 96000, so serialized input plus result is at most 192000 bytes
  • Every occurrence count is at least 1 and at most events.length <= 8000; no value or intermediate result approaches 2^31 - 1, so a 32-bit signed integer counter is sufficient in Java and C++
  • Inputs are never modified
  • Invalid inputs are outside the valid domain and never occur: no name in groupBy is absent from fieldNames, no name list contains duplicates, no row is ragged, and no character falls outside the permitted alphabet

Examples

Input: (['type', 'country'], [['type1', 'us'], ['type1', 'uk'], ['type1', 'us'], ['type2', 'fr']], ['type', 'country'])

Expected Output: '{"type1":{"uk":1,"us":2},"type2":{"fr":1}}'

Input: (['browser', 'country'], [], ['country'])

Expected Output: '{}'

Hints

  1. Resolve each name in groupBy to its column index in fieldNames once, before you touch any event row, instead of searching fieldNames again for every value you read.
  2. Split the work in two: accumulate the nested counts first, then walk the finished structure once to build the output string. Nesting depth is at most 12, so recursing over levels is safe.
  3. Before you reach for a built-in JSON serializer, check what key order your language's default map or plain object actually produces. Some runtimes reorder keys that look like array indices ahead of everything else, and some string comparators are locale-aware rather than byte-ordered.

Loading coding console...