いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

Goメモ-69 (指定された値を繰り返すチャネル, Repeat)

概要

チャネル関数の続き。

今回は、指定された値を延々と繰り返してくれるチャネルを返す関数です。C# の Enumerable.Repeat チックな感じ。

サンプル

package chans

// Repeat -- 指定した値を永遠と繰り返すチャネルを返します。
func Repeat(done <-chan struct{}, values ...interface{}) <-chan interface{} {
    out := make(chan interface{})

    go func() {
        defer close(out)

        for {
            for _, v := range values {
                select {
                case <-done:
                    return
                case out <- v:
                }
            }
        }
    }()

    return out
}

// RepeatFn -- 指定した関数を永遠と繰り返し、その戻り値を返すチャネルを返します。
func RepeatFn(done <-chan struct{}, fn func() interface{}) <-chan interface{} {
    out := make(chan interface{})

    go func() {
        defer close(out)

        for {
            select {
            case <-done:
                return
            case out <- fn():
            }
        }
    }()

    return out
}

gomy/repeat.go at master · devlights/gomy · GitHub

以下、テストコードです。

package chans

import (
    "testing"
)

func TestRepeat(t *testing.T) {
    type (
        testin struct {
            data []interface{}
        }
        testout struct {
            value []interface{}
        }
        testcase struct {
            in  testin
            out testout
        }
    )

    cases := []testcase{
        {
            in: testin{
                data: []interface{}{1},
            },
            out: testout{
                value: []interface{}{1, 1, 1},
            },
        },
        {
            in: testin{
                data: []interface{}{1, 2, 3},
            },
            out: testout{
                value: []interface{}{1, 2, 3},
            },
        },
    }

    for index, c := range cases {
        func() {
            done := make(chan struct{})
            defer close(done)

            repeatCh := Repeat(done, c.in.data...)

            result1 := <-repeatCh
            result2 := <-repeatCh
            result3 := <-repeatCh

            if result1 != c.out.value[0] {
                t.Errorf("[testcase-%02d][result1] want: %v\tgot: %v", index+1, c.out.value[0], result1)
            }

            if result2 != c.out.value[1] {
                t.Errorf("[testcase-%02d][result2] want: %v\tgot: %v", index+1, c.out.value[1], result1)
            }

            if result3 != c.out.value[2] {
                t.Errorf("[testcase-%02d][result3] want: %v\tgot: %v", index+1, c.out.value[2], result1)
            }
        }()
    }
}

func TestRepeatFn(t *testing.T) {
    type (
        testin struct {
            fn func() interface{}
        }
        testout struct {
            value []interface{}
        }
        testcase struct {
            in  testin
            out testout
        }
    )

    cases := []testcase{
        {
            in: testin{
                fn: func() interface{} {
                    return "helloworld"
                },
            },
            out: testout{
                value: []interface{}{"helloworld", "helloworld", "helloworld"},
            },
        },
    }

    for index, c := range cases {
        func() {
            done := make(chan struct{})
            defer close(done)

            repeatCh := RepeatFn(done, c.in.fn)

            result1 := <-repeatCh
            result2 := <-repeatCh
            result3 := <-repeatCh

            if result1 != c.out.value[0] {
                t.Errorf("[testcase-%02d][result1] want: %v\tgot: %v", index+1, c.out.value[0], result1)
            }

            if result2 != c.out.value[1] {
                t.Errorf("[testcase-%02d][result2] want: %v\tgot: %v", index+1, c.out.value[1], result1)
            }

            if result3 != c.out.value[2] {
                t.Errorf("[testcase-%02d][result3] want: %v\tgot: %v", index+1, c.out.value[2], result1)
            }
        }()
    }
}

実行すると以下のようになります。

$ go test -v github.com/devlights/gomy/chans -run ^TestRepeat.*$
=== RUN   TestRepeat
--- PASS: TestRepeat (0.00s)
=== RUN   TestRepeatFn
--- PASS: TestRepeatFn (0.00s)
PASS
ok      github.com/devlights/gomy/chans 0.003s

RepeatFn() の方は、乱数などを返す関数を設定したりすると毎回異なる乱数を延々と繰り返すように出来ていいかもしれませんね。

例えば、こんな感じです。

package async

import (
    "context"
    "fmt"
    "math/rand"

    "github.com/devlights/gomy/chans"
)

// TakeFirst10Items -- 最初の10個のみを取得するサンプルです
func TakeFirst10Items() error {
    var (
        rootCtx         = context.Background()
        mainCtx, cancel = context.WithCancel(rootCtx)
    )

    defer cancel()

    // 乱数を返す関数
    randomInt := func() interface{} {
        return rand.Int()
    }

    // 乱数を延々と返すチャネル生成
    repeatCh := chans.RepeatFn(mainCtx.Done(), randomInt)

    // 最初の10件のみ取得するチャネル生成
    takeFirst10ItemCh := chans.Take(mainCtx.Done(), repeatCh, 10)

    // 出力
    for v := range takeFirst10ItemCh {
        fmt.Println(v)
    }

    return nil
}

実行すると以下のようになります。

$ make run
ENTER EXAMPLE NAME: async_take_first_10items
[Name] "async_take_first_10items"
711512294376717777
6597590559487096282
7546803073246591619
2730330101447707632
8913116274994349004
1574111430622025378
963317749379199556
5718127538066206519
3934619028980328702
7294862805965956347


[Elapsed] 2.340763ms

参考

Go言語による並行処理

Go言語による並行処理

関連記事

devlights.hatenablog.com

devlights.hatenablog.com


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com