Nested Map: Breakdown analysis of events and return result as nested JSON

Problem

An event consists of multiple properties, each defined as a key-value pair, where the key is a string and the value is of a primitive type such as numbers or strings. Importantly, each event must include a mandatory 'Name' property. Given a list of events, the task is to count the number of events based on specified properties. To illustrate, let's consider an example with four events and two analyses

 1[
 2  {
 3    "Name": "SignUp",
 4    "Device": "Android",
 5    "Country": "US",
 6    "City": "NYC",
 7  },
 8  {
 9    "Name": "SignUp",
10    "Device": "Android",
11    "Country": "France",
12  },
13  {
14    "Name": "SignUp",
15    "Device": "Desktop",
16    "Country": "US",
17    "City": "Seattle",
18    "Browser": "Chrome"
19  },
20  {
21    "Name": "SignUp",
22    "Device": "iOS",
23    "Country": "France",
24    "Browser": "Safari",
25  }
26]
 1// count by properties: first by Device, then by Country. 
 2{
 3  "Android": {"US": 1, "France": 1},
 4  "Desktop": {"US": 1},
 5  "iOS": {"France": 1}
 6}
 7
 8// count by properties: first by Country, then by City, then by Browser.
 9// If a property is missing , treat the value as "UnKnown"
10{
11  "US": {"NYC": {"UnKnown": 1}, "Seattle": {"Chrome": 1}},
12  "France": {"UnKnown": {"UnKnown": 1, "Safari": 1}}
13}

You are provided the following code to start with. Implement the function breakdownAnalysis. For simplicity, you can assume each property value is string.

1type Event struct {
2  Name       string
3  Properties map[string]interface{}
4}
5
6type Result map[string]interface{}
7
8func breakdownAnalysis(events []Event, eventName string, props []string) Result {
9}

Solution

The requirement specifies that the Result is a nested map. The task is to dynamically construct a nested map with the following characteristics: 1). each internal level should be a map from string to map, and 2). each terminal level should be a map from string to an integer. During the interview, it's essential not to be overwhelmed by type casting interface{} in Go, which is not be a common task in daily development. Stay calm and recognize the core of the problem: traversing the nested map level by level and incrementing counters at the terminal level. Once this core concept is understood, it becomes evident that this is a classical problem of constructing a tree recursively, similar to a Trie. The following insertToResult constructs the nested map recursively. See the full code and test.

 1// Recursively insert prop values to the result.
 2func insertToResult(r Result, propValues []string) {
 3  if len(propValues) == 0 {
 4    return
 5  }
 6  curValue := propValues[0]
 7  v, ok := r[curValue]
 8  if ok {
 9    if len(propValues) == 1 {
10      r[curValue] = v.(int) + 1
11    } else {
12      insertToResult(v.(Result), propValues[1:])
13    }
14    return
15  }
16
17  if len(propValues) == 1 {
18    r[curValue] = 1
19  } else {
20    r[curValue] = make(Result)
21    insertToResult(r[curValue].(Result), propValues[1:])
22  }
23}