Golang: Interview Question: Programming: General 1

 11th July 2022 at 3:16pm

题目

写一个函数,接受一个 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",
      }

参考

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
}