我如何(简洁地)从Go中的切片中删除第一个元素?

mod*_*che 22 queue go

我在Go中构建了一个简单的队列.它使用内部切片来跟踪其元素.通过附加到切片将元素推送到队列中.我想.Pop()通过删除第一个元素来实现elements.

在许多其他语言中,"弹出"列表的第一个元素是一个单行,这使我相信我的实现在下面是草率和冗长.有没有更好的办法?

type Queue struct {
    elements []interface{}
}

func (queue *Queue) Push(element interface{}) {
    queue.elements = append(queue.elements, element)
}

func (queue *Queue) Pop() interface{} {
    element := queue.elements[0]
    if len(queue.elements) > 1 {
        queue.elements = queue.elements[1:]
    } else {
        queue.elements = make([]interface{}, 0)
    }
    return element
}
Run Code Online (Sandbox Code Playgroud)

请注意,我希望Queue如果恐慌len(queue.elements) == 0.我不检查边界不是疏忽.

Eve*_*ton 52

你试过这些吗?

从队列弹出

x, a = a[0], a[1:]
Run Code Online (Sandbox Code Playgroud)

从堆栈弹出

x, a = a[len(a)-1], a[:len(a)-1]
Run Code Online (Sandbox Code Playgroud)

a = append(a, x)
Run Code Online (Sandbox Code Playgroud)

来自:https://code.google.com/p/go-wiki/wiki/SliceTricks

  • 孩子,我脸红了吗?我读了那篇文章,并以为我已经尝试过“从队列中弹出”示例,但我再次尝试,结果如我所愿。谢谢!(并且很抱歉这个愚蠢的问题。) (2认同)
  • 我认为这种方法的主要问题是每个队列插入都有潜在的分配 - [playground example](http://play.golang.org/p/wMl9LTNqs-) (2认同)
  • 我将基于环形缓冲区的队列提取到其自己的包中(位于 https://github.com/eapache/queue),以便其他人如果想要使用它就可以导入它。 (2认同)

Nic*_*ood 8

如果您需要环形缓冲区或 FIFO 结构,那么在@Everton 的回答中使用切片会导致垃圾收集问题,因为底层数组可能会无限增长。

如果您不介意限制大小,那么在 go 中执行此操作的最简单方法是使用对于并发访问也是安全的通道。这是 go 中的一个常见习语,您通常不会费心将其包装成如下所示的类型。

例如(游乐场

package main

import "fmt"

type Queue struct {
    elements chan interface{}
}

func NewQueue(size int) *Queue {
    return &Queue{
        elements: make(chan interface{}, size),
    }
}

func (queue *Queue) Push(element interface{}) {
    select {
    case queue.elements <- element:
    default:
        panic("Queue full")
    }
}

func (queue *Queue) Pop() interface{} {
    select {
    case e := <-queue.elements:
        return e
    default:
        panic("Queue empty")
    }
    return nil
}

func main() {
    q := NewQueue(128)

    q.Push(1)
    q.Push(2)
    q.Push(3)
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())
    fmt.Printf("Pop %d\n", q.Pop())

}
Run Code Online (Sandbox Code Playgroud)

  • 通道恰好提供了一个有用且高效的工具来制作这样的环形缓冲区。正如 CARHoare 最初设想的那样,通道旨在*在*进程(goroutines)之间使用;这种模式相当不同。请注意,如果通道缓冲区已满,并且唯一的消耗线程(调用“Pop”)也尝试调用“Push”,则可能会发生**死锁**。@Everton 的方法没有这个缺点;垃圾收集问题可以通过不同的方式解决。 (2认同)
  • @Rick-777 使用 `Push` 方法中的另一个 `select` 可以轻松解决死锁。 (2认同)
  • @Rick-777 更新了答案以展示如何检测队列已满。 (2认同)
  • @modocache 我会说,如果您在 go 例程之间进行通信,只需使用通道 - 这就是它们的用途。您不需要队列为空或队列满检测 - 运行时会自动为您暂停和取消暂停 goroutine。否则,您可以使用此类型安全但有界的解决方案,或者您可以使用 Evan 提到的没有任何分配或垃圾收集问题的代码。 (2认同)