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}