web-dev-qa-db-ja.com

キューの実装はありますか?

誰もがシンプルで高速なFIF /キュー用のGoコンテナを提案できますか。Goには3つの異なるコンテナがあります:heaplistvector。キューの実装に適しているのはどれですか?

47
rev

Vectorまたはlistのいずれかが機能するはずですが、おそらくvectorがその方法です。これは、ベクターはおそらくリストよりも頻繁に割り当てられず、ガベージコレクション(現在のGo実装では)がかなり高いためです。ただし、小さなプログラムではおそらく問題になりません。

13
Evan Shaw

実際、基本的で使いやすいfifoキューが必要な場合、スライスは必要なものすべてを提供します。

queue := make([]int, 0)
// Push to the queue
queue = append(queue, 1)
// Top (just get next element, don't remove it)
x = queue[0]
// Discard top element
queue = queue[1:]
// Is empty ?
if len(queue) == 0 {
    fmt.Println("Queue is empty !")
}

もちろん、appendとsliceingの内部実装を信頼して、無駄なサイズ変更と再割り当てを回避できると仮定します。基本的な使い方では、これで十分です。

66
Marwan Burelle

バッファリングされたチャンネルをまだ誰も提案していないことに驚いたので、サイズ制限FIFO=キュー.

//Or however many you might need + buffer.
c := make(chan int, 300)

//Push
c <- value

//Pop
x <- c
43
saarrrr

実装側で拡張するには、 Moraes で提案します his Gist キューとスタックからの構造体:

// Stack is a basic LIFO stack that resizes as needed.
type Stack struct {
    nodes   []*Node
    count   int
}
// Queue is a basic FIFO queue based on a circular list that resizes as needed.
type Queue struct {
    nodes   []*Node
    head    int
    tail    int
    count   int
}

この playgroundの例 で実際に動作を確認できます。

7
VonC

ほとんどのキュー実装は、スライスベース、リンクリストベース、および循環バッファ(リングバッファ)ベースの3つのフレーバーのいずれかです。

  • スライスベースのキューは、以前に削除されたアイテムが占有していたメモリを再利用しないため、メモリを浪費する傾向があります。また、スライスベースのキューは、シングルエンドのみになる傾向があります。
  • リンクリストキューは、メモリreuseについては優れていますが、リンクを維持するためのオーバーヘッドのために、一般的に少し遅くなり、全体的に多くのメモリを使用します。メモリを移動することなく、キューの中央からアイテムを追加および削除する機能を提供できますが、その多くを行っている場合、キューは間違ったデータ構造です。
  • リングバッファキューは、メモリを無駄にしないという利点とともに、スライスのすべての効率を提供します。割り当てが少ないほど、パフォーマンスが向上します。それらはどちらの端からでもアイテムを追加および削除するのと同じくらい効率的であるため、自然に両端キューを取得できます。したがって、一般的な推奨事項として、リングバッファベースのキュー実装を推奨します。これは、この投稿の残りの部分で説明されています。

リングバッファベースのキューは、ストレージをラップしてメモリを再利用します。キューが基礎となるスライスの一端を超えて大きくなると、スライスの他端にノードを追加します。 deque diagram を参照してください

また、説明するための少しのコード:

// PushBack appends an element to the back of the queue.  Implements FIFO when
// elements are removed with PopFront(), and LIFO when elements are removed
// with PopBack().
func (q *Deque) PushBack(elem interface{}) {
    q.growIfFull()
    q.buf[q.tail] = elem
    // Calculate new tail position.
    q.tail = q.next(q.tail)
    q.count++
}

// next returns the next buffer position wrapping around buffer.
func (q *Deque) next(i int) int {
    return (i + 1) & (len(q.buf) - 1) // bitwise modulus
}

この 特定の実装 は、常に2のべき乗のバッファサイズを使用するため、ビット単位のモジュラスを計算して、少しだけ効率的にすることができます。

これは、すべての容量が使い果たされた場合にのみスライスを拡張する必要があることを意味します。同じ境界でのストレージの成長と縮小を回避するサイズ変更戦略により、これは非常にメモリ効率が良くなります。

基になるスライスバッファーのサイズを変更するコードは次のとおりです。

// resize resizes the deque to fit exactly twice its current contents. This is
// used to grow the queue when it is full, and also to shrink it when it is     
// only a quarter full.                                                         
func (q *Deque) resize() {
    newBuf := make([]interface{}, q.count<<1)
    if q.tail > q.head {
        copy(newBuf, q.buf[q.head:q.tail])
    } else {
        n := copy(newBuf, q.buf[q.head:])
        copy(newBuf[n:], q.buf[:q.tail])
    }
    q.head = 0
    q.tail = q.count
    q.buf = newBuf
}

考慮すべきもう1つのことは、実装に並行性の安全性を組み込みたい場合です。これを避けて、並行性戦略に最適な方法を実行できるようにしたい場合があります。また、必要ない場合は絶対に望まないでしょう。何らかのシリアル化が組み込まれたスライスが必要ないのと同じ理由です。

Dequeの godoc で検索を行う場合、Goのリングバッファベースのキューの実装がいくつかあります。あなたの好みに最適なものを選択してください。

4
gammazero

スライスと適切な(「円形」)インデックススキームを一番上に使用することは、まだ方法のようです。これについての私の見解は次のとおりです: https://github.com/phf/go-queue より制限された機能の価格。

4
Peter Froehlich

残念ながら、キューは現在go標準ライブラリの一部ではないため、独自のソリューションを作成するか、他の誰かのソリューションをインポートする必要があります。標準ライブラリの外部で記述されたコンテナはジェネリックを使用できないため、これは残念です。

固定容量キューの簡単な例は次のとおりです。

type MyQueueElement struct {
  blah int // whatever you want
}

const MAX_QUEUE_SIZE = 16
type Queue struct {
  content  [MAX_QUEUE_SIZE]MyQueueElement
  readHead int
  writeHead int
  len int
}

func (q *Queue) Push(e MyQueueElement) bool {
  if q.len >= MAX_QUEUE_SIZE {
    return false
  }
  q.content[q.writeHead] = e
  q.writeHead = (q.writeHead + 1) % MAX_QUEUE_SIZE
  q.len++
  return true
}

func (q *Queue) Pop() (MyQueueElement, bool) {
  if q.len <= 0 {
    return MyQueueElement{}, false
  }
  result := q.content[q.readHead]
  q.content[q.readHead] = MyQueueElement{}
  q.readHead = (q.readHead + 1) % MAX_QUEUE_SIZE
  q.len--
  return result, true
}

ここで避けられる落とし穴には、無制限のスライス成長がないこと(スライス[1:]操作を使用して破棄することにより)、ポップされた要素をゼロ化して、それらのコンテンツがガベージコレクションに利用できるようにすることが含まれます。ここでのようなintのみを含むMyQueueElement構造体の場合、違いはありませんが、structにポインターが含まれている場合は違います。

自動拡張キューが必要な場合は、ソリューションを拡張して再割り当てとコピーを行うことができます。

このソリューションはスレッドセーフではありませんが、必要に応じてプッシュ/ポップにロックを追加できます。

遊び場 https://play.golang.org/

2
tul

"container/list"を単純な実装内に埋め込み、インターフェースを公開するだけです

package queue

import "container/list"

// Queue is a queue
type Queue interface {
    Front() *list.Element
    Len() int
    Add(interface{})
    Remove()
}

type queueImpl struct {
    *list.List
}

func (q *queueImpl) Add(v interface{}) {
    q.PushBack(v)
}

func (q *queueImpl) Remove() {
    e := q.Front()
    q.List.Remove(e)
}

// New is a new instance of a Queue
func New() Queue {
    return &queueImpl{list.New()}
}
2
David Rz Ayala

上記のようにスライスからキューも実装します。ただし、スレッドセーフではありません。そこで、スレッドセーフを保証するためにロック(ミューテックスロック)を追加することにしました。

package queue

import (
  "sync"
)

type Queue struct {
  lock *sync.Mutex
  Values []int
}

func Init() Queue {
  return Queue{&sync.Mutex{}, make([]int, 0)}
}

func (q *Queue) Enqueue(x int) {
  for {
    q.lock.Lock()
    q.Values = append(q.Values, x)
    q.lock.Unlock()
    return
  }
}

func (q *Queue) Dequeue() *int {
  for {
    if (len(q.Values) > 0) {
      q.lock.Lock()
      x := q.Values[0]
      q.Values = q.Values[1:]
      q.lock.Unlock()
      return &x
    }
    return nil
  }
  return nil
}

あなたはここでgithubで私のソリューションを確認できます シンプルキュー

1
Dat Tran

基礎となるバッファーを自動的に拡張するキューを実装しました。

package types

// Note: this queue does not shrink the underlying buffer.                                                                                                               
type queue struct {
        buf  [][4]int // change to the element data type that you need                                                                                                   
        head int
        tail int
}

func (q *queue) extend(need int) {
        if need-(len(q.buf)-q.head) > 0 {
                if need-len(q.buf) <= 0 {
                        copy(q.buf, q.buf[q.head:q.tail])
            q.tail = q.tail - q.head
                        q.head = 0
                        return
                }

                newSize := len(q.buf) * 2
                if newSize == 0 {
                    newSize = 100
            }
                newBuf := make([][4]int, newSize)
                copy(newBuf, q.buf[q.head:q.tail])
                q.buf = newBuf
        q.tail = q.tail - q.head
                q.head = 0
        }
}

func (q *queue) Push(p [4]int) {
        q.extend(q.tail + 1)
        q.buf[q.tail] = p
        q.tail++
}

func (q *queue) pop() [4]int {
        r := q.buf[q.head]
        q.head++
        return r
}

func (q *queue) size() int {
        return q.tail - q.head
}


// put the following into queue_test.go
package types

import (
        "testing"

        "github.com/stretchr/testify/assert"
)

func TestQueue(t *testing.T) {
        const total = 1000
        q := &queue{}
        for i := 0; i < total; i++ {
                q.Push([4]int{i, i, i, i})
                assert.Equal(t, i+1, q.size())
        }

    for i := 0; i < total; i++ {
                v := q.pop()
                assert.Equal(t, [4]int{i, i, i, i}, v)
                assert.Equal(t, total-1-i, q.size())
        }
}
0
Helin Wang

ダブルスタックの実装:

O(1)EnqueueおよびDequeueを使用し、slicesを使用します(キャッシュミスの方が良い傾向があります)。

type Queue struct{
    enqueue, dequeue Stack
}

func (q *Queue) Enqueue(n *Thing){
    q.enqueue.Push(n)
}

func (q *Queue) Dequeue()(*Thing, bool){
    v, ok := q.dequeue.Pop()
    if ok{
        return v, true
    }

    for {
        v, ok := d.enqueue.Pop()
        if !ok{
            break
        }

        d.dequeue.Push(v)
    }

    return d.dequeue.Pop()
}

type Stack struct{
    v []*Thing
}

func (s *Stack)Push(n *Thing){
    s.v=append(s.v, n)
}

func (s *Stack) Pop()(*Thing, bool){
    if len(s.v) == 0 {
        return nil, false
    }

    lastIdx := len(s.v)-1
    v := s.v[lastIdx]
    s.v=s.v[:lastIdx]
    return v, true
}
0
poy