在Go语言中理解使用`interface{}`的问题

huangapple go评论88阅读模式
英文:

Problems understanding usage of `interface{}` in Go

问题

我正在尝试将一个算法从Python移植到Go。其中的核心部分是使用字典构建的树,由于每个节点可以有任意数量的子节点,所以应该保持这种方式。所有叶子节点都在同一层级上,因此在最低层级上,字典包含其他字典,而最低层级的字典包含浮点数。就像这样:

tree = {}
insert(tree, ['a', 'b'], 1.0)
print tree['a']['b']

因此,在尝试将代码移植到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"])
}

这按预期工作,所以下一步是将其转换为一个接受“tree”、路径和值的例程。我选择了递归方法,并得到了以下结果:

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) //递归
    case l == 1:
        leaf := tree
        leaf[node] = value
    }
}

这是我想象中例程应该的结构,但是我无法让标有“递归”的那一行工作。要么会有编译器错误,要么如果我尝试对tree[node]执行类型断言,则会有运行时错误。请问正确的做法是什么?

英文:

I'm trying to port an algorithm from Python to Go. The central part of it is a tree built using dicts, which should stay this way since each node can have an arbitrary number of children. All leaves are at the same level, so up the the lowest level the dicts contain other dicts, while the lowest level ones contain floats. Like this:

tree = {}
insert(tree, ['a', 'b'], 1.0)
print tree['a']['b']

So while trying to port the code to Go while learning the language at the same time, this is what I started with to test the basic idea:

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"])
}

This works as expected, so the next step was to turn this into a routine that would take a "tree", a path, and a value. I chose the recursive approach and came up with this:

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
	}
}

This is how I imagine the routine should be structured, but I can't get the line marked with "recursion" to work. There is either a compiler error, or a runtime error if I try to perform a type assertion on tree[node]. What would be the correct way to do this?

答案1

得分: 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
	}
}

请注意,我为该映射创建了一个新类型。这使得代码更容易理解。我还在树节点和叶子上都使用了相同的map[string]interface{}。如果你想从结果树中获取一个浮点数,还需要另一个类型断言:

leaf := t["a"].(Tree)["b"]  // leaf的类型是`interface{}`
val := leaf.(float32)
英文:

Go is perhaps not the ideal solution for generic data structures like this. The type assertions make it possible, but manipulating data in it requires more work that you are used to from python and other scripting languages.

About your specific issue: You are missing a type assertion in the insert() call. The value of tree[node] is of type interface{} at that point. The function expects type map[string]interface{}. A type assertion will solve that.

Here's a working example:

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
	}
}

Note that I created a new type for the map. This makes the code a little easier to follow. I also use the same 'map[string]interface{}` for both tree nodes and leaves. If you want to get a float out of the resulting tree, another type assertion is needed:

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

答案2

得分: 6

好的...问题是你试图使用Python的习惯用法来编写Go代码,并且你正在使用哈希表创建一棵树?嗯?然后你必须确保键是唯一的并且做一堆其他的事情,而如果你只是将子节点集合设置为一个切片,你就可以免费获得这种功能。

我不会将Tree明确地定义为map[string]interface{}。树和树上的节点实际上是相同的东西,因为它是一种递归数据类型。

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

func NewTree(v interface{}) *Tree {
    return &Tree{
        Children: []*Tree{},
        Value:    v,
    }
}

所以要添加一个子节点...

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))
    }
}

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

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") // 你应该真正观察这里的返回值。
        for _, grandchild := range child.Children {
            inner(depth+1, grandchild)
        }
    }
    inner(0, t)
}

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

有一些文章,比如《Python不是Java》(http://dirtsimple.org/2004/12/python-is-not-java.html),以此类推,Go也不是Python。

英文:

well... the problem is that you're trying to code Go using Python idioms, and you're making a tree with... hashtables? Huh? Then you have to maintain that the keys are unique and do a bunch of otherstuff, when if you just made the set of children a slice, you get that sort of thing for free.

I wouldn't make a Tree an explicit map[string]interface{}. A tree and a node on a tree are really the same thing, since it's a recursive datatype.

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

func NewTree(v interface{}) *Tree {
    return &amp;Tree{
        Children: []*Tree{},
        Value:    v,
    }
}

so to add a child...

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))
    }
}

and if you wanted to implement some recursive function...

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 &lt; depth; i++ {
            io.WriteString(w, prefix)
        }
        io.WriteString(w, child.String()+&quot;\n&quot;) // you should really observe the return value here.
        for _, grandchild := range child.Children {
            inner(depth+1, grandchild)
        }
    }
    inner(0, t)
}

something like that. Any node can be made the root of some tree, since a subtree is just a tree itself. See here for a working example: http://play.golang.org/p/rEx43vOnXN

There are some articles out there like "Python is not Java" (http://dirtsimple.org/2004/12/python-is-not-java.html), and to that effect, Go is not Python.

huangapple
  • 本文由 发表于 2012年6月5日 22:51:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/10899571.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定