いろいろ備忘録日記

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

Goメモ-86 (チャネルの要素に指定された処理を適用した結果を返す関数, Map)

概要

引き続き、小ネタチャネル関数の続き。( #関連記事 参照)

python には、mapというビルドイン関数があります。

こんな感じ。

$ python3
Python 3.7.7 (default, Mar 25 2020, 10:55:55) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def fn(x:int) -> int:
...     return x**2
... 
>>> l = [1,2,3]
>>> print(*map(fn, l))
1 4 9

チャネルからの要素に対しても同じようにしたいときがあります。

なので、ちょっとサンプル作ってみました。

サンプル

package chans

type (
    // MapFunc -- chans.Map にて利用されるチャネルの各要素に適用する関数です。
    MapFunc func(interface{}) interface{}

    // MapValue -- chans.Map にて利用されるデータ型です。
    MapValue struct {
        Before interface{} // 元の値
        After  interface{} // 適用後の値
    }
)

func newMapValue(before, after interface{}) *MapValue {
    return &MapValue{
        Before: before,
        After:  after,
    }
}

// Map -- 関数 fn を入力チャネル in の各要素に適用した結果を返すチャネルを生成します。
//
// 戻り値のチャネルから取得できるデータ型は、*chans.MapValue となっています。
//
//         for m := range chans.Map(done, inCh, fn) {
//             if v, ok := m.(*chans.MapValue); ok {
//                 // v.Before で元の値、 v.After で適用後の値が取得できる
//             }
//         }
//
func Map(done <-chan struct{}, in <-chan interface{}, fn MapFunc) <-chan interface{} {
    out := make(chan interface{})

    go func() {
        defer close(out)

    ChLoop:
        for {
            select {
            case <-done:
                break ChLoop
            case v, ok := <-in:
                if !ok {
                    break ChLoop
                }

                before := v
                after := fn(v)

                select {
                case out <- newMapValue(before, after):
                case <-done:
                }
            }
        }
    }()

    return out
}

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

以下テストコードです。

package chans

import (
    "reflect"
    "strings"
    "testing"
)

func TestMap(t *testing.T) {
    type (
        testin struct {
            input []interface{}
            fn    MapFunc
        }
        testout struct {
            result []interface{}
        }
        testcase struct {
            in  testin
            out testout
        }
    )

    cases := []testcase{
        {
            in: testin{
                input: []interface{}{"hello", "world"},
                fn: func(v interface{}) interface{} {
                    return strings.ToUpper(v.(string))
                },
            },
            out: testout{result: []interface{}{"HELLO", "WORLD"}},
        },
    }

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

            results := make([]interface{}, 0, 0)
            for m := range Map(done, ForEach(done, c.in.input...), c.in.fn) {

                if v, ok := m.(*MapValue); ok {
                    t.Logf("[test-%02d] [%v] ==> [%v]", caseIndex, v.Before, v.After)
                    results = append(results, v.After)
                }
            }

            if !reflect.DeepEqual(c.out.result, results) {
                t.Errorf("want: %v\tgot: %v", c.out.result, results)
            }
        }()
    }
}

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

$ go test -v github.com/devlights/gomy/chans -run ^TestMap.*$
=== RUN   TestMap
    TestMap: map_test.go:45: [test-00] [hello] ==> [HELLO]
    TestMap: map_test.go:45: [test-00] [world] ==> [WORLD]
--- PASS: TestMap (0.00s)
PASS
ok      github.com/devlights/gomy/chans 0.004s

参考

Go言語による並行処理

Go言語による並行処理

関連記事

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com


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

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

devlights.github.io

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

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

github.com

github.com

github.com