题目
写一个函数,接受一个 map[string]string
作为输入,把 map 的 key 按点号(.
)进行分割成多层 map(树状结构)。
输入示例:
{
"a.a1": "hello",
"a.a2": "42",
"b.b1.b11": "world",
"c": "42",
}
输出示例:
{
"a": {
"a1": "hello",
"a2": "42",
},
"b": {
"b1": {
"b11": "world",
},
},
"c": "42",
}
考察点
需求拆解的能力:
- 能否把需求分解?
- 有没有先定义一个好的函数签名?
- 有没有先构思好整体思路?
- 错误信息如何包裹?
编码细节:
- 有没有思考可能的错误情况?比如:
- 不合法的 key,如
a.
,.
,.a
- 切割后,某 key 即是叶子节点又是非叶子的情况,如下面的
a
:{ "a": "42", "a.a1": "hello", }
- 不合法的 key,如
参考
package projectconfig
import (
"errors"
"fmt"
"git.garena.com/shopee/chatbot/common/logkit"
"github.com/go-playground/validator/v10"
"github.com/mitchellh/mapstructure"
"go.uber.org/zap"
"strings"
)
const Separator = "."
type TreeNodeErrorReason int
const (
TreeNodeErrorConflict TreeNodeErrorReason = iota
TreeNodeErrorEmptyPart
)
type TreeNodeError struct {
Node string
Reason TreeNodeErrorReason
}
func (t TreeNodeError) Error() string {
switch t.Reason {
case TreeNodeErrorConflict:
return fmt.Sprintf("path `%s` is used as both leaf and non-leaf nodes", t.Node)
case TreeNodeErrorEmptyPart:
return fmt.Sprintf("path `%s` contains empty parts:%q", t.Node, strings.Split(t.Node, Separator))
default:
return fmt.Sprintf("(bug) unknown error reason %d for path `%s`", t.Reason, t.Node)
}
}
// ToMultiLayerMap 把 map key 按点号(".")进行分割成多层 map(树状结构)。比如:
// 输入:
// {
// "a.a1": "hello",
// "a.a2": "42",
// "b.b1.b11": "world",
// "c": "42",
// }
// 输出:
// {
// "a": {
// "a1": "hello",
// "a2": "42",
// },
// "b": {
// "b1": {
// "b11": "world",
// },
// },
// "c": "42",
// }
// 假如分割后存在一个节点,即作为叶子节点又作为非叶子节点,函数会报错。比如:
// {
// "a": "42",
// "a.a1": "hello",
// }
// 其中 "a" 即需要作为叶子节点,又需要作为某一层的非叶子节点。这种情况是无法被处理的。
func ToMultiLayerMap(m map[string]string) (map[string]interface{}, error) {
result := make(map[string]interface{})
subLayerMap := make(map[string]map[string]string)
for k, v := range m {
parts := strings.SplitN(k, Separator, 2)
for _, part := range parts {
if part == "" {
return nil, TreeNodeError{k, TreeNodeErrorEmptyPart}
}
}
if len(parts) == 1 {
if _, ok := subLayerMap[parts[0]]; ok {
return nil, TreeNodeError{parts[0], TreeNodeErrorConflict}
}
result[parts[0]] = v
continue
}
branchPart, remainingPart := parts[0], parts[1]
if _, ok := result[branchPart]; ok {
return nil, TreeNodeError{parts[0], TreeNodeErrorConflict}
}
val, ok := subLayerMap[branchPart]
if ok {
val[remainingPart] = v
} else {
subLayerMap[branchPart] = map[string]string{remainingPart: v}
}
}
for k, v := range subLayerMap {
subMap, err := ToMultiLayerMap(v)
if err != nil {
treeNodeError := TreeNodeError{}
if errors.As(err, &treeNodeError) {
treeNodeError.Node = k + "." + treeNodeError.Node
return nil, treeNodeError
}
return nil, err
}
result[k] = subMap
}
return result, nil
}