Go中的结构体队列

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

Queue of structs in Go

问题

我已经学习了Go,并且正在使用它进行BFS(广度优先搜索)拼图。我选择的队列实现如下:

//定义BFS算法的队列为frontier
type frontier struct {
	queue []*graphNode
}

func (F *frontier) buildFrontier() {
	F.queue = make([]*graphNode, 0, 1000)
}

func (F *frontier) pop() (g *graphNode) {
	if F.isEmpty() {
		panic("pop on empty queue!")
	}
	temp := F.queue[0]
	F.queue = F.queue[1:]
	return temp
}

func (F *frontier) push(g *graphNode) {
	F.queue = append(F.queue, g)
}

func (F *frontier) isEmpty() bool {
	return len(F.queue) == 0
}

我有两个问题:

  1. 这是一个好的实现吗?关于Go中的队列的文档很少,一般有一些关于向量的旧帖子,而列表似乎有太多的开销(虽然对于我的情况并不重要,但我想以最佳方式完成它)。

  2. 为什么对对象的调用(在这种情况下是结构体指针F *frontier)必须是一个指针?根据语法的工作方式,它似乎应该是指针的默认值,而不是显式的(也就是说,在这些情况下,你为什么会不想使用指针?)

修改后的循环版本如下:

//定义BFS算法的队列为frontier
type frontier struct {
	queue                []*graphNode
	size, head, capacity int
}

func (F *frontier) buildFrontier() {
	F.capacity = 1
	F.queue = make([]*graphNode, F.capacity)
	F.size = 0
	F.head = 0
}

func (F *frontier) pop() (g *graphNode) {
	if F.isEmpty() {
		panic("pop on empty queue!")
	}
	F.size = (F.size - 1)
	temp := F.queue[F.head]
	F.head = (F.head + 1) % F.capacity
	return temp
}

func (F *frontier) push(g *graphNode) {
	if F.isFull() {
		newSlice := make([]*graphNode, F.capacity*2)
		copy(newSlice, F.queue)
		F.queue = newSlice
		F.capacity *= 2
	}
	F.queue[(F.head+F.size)%F.capacity] = g
	F.size = (F.size + 1)
}

func (F *frontier) isEmpty() bool {
	return F.size == 0
}

func (F *frontier) isFull() bool {
	return F.size == F.capacity
}

以上是你要翻译的内容。

英文:

I have been learning Go, and I was doing a BFS puzzle with it. The queue implementation I decided on is:

//define the queue for our BFS algo as frontier.
type frontier struct {
	queue []*graphNode
}

func (F *frontier) buildFrontier() {
	F.queue = make([]*graphNode, 0, 1000)
}

func (F *frontier) pop() (g *graphNode) {
	if F.isEmpty() {
		panic("pop on empty queue!")
	}
	temp := F.queue[0]
	F.queue = F.queue[1:]
	return temp
}

func (F *frontier) push(g *graphNode) {
	F.queue = append(F.queue, g)
}

func (F *frontier) isEmpty() bool {
	return len(F.queue) == 0
}

I have 2 questions:

  1. Is this a good implementation? Documentation on queues in go is sparse, and in general there are some old posts about vectors, and a list seems to have too much overhead (not that it matters for my case, but I'm trying to do it the best way possible).

  2. Why does the call to an object (in this case the struct pointer F *frontier) have to be a pointer? It seems like the way the syntax works that it should be a default for a pointer, not explicit (i.e. why would you ever not want to use a pointer in these instances?)

MODIFIED CIRCULAR VERSION:

//define the queue for our BFS algo as frontier.
type frontier struct {
	queue                []*graphNode
	size, head, capacity int
}

func (F *frontier) buildFrontier() {
	F.capacity = 1
	F.queue = make([]*graphNode, F.capacity)
	F.size = 0
	F.head = 0
}

func (F *frontier) pop() (g *graphNode) {
	if F.isEmpty() {
		panic("pop on empty queue!")
	}
	F.size = (F.size - 1)
	temp := F.queue[F.head]
	F.head = (F.head + 1) % F.capacity
	return temp
}

func (F *frontier) push(g *graphNode) {
	if F.isFull() {
		newSlice := make([]*graphNode, F.capacity*2)
		copy(newSlice, F.queue)
		F.queue = newSlice
		F.capacity *= 2
	}
	F.queue[(F.head+F.size)%F.capacity] = g
	F.size = (F.size + 1)
}

func (F *frontier) isEmpty() bool {
	return F.size == 0
}
func (F *frontier) isFull() bool {
	return F.size == F.capacity
}

答案1

得分: 1

  1. 看起来你的实现方法是可行的,但效率可能会稍低。在开始时,queue 是一个容量为1000的切片。这意味着底层数组可以容纳1000个元素。每次调用 pop 时,你都会将队列的开头元素向下移动一个位置,从而将容量减少1。最终,当你调用 push 时,容量可能不足以容纳新的元素,即使队列中可能没有太多(或没有)元素。这将导致在调用 append 时分配一个新的数组。无论 pushpop 调用的模式如何,数组都将被重复重新分配,即使它从未容纳过接近1000个元素。我建议你在这里使用链表,可以使用 list 包中的链表,或者设计自己的链表。

  2. 如果函数将修改接收器的值,或者接收器对象在其他地方被使用且需要共享值,那么接收器需要是一个指针。否则,它不需要是一个指针。如果值很大,你可能还是希望将接收器设为指针,这样就不需要复制它。(接收器的行为类似于任何其他函数参数,相同的考虑因素适用。)

英文:
  1. It looks like your implementation will work, but it will end up being a bit inefficient. At the beginning, queue is a slice with capacity 1000. That means that the underlying array can hold 1000 elements. Every time you call pop, you're moving the beginning of the queue one element further down that array, thus reducing the capacity by 1. Eventually, the capacity will be insufficient to hold a new element when you call push, even though there may not be many (or any) elements in the queue. That will cause a new array to be allocated when append is called. No matter what the pattern of push and pop calls is, the array will be repeatedly reallocated, even if it never holds anywhere near 1000 elements. I would suggest using a linked list for this, either the one in the list package or one of your own design.

  2. The receiver needs to be a pointer if the function will modify the receiver's value or if the receiver object is used in other places and the value must be shared. Otherwise, it doesn't need to be a pointer. You may want to make the receiver a pointer anyway if the value is large so that it doesn't have to be copied. (The receiver behaves like any other function parameter and the same considerations apply.)

huangapple
  • 本文由 发表于 2017年4月13日 22:43:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/43395303.html
匿名

发表评论

匿名网友

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

确定