在Go中理解`interface {}`的用法的问题

elp*_*res 2 go

我正在尝试将算法从Python移植到Go.它的核心部分是使用dicts构建的树,它应该保持这种方式,因为每个节点可以有任意数量的子节点.所有叶子都处于同一水平,所以最高水平的dicts包含其他dicts,而最低水平的叶子包含浮动.像这样:

tree = {}
insert(tree, ['a', 'b'], 1.0)
print tree['a']['b']
Run Code Online (Sandbox Code Playgroud)

因此,在尝试将代码移植到Go同时学习语言时,这就是我开始测试基本思想的原因:

func main() {
    tree := make(map[string]interface{})
    tree["a"] = make(map[string]float32)
    tree["a"].(map[string]float32)["b"] = 1.0
    fmt.Println(tree["a"].(map[string]float32)["b"])
}
Run Code Online (Sandbox Code Playgroud)

这按预期工作,因此下一步是将其转换为采用"树",路径和值的例程.我选择了递归方法并想出了这个:

func insert(tree map[string]interface{}, path []string, value float32) {
    node := path[0]
    l := len(path)
    switch {
    case l > 1:
        if _, ok := tree[node]; !ok {
            if l > 2 {
                tree[node] = make(map[string]interface{})
            } else {
                tree[node] = make(map[string]float32)
            }
        }
        insert(tree[node], path[1:], value) //recursion
    case l == 1:
        leaf := tree
        leaf[node] = value
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我想象的例程应该是如何构建的,但我不能让标有"递归"的行工作.如果我尝试执行类型断言,则存在编译器错误或运行时错误tree[node].这样做的正确方法是什么?

jim*_*imt 7

Go可能不是像这样的通用数据结构的理想解决方案.类型断言使其成为可能,但是在其中操作数据需要您从python和其他脚本语言中习惯的更多工作.

关于您的具体问题:您在insert()呼叫中缺少类型断言.该点的值tree[node]是该类型interface{}的值.该函数需要类型map[string]interface{}.类型断言将解决这个问题.

这是一个有效的例子:

package main

import "fmt"

type Tree map[string]interface{}

func main() {
    t := make(Tree)
    insert(t, []string{"a", "b"}, 1.0)

    v := t["a"].(Tree)["b"]
    fmt.Printf("%T %v\n", v, v)

    // This prints: float32 1
}

func insert(tree Tree, path []string, value float32) {
    node := path[0]
    len := len(path)

    switch {
    case len == 1:
        tree[node] = value

    case len > 1:
        if _, ok := tree[node]; !ok {
            tree[node] = make(Tree)
        }

        insert(tree[node].(Tree), path[1:], value) //recursion
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我为地图创建了一个新类型.这使代码更容易遵循.我也对树节点和叶子使用相同的'map [string] interface {}`.如果要从结果树中获取浮点数,则需要另一种类型的断言:

leaf := t["a"].(Tree)["b"]  // leaf is of type 'interface{}`.
val := leaf.(float32)
Run Code Online (Sandbox Code Playgroud)

  • 我也会考虑@jorelli提供的答案.通过针对特定用例的一些修复,它可能是Go中更好的解决方案.正如他所指出的那样,将语言X中的解决方案的1:1翻译编写为语言Y往往不是一个好主意.Go有不同的做事方式.深入挖掘它将为您提供有关如何解决问题的其他想法. (2认同)

jor*_*lli 6

嗯......问题是你正在尝试使用Python成语来编写Go,而你正在使用... hashtables创建一个树?咦?然后你必须保持键是唯一的,并做一堆其他的,如果你只是让一组孩子成为一片,你可以免费得到那种东西.

我不会让树成为一个显式的map [string] interface {}.树上的树和节点实际上是相同的,因为它是递归数据类型.

type Tree struct {
    Children []*Tree
    Value    interface{}
}

func NewTree(v interface{}) *Tree {
    return &Tree{
        Children: []*Tree{},
        Value:    v,
    }
}
Run Code Online (Sandbox Code Playgroud)

所以要加个孩子......

func (t *Tree) AddChild(child interface{}) {
    switch c := child.(type) {
    case *Tree:
        t.Children = append(t.Children, c)
    default:
        t.Children = append(t.Children, NewTree(c))
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你想实现一些递归函数......

func (t *Tree) String() string {
    return fmt.Sprint(t.Value)
}

func (t *Tree) PrettyPrint(w io.Writer, prefix string) {
    var inner func(int, *Tree)
    inner = func(depth int, child *Tree) {
        for i := 0; i < depth; i++ {
            io.WriteString(w, prefix)
        }
        io.WriteString(w, child.String()+"\n") // you should really observe the return value here.
        for _, grandchild := range child.Children {
            inner(depth+1, grandchild)
        }
    }
    inner(0, t)
}
Run Code Online (Sandbox Code Playgroud)

类似的东西.任何节点都可以成为某个树的根,因为子树本身就是一棵树.请看这里的工作示例:http://play.golang.org/p/rEx43vOnXN

有一些文章像"Python不是Java"(http://dirtsimple.org/2004/12/python-is-not-java.html),为此,Go不是Python.