vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

GolangでFizzBuzzしてみた

はじめに

ひょんなことから(Twitterで見た)4月にGolangの勉強会に参加することになりました。 いくら話を聴くだけとはいえ、何もわからずに参加しても楽しくないよなぁ…。

ということでちょっと触ってみることにしました。

何を作ってみるか?というところなのですが、
実は「プログラミングの基本」のような扱いを受けるFizzBuzzってやったことがなかったので、Golangでやってみることにしました。

念の為FizzBuzzのルールですが、

  1. 1から100までカウントアップする
  2. 3で割り切れる数なら「Fizz」と出力
  3. 5で割り切れる数なら「Buzz」と出力
  4. 3でも5でも割り切れる数なら「FizzBuzz」と出力
  5. それ以外の数はそのまま出力

というルールで進めます。

インストール

まずはGolangのインストールから。

Ubuntuではaptからインストールできるのですが、これは少しバージョンが古い(ver.1.6)です。

最新のver.1.8をインストールするには、下記のような方法がありそうです。

  1. 公式サイトからtar.gzをダウンロードして展開する。
  2. goenvを使う。
  3. リポジトリを追加してapt installする。

ただ、ググった中で今ひとつスタンダードなやり方が定まっていない気がしたため、1のtar.gzを使うことにしました。

インストール方法は下記を参照。

https://golang.org/doc/install

一点、/usr/localにファイルを置く下記の部分で権限エラーになったため、sudoで実行しました。

tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz

「go version」でバージョン情報が表示されたらOKです。

エディタはIntelliJ IDEAと迷いましたが、 Angularで使っていたこともありVisual Studio CodeにGolangプラグインをインストールして使っています。

通常のFizzBuzz

ではさっそくFizzBuzzってみましょう。

まずは至って普通の方法から。

package main

import "fmt"

func main() {
    useFor()
}
func useFor() {
    for i := 1; i <= 100; i++ {
        if i%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if i%3 == 0 {
            fmt.Println("Fizz")
        } else if i%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(i)
        }
    }
}

これを「go run ファイル名」で実行すればOKです。

簡単簡単。。。

}の位置にハマる

orz

✗ 間違い

if i%15 == 0 {
    fmt.Println("FizzBuzz")
}
else if i%3 == 0 {
    fmt.Println("Fizz")
}

○ 正しい

if i%15 == 0 {
    fmt.Println("FizzBuzz")
} else if i%3 == 0 {
    fmt.Println("Fizz")
}

「}」と「else if{」が同じ行にないと、「'syntax error: unexpected else, expecting }‘」とエラーになります。
厳しい…

rangeを使う

さて、For文には配列を作ってrangeを使う方法もあります。

package main

import "fmt"

func main() {
    useRange()
}
func useRange() {
    for _, v := range getNumArray(1, 100) {
        if v%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if v%3 == 0 {
            fmt.Println("Fizz")
        } else if v%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(v)
        }
    }
}
func getNumArray(fromNum int, toNum int) []int {
    // fromNumからtoNumまでの配列(Slice)を作る.
    resultArray := make([]int, toNum-fromNum+1)
    for i := 0; i < len(resultArray); i++ {
        resultArray[i] = fromNum + i
    }
    return resultArray
}

range

For文で使用し、配列(今回は[]int)を渡すことでIndexと値を取得できます。

今回は使用しませんが、Indexが必要な場合は「for _, v := 〜」の部分を「for i, v := 〜」のように変更します。
「for i := 〜」のように書くとIndexのみが取得でき、
「i」にはIndexが、「v」には値が格納されます。

「_」のように書いているのは、Goでは未使用の変数があるとエラーになってしまうためそれを避けるためです。

slice

「getNumArray」では、1から100までの数値の配列を作っています。
Rubyのように「(1..100)」とかできると楽だったのですが。

配列には「[3]int」のように要素数を指定したものと、そうでないSliceとがあります。

素数を指定する場合、「[3]int」と「[5]int」が別の型として扱われるため、
要素を指定する必要のないSliceを使っています。

Sliceのインスタンスを初期化する場合、「make(型, 要素数)」を使うことができます。

RxGoを使う

例えばC#FizzBuzzをする場合、Linqを使ってFor文は使わずにFizzBuzzすることができます。
しかし、残念ながら?Goには標準でそのような機能が用意されていないようです。

で、ふと思いだしたのがRxJavaで、StreamAPI的な処理ができるっていってたな〜、と。

ということは、GoにもRxが用意されていれば、同じようなことできるんじゃね?

と思って調べてみたら、そのままズバリRxGoがありました。

https://github.com/ReactiveX/RxGo

そんなこんなでRxGoを使ってFizzBuzzしてみます。

go get

RxGoはGolang標準の機能ではないため、ファイルをインストールしてやる必要があります。

そのために行うのがgo getです。
READMEにある通り下記をターミナルで実行するとインストールできます。

go get -u github.com/reactivex/rxgo

今回はobservable.Range(さっきとは違うやつです)を使ってみました。

package main

import (
    "fmt"
    "github.com/reactivex/rxgo/handlers"
    "github.com/reactivex/rxgo/observable"
)
func main() {
    useRx()
}
func useRx() {
    stream := observable.Range(1, 101)
    wait := stream.Subscribe(handlers.NextFunc(func(item interface{}) {
        if v, ok := item.(int); ok {
            if v%15 == 0 {
                fmt.Println("FizzBuzz")
            } else if v%3 == 0 {
                fmt.Println("Fizz")
            } else if v%5 == 0 {
                fmt.Println("Buzz")
            } else {
                fmt.Println(v)
            }
        } else {
            fmt.Println("Invalid value")
        }
    }))
    <-wait
}
  • 「observable.Range(1, 101)」で1から100まで(101は含まない)繰り返し実行するためのStreamを生成します。
  • 「wait」の型は「channel」で、非同期で実行される処理の完了を待ちます。
  • RxGoのWikiでは「handlers.NextFunc」の引数である「func(item interface{})」に型は指定されていないのですが、
    少なくともver.1.8ではエラーとなるため、型を指定しています。
  • 引数の型を「interface{}」とすることで、型を指定せずに引数を受け取ることができます。
  • 「if v, ok := item.(int); ok {」の行で引数の型を確認し、intであった場合は「v」に格納された値を使ってFizzBuzzします。

おわりに

これまでC#やKotlinをはじめ多機能な言語ばかりを触っていたため、あれもないこれもない、おまけにフォーマットや型に厳しい(´・ω・`)
というのがGolangに対する正直な感想です。

ただ、「Goに入ってはGoに従え」という言葉もありますし、これまでの言語のやり方が完璧か?と言われればそうではない気がするので、
Golangが別の方向に進化する、というのは興味深いと思います。

一点、言語的にシンプルだったり独自路線なのは良いと思うのですが、
その先に幸せが待っている、と信じさせる何かがないと結局各々が独自にフレームワークやオレオレ実装プラグインを持ちだして大変になるのでは…?
という危惧はあります。

まぁ素人考えなので、ちゃんとその辺りも考慮して進められているのかとは思いますが。

参照