13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Golang】コアラのように怠惰でチンパンジーのように賢い、高速配列操作ライブラリ「Koazee」使ってみた

Last updated at Posted at 2019-08-14

image.png

今回はGolangの配列操作ライブラリ「Koazee」について紹介したいと思います。
「そもそもKoazeeとは?お前C#一筋ちゃうんか」すみません。

公式ドキュメントによると、
"Lazy like a Koala, smart like a chimpanzee" (コアラのように怠惰でチンパンジーのように賢い)
ライブラリです。Koa(ra) (chimpan)zee という意味みたいですね。

このライブラリ、とてつもなく速いらしいです。
作者によるMedium.comの投稿からベンチマークを引用すると、
他のGoの配列操作ライブラリと比べて以下の画像の通りの速度差があるとのこと。マユツバ。
image.png

Koazeeの特徴は以下の通り。

  • Immutable: Koazee won't modify your inputs. 
  • StreamLike: We can combine operations up our convenience. 
  • Lazy loading: Operations are not performed until they're required
  • Generic: Koazee provides a generic interface able to deal with slice of any type without creating custom functions.
  • Focusing on performance: First rule for implementing a new operation is providing the best possible performance.

個人的に惹かれたのは ImmutableLazy loading。ほら、C#erならわかるでしょ(?)。
ではさっそくこのKoazeeを使っていきたいと思います。

1. Golang 環境構築(Linux編)

CentOS 7にyumでGoを入れてHello Worldするまでを参考にさせて頂き、環境構築を進めていきます。
「Goもう入ってるよ!」という方は 2番 へGOGO!!

環境

  • CentOS7:latest

Golang インストール

epelリポジトリをインストールし、golangをインストールする。

# yum install epel-release
# yum install -y golang

テンプレートディレクトリを作成する。

# mkdir /usr/local/go
# cd /usr/local/go/
# mkdir bin pkg src

インストール完了後、以下のコマンドでバージョンを表示し、インストールされていることを確認します。

# go version
go version go1.11.5 linux/amd64

パスの設定

パスを通していきます。bash_profileを以下の通り編集。

# vi ~/.bash_profile
~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

GOPATH=/usr/local/go
PATH=$GOPATH/bin:$PATH:$HOME/bin

export PATH
export GOPATH 

変更したファイルを適用し、GOPATH, PATH それぞれ正常に出力されるか確認します。

# source ~/.bash_profile

# echo $GOPATH
/usr/local/go

# echo $PATH
/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

確認ができたらインストールは完了!

1. Golang 環境構築(Windows編)

では続いてGolangのインストールを行います。

「えっ!? 今Linuxにインストールしたよね!? 痴呆か?」 と思われたアナタ。
大丈夫です。まだボケてはいません。

今回作業中に帰省してしまったので、手元のWindows10にも環境構築しました。
せっかくなのでWindowsのインストール方法を以下の通りまとめておきます。
Linuxで開発を行われる方はこの手順はもちろん不要です。

環境

  • Windows10 Home

インストーラのダウンロード

公式ページにアクセスし、以下の赤枠のリンクからインストーラをダウンロードする。
https://golang.org/dl/

image.png

Golang インストール

ダウンロード後、起動してナビゲーションの通り進めていくとインストールが完了します。
デフォルトだとCドライブ直下にインストールされるようなので、Cを汚染されたくない場合は変更していいと思われ。
image.png

インストール完了後、コンソールでバージョンを表示し、インストールされていることを確認します。
インストーラでパスの設定まで行われているようですので、別途設定する必要はありません。
image.png

これでWindowsへのGoのインストールは完了!
では引き続きKoazeeのインストールと使用に進んでいきます。

2. Koazeeのインストール

https://github.com/wesovilabs/koazeeからライブラリを取得します。
出力は特に吐かれません。ワイだけ?

# go get github.com/wesovilabs/koazee

使用する際の参照方法はこんな感じ。

sample.go
package main

import (
	"github.com/wesovilabs/koazee"
)

早速公式のサンプルコードを実行してみましょう!

3. Koazeeのサンプルプログラム

以下、公式ドキュメントより転載したコードにコメントをつけています。

実行サンプル1 - 指定した要素を取り出す

02_at_first_last.go
package main

import (
	"fmt"
	"github.com/wesovilabs/koazee"
)

var numbers = []int{1, 5, 4, 3, 2, 7, 1, 8, 2, 3}

func main() {
    // koazee.StreamOfメソッドを使用して配列をKoazee形式で格納する。
	stream := koazee.StreamOf(numbers)

    // koazee形式で配列が格納されたstreamオブジェクトのメソッドをコールし、値を出力する。

    // 指定されたインデックスの要素を取得
	fmt.Printf("stream.At(4): %d\n", stream.At(4).Int())

    // 最初の要素を取得
	fmt.Printf("stream.First: %d\n", stream.First().Int())

    // 最後の要素を取得
	fmt.Printf("stream.Last: %d\n", stream.Last().Int())
}

/** output
go run main.go

stream.At(4): 2
stream.First: 1
stream.Last: 3
*/

わかりやすいですね!

実行サンプル2 - 要素の追加・削除

要素の追加・削除も以下の通りおちゃのこさいさい。

03_add_delete.go
package main

import (
	"fmt"
	"github.com/wesovilabs/koazee"
)

var numbers = []int{1, 5, 4, 3, 2, 7, 1, 8, 2, 3}

func main() {
	fmt.Printf("input: %v\n", numbers)

	stream := koazee.StreamOf(numbers)

    // 要素の追加
	fmt.Print("stream.Add(10): ")
	fmt.Println(stream.Add(10).Do().Out().Val())
    
    // 要素の削除
	fmt.Print("stream.Drop(5): ")
	fmt.Println(stream.Drop(5).Do().Out().Val())

    // 範囲を指定して要素の削除	
	fmt.Print("stream.DropWhile(val<=5): ")
	fmt.Println(stream.DropWhile(func(element int)bool{return element<=5}).Do().Out().Val())

    // インデックスを指定して要素の削除
	fmt.Print("stream.DeleteAt(4): ")
	fmt.Println(stream.DeleteAt(4).Do().Out().Val())

    // インデックスを指定して要素の値の変更(0番目のインデックスの要素を5に書き換え)
	fmt.Print("stream.Set(0,5): ")
	fmt.Println(stream.Set(0, 5).Do().Out().Val())

    // 最初の要素を抽出し、自身と新しいストリームを生成する。(output参照)
	fmt.Print("stream.Pop(): ")
	val, newStream := stream.Pop()
	fmt.Printf("%d ... ", val.Int())
	fmt.Println(newStream.Out().Val())

}

/** output
go run main.go

input: [1 5 4 3 2 7 1 8 2 3]
stream.Add(10): [1 5 4 3 2 7 1 8 2 3 10]
stream.Drop(5): [1 4 3 2 7 1 8 2 3]
stream.DropWhile(val<=5): [7 8]
stream.DeleteAt(4): [1 5 4 3 7 1 8 2 3]
stream.Set(0,5): [5 5 4 3 2 7 1 8 2 3]
stream.Pop(): 1 ... [5 4 3 2 7 1 8 2 3]
*/

実行サンプル3 - 要素の抽出

06_take_filter_duplicate.go
package main

import (
	"fmt"
	"github.com/wesovilabs/koazee"
)

var animals = []string{"lynx", "dog", "cat", "monkey", "dog", "fox", "tiger", "lion"}

func main() {
	fmt.Print("input: ")
	fmt.Println(animals)
	stream := koazee.StreamOf(animals)

    // 範囲を指定して要素を抽出
	fmt.Print("stream.Take(1,4): ")
	fmt.Println(stream.Take(1, 4).Out().Val())

    // 条件を指定して要素を抽出
	fmt.Print("stream.Filter(len==4): ")
	fmt.Println(stream.
		Filter(
			func(val string) bool {
				return len(val) == 4
			}).
		Out().Val(),
	)
    // 重複する要素を省く
	fmt.Print("stream.RemoveDuplicates(): ")
	fmt.Println(stream.RemoveDuplicates().Out().Val())
}

/**
go run main.go

input: [lynx dog cat monkey dog fox tiger lion]
stream.Take(1,4): [dog cat monkey dog]
stream.Filter(len==4): [lynx lion]
stream.RemoveDuplicates(): [lynx dog cat monkey fox tiger lion]
*/

実行サンプル4 - 要素の集計

07_groupby.go
package main

import (
	"fmt"
	"github.com/wesovilabs/koazee"
	"strings"
)

var animals = []string{"lynx", "dog", "cat", "monkey", "dog", "fox", "tiger", "lion"}

func main() {
	fmt.Printf("input: %v\n", animals)
	stream := koazee.StreamOf(animals)
	fmt.Print("stream.GroupBy(strings.Len): ")

    // 文字列の長さでグルーピングする
	out, _ := stream.GroupBy(func(val string)int{return len(val)})
	fmt.Println(out)
}

/**
go run main.go

input: [lynx dog cat monkey dog fox tiger lion]

// 文字列の長さごとにグルーピングされて出力される
stream.GroupBy(strings.Len): map[5:[tiger] 4:[lynx lion] 3:[dog cat dog fox] 6:[monkey]]
*/

そのほかのメソッドについてはWikiに詳細な解説があるので公式ドキュメントと合わせてご活用ください。

実行サンプル5 - 遅延実行(だいじ)

さて、最後にkoazeeで自分が一番注目した 遅延実行 について紹介します。
以下のコードをご覧ください。

11_lazyEvaluate.go
package main

import (
	"fmt"
	"github.com/wesovilabs/koazee"
	"strings"
)

type Person struct {
	Name string
	Male bool
	Age  int
}

var people = []*Person{
	{"John Smith", true, 32},
	{"Peter Pan", true, 17},
	{"Jane Doe", false, 20},
	{"Anna Wallace", false, 35},
	{"Tim O'Brian", true, 13},
	{"Celia Hills", false, 15},
}

func main() {
    // 渡された配列から女性だけを抽出してソートしてForeachで出力する処理を格納する
	stream := koazee.
		StreamOf(people).
		Filter(func(person *Person) bool {
			return !person.Male
		}).
		Sort(func(person, otherPerson *Person) int {
			return strings.Compare(person.Name, otherPerson.Name)
		}).
		ForEach(func(person *Person) {
			fmt.Printf("%s is %d years old\n", person.Name, person.Age)
		})

    // 実行するまで式は評価されないぜ。
	fmt.Println("Operations are not evaluated until we perform stream.Do()\n")

    // 実行と同時に評価される。
	stream.Do()
}

/** output
go run main.go

// 実行するまで式は評価されてないぜ。
Operations are not evaluated until we perform stream.Do()

// stream.Do() の実行時に格納された式が評価され、出力される。
Anna Wallace is 35 years old
Celia Hills is 15 years old
Jane Doe is 20 years old
 */

いかがでしょうか?(NAVER風(前回やった))
拙いコメントでの説明となってしまったので、順を追って説明します。

まずは以下の個所で、式をstreamオブジェクトに格納 します。
(厳密には式を格納という表現もおかしいですが、ひとまずはその理解で大丈夫です。)
みなさんよくご存じの即時実行関数ではないので、ここでは出力は行われません。

	stream := koazee.
		StreamOf(people).
		Filter(func(person *Person) bool {
			return !person.Male
		}).
		Sort(func(person, otherPerson *Person) int {
			return strings.Compare(person.Name, otherPerson.Name)
		}).
		ForEach(func(person *Person) {
			fmt.Printf("%s is %d years old\n", person.Name, person.Age)
		})

式の格納後、次の行で出力されたメッセージ Operations are ~ がコンソール出力されます。
そして stream.Do() が実行されたタイミングで初めて式が評価され、値が確定します。

    // 実行するまで式は評価されないぜ。
	fmt.Println("Operations are not evaluated until we perform stream.Do()\n")

    // 実行と同時に評価される。ここで結果がコンソール出力される!
	stream.Do()

遅延実行の何が嬉しいかというと、遅延により計算の全体像をとらえた上で演算処理が実行されるため、余計な計算を行わずに済む という点です。
C#erならばLinq、Scalaer(Scara+er?)ならばStreamの概念と同じですね。
遅延評価についてはまた別記事を作成したいと思いますので、「は?」という方はもうしばらくお待ちくださいませ。。。

4. まとめ

いかがでしょうか?
本稿で使用した資料を以下の通りまとめておきましたので、ぜひご参照ください。

  • GitHub ... Koazee公式GitHub。今回使用したサンプルはすべて載っている。
  • Wiki ... Koazee公式Wiki
  • Medium ... 作者による他配列操作ライブラリとのベンチマーク。

このライブラリを使用すれば既存のGolangコードに対して、更なる高速化アプローチが可能になりますね!
ご質問等あればお気軽にコメントくださいませ~。

5. 追記:サンプルコード

簡単な時間計測用サンプルコードを作ってみました。
自由に改変していろいろ触ってみてください。

koazeeFilter.go
package main

import (
	"fmt"
	"math/rand"
	"time"

	"github.com/wesovilabs/koazee"
)

var numbers = []int{}

func main() {

	maxValue := 100
	dummyCount := 5 * 10000000

	fmt.Printf("1. 0~100までのランダムな数値%d件を生成。\n", dummyCount)
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < dummyCount; i++ {
		numbers = append(numbers, rand.Intn(maxValue))
	}

	fmt.Printf("2. %d件生成完了。Koazeeに読み込み。\n", dummyCount)
	stream := koazee.StreamOf(numbers)

	/*
		このコメントアウトを外したら一覧が出力できます。超重いよ。
		fmt.Print("Koazeeに読み込み完了。一覧はコチラ ↓ \n")
		fmt.Println(stream.Out().Val())
		fmt.Println("\n")
	*/

	start := time.Now() // filter start

    // 値が10の要素だけを抽出してカウント
	count, _ := stream.Filter(func(value int) bool {
		return value == 10
	}).Count()

	end := time.Now() // filter end

	fmt.Printf("値が10の要素だけを%f秒で抽出しました。\n", (end.Sub(start)).Seconds())
	fmt.Printf("対象件数は %d 件です。\n", count)
}

/**
go run koazeeFilter.go

1. 0~100までのランダムな数値50000000件を生成。
2. 50000000件生成完了。Koazeeに読み込み。
値が10のものだけを0.179520秒で抽出しました。
対象件数は 499951 件です。
*/

PCスペックにもよると思いますが、0.2秒かからないですね。Koazeeサイコー!!

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?