第4回 Goのswitch文で解くFizzBuzz問題と構造体のイントロ
赤坂 けい
チームWordProgress
2010/2/25
突然登場した新しいプログラミング言語「Go」。その独自性、魅力を余すところなく堪能してみよう(編集部)
goroutine以外の並列処理アプローチについての議論
第3回「ハロー、goroutine!」から、Goの並列処理記述を特徴付けているgoroutineを取り上げている。goroutineは、チャネルなどを通じてメッセージをやり取りする軽量プロセスである。このアプローチは、Erlangの軽量プロセスと類似している。
他方、HaskellやClojureなどの関数型言語では、「ソフトウェアトランザクショナルメモリ(Software Transactional Memory:STM)」と呼ばれる並列処理のアプローチが前面に押し出されている感がある。
STMもgoroutineも、これまで一般的であった「スレッド」を用いた並列処理モデルに対するオルタナティブを目指すものといえる。スレッド・プログラミングは、一般のプログラマにとって難易度が高いとされていることがその背景にある。
最近、Goのメーリングリストにおいて、「Goは、goroutineに加えてSTMもサポートすべきではないか」という議論が行われている(軽量プロセスのアプローチと、STMのアプローチは排他的なものではなく、両者を実装している言語はいくつもある)。
Goのメーリングリストということもあってか、いまのところ議論はgoroutineの優勢勝ちのようだ(例えば、STMよりもgoroutineの方がスケールする、STMの信頼性はいまだ低いなど)。
しかし、今後の並列処理へのアプローチを考える上で興味深い内容が含まれているため、興味のある方はメーリングリストをチェックしてほしい(メーリングリスト内で「STM」を検索するのが手っ取り早い)。
今回もGoの基本的な文法を扱いながら、goroutineについても見ていこう。
STMに興味をお持ちの方は、こちらも一読をお勧めする。この論文に対するSTMの擁護コメント(例えば、Haskellなどで実装されている、型安全なトランザクションメモリの考察が欠けているなど)も興味深い。
switch文でFizzBuzz問題
Goの基本的な制御構造で、まだ解説していなかったswitch文を確認しよう。Goの式は、Cのswitch文に比べると柔軟性が高く、使い勝手が良い。
第一に、Goのswitch文では、条件節(case)のところに「式」を書くことができる。このことは、if文と同等の制御構造をswitch文で表現できることを意味する。
試してみよう。
package main func main() { var i =3; if i < 5 {println("5以下")} else {println("それ以外")}; //「if文」と同一の動作をする「switch文」 switch { case i <5 : println("5以下") default : println("それ以外") } }
5以下 5以下
ここで、「if 〜 else 〜」文と「switch { case 〜 defalut 〜 }」文は、同一の動作をしている。もっとも、この例のように条件が1つしかない場合には、if文の方を使う方が明らかに自然だ。
一方、複数の条件がある場合には、switch文を使う方が表現が簡潔になる。複数の条件がある例として、有名な「FizzBuzzゲーム」を記述してみよう。
FizzBuzzゲームでは、1〜100までの数字を順に列挙していく。ただし、3の倍数のときはBuzz、5の倍数のときはFizz、3と5の倍数のときはFizzBuzzと表示する特別ルールがある。
このことは、switch文を用いると以下のように書ける。
package main import ("fmt") func main() { for i := 1; i <= 100; i++ { switch { case i % 15 == 0 : print ("FizzBuzz, ") case i % 5 == 0 : print ("Fizz, ") case i % 3 == 0 : print ("Buzz, ") default : fmt.Printf ("%d, ",i) } } }
1, 2, Buzz, 4, Fizz, Buzz, 7, 8, Buzz, Fizz, 11, Buzz, 13, 14, FizzBuzz, 16, 17, Buzz, 19, Fizz, Buzz, 22, 23, Buzz, Fizz, 26, Buzz, 28, 29, FizzBuzz, 31, 32, B uzz, 34, Fizz, Buzz, 37, 38, Buzz, Fizz, 41, Buzz, 43, 44, FizzBuzz, 46, 47, Buz z, 49, Fizz, Buzz, 52, 53, Buzz, Fizz, 56, Buzz, 58, 59, FizzBuzz, 61, 62, Buzz, 64, Fizz, Buzz, 67, 68, Buzz, Fizz, 71, Buzz, 73, 74, FizzBuzz, 76, 77, Buzz, 7 9, Fizz, Buzz, 82, 83, Buzz, Fizz, 86, Buzz, 88, 89, FizzBuzz, 91, 92, Buzz, 94, Fizz, Buzz, 97, 98, Buzz, Fizz,
switch文の中の条件節は、ゲームのルールをそのまま列挙しているだけだ。switch文は、条件節(case)を上から順に評価し、条件にマッチした時点で、その節を実行し評価を打ち切る。
これを利用して、ここではゲームのルールを特殊な例から順に表記することで、シンプルな記述が実現できている。
それぞれの条件節は、「case 条件 : 実行内容」と表記する。また、いずれの条件にマッチしなかった場合の処理を、switch文の末尾に「default : 実行内容」の形式で記述できる。
加えて、(if文で、実行内容部分の中括弧が省略不可であることとは異なり)、switch文の「case:」の後の実行内容部分では「{ }(中括弧)」を書かなくてもよいこと、実行内容の最後に終了条件(break)を記述する必要がないことも確認しておこう。
続いて、値を返す関数内でのswitch文の使い方を見ておこう。
package main import ("fmt";"strconv") //無名関数にFizzBuzzという変数名を付けている var FizzBuzz = func (i int) (rtn string){ switch { case i % 15 == 0 : rtn = "FizzBuzz" case i % 5 == 0 : rtn = "Fizz" case i % 3 == 0 : rtn = "Buzz" default : rtn = strconv.Itoa(i) } return } func main() { for i := 1; i <= 100; i++ { fmt.Printf("%s, ",FizzBuzz(i)) } }
1, 2, Buzz, 4, Fizz, Buzz, 7, 8, Buzz, Fizz, 11, Buzz, 13, 14, FizzBuzz, 16, 17, Buzz, 19, Fizz, Buzz, 22, 23, Buzz, Fizz, 26, Buzz, 28, 29, FizzBuzz, 31, 32, B uzz, 34, Fizz, Buzz, 37, 38, Buzz, Fizz, 41, Buzz, 43, 44, FizzBuzz, 46, 47, Buz z, 49, Fizz, Buzz, 52, 53, Buzz, Fizz, 56, Buzz, 58, 59, FizzBuzz, 61, 62, Buzz, 64, Fizz, Buzz, 67, 68, Buzz, Fizz, 71, Buzz, 73, 74, FizzBuzz, 76, 77, Buzz, 7 9, Fizz, Buzz, 82, 83, Buzz, Fizz, 86, Buzz, 88, 89, FizzBuzz, 91, 92, Buzz, 94, Fizz, Buzz, 97, 98, Buzz, Fizz,
冒頭で、int型の変数iを受け取り、string型の変数rtnを返す無名関数が定義され、それに変数名FizzBuzzという名前を付けている。同じく冒頭で、返り値の変数名rtnを定義し、returnには変数名を記述しなくてもよいという、特徴的な表記も用いている(冒頭で返り値を定義することによって、関数内の見通しがよくすることができると筆者は感じている)。
もちろん、Cと同様に、関数冒頭で返り値の型のみを定義し、returnに返り値を記述することも可能である。すなわち、変数FizzBuzzは以下のようにも定義できる。
var FizzBuzz = func (i int) string{ switch { case i % 15 == 0 : return "FizzBuzz" case i % 5 == 0 : return "Fizz" case i % 3 == 0 : return "Buzz" } return strconv.Itoa(i) }
最後に、switch文とgoroutineを組み合わせておこう。同じ処理を3度も記述するのは芸がないと思うので、ルールを一般化可能なコードにしておくことにする。
package main import ("fmt") var ch = make(chan string) func FizzBuzzOne(init int) { const Fizz ,Buzz =5,3; //任意の2つの数値に置換可能 Fin := Fizz*Buzz; for i := init * Fin +1; ;i++{ switch { case i % Fin == 0 : ch <- "FizzBuzz."; return case i % Fizz == 0 : print ("Fizz,") case i % Buzz == 0 : print ("Buzz,") default : fmt.Printf ("%3d,",i) } } } func main() { for num := 0; num <= 10; num++ { go FizzBuzzOne(num); println(<- ch); // ここで「待ち」が入る } }
1, 2,Buzz, 4,Fizz,Buzz, 7, 8,Buzz,Fizz, 11,Buzz, 13, 14,FizzBuzz. 16, 17,Buzz, 19,Fizz,Buzz, 22, 23,Buzz,Fizz, 26,Buzz, 28, 29,FizzBuzz. 31, 32,Buzz, 34,Fizz,Buzz, 37, 38,Buzz,Fizz, 41,Buzz, 43, 44,FizzBuzz. 46, 47,Buzz, 49,Fizz,Buzz, 52, 53,Buzz,Fizz, 56,Buzz, 58, 59,FizzBuzz. 61, 62,Buzz, 64,Fizz,Buzz, 67, 68,Buzz,Fizz, 71,Buzz, 73, 74,FizzBuzz. 76, 77,Buzz, 79,Fizz,Buzz, 82, 83,Buzz,Fizz, 86,Buzz, 88, 89,FizzBuzz. 91, 92,Buzz, 94,Fizz,Buzz, 97, 98,Buzz,Fizz,101,Buzz,103,104,FizzBuzz. 106,107,Buzz,109,Fizz,Buzz,112,113,Buzz,Fizz,116,Buzz,118,119,FizzBuzz. 121,122,Buzz,124,Fizz,Buzz,127,128,Buzz,Fizz,131,Buzz,133,134,FizzBuzz. 136,137,Buzz,139,Fizz,Buzz,142,143,Buzz,Fizz,146,Buzz,148,149,FizzBuzz. 151,152,Buzz,154,Fizz,Buzz,157,158,Buzz,Fizz,161,Buzz,163,164,FizzBuzz.
すでにお気付きの方も多いだろうが、FizzBuzz問題でgoroutineを使うのはほとんど無意味である(順々に増えていくものを表示するために、並列処理することはほとんど無意味だ)。
ただし、gooutineとswitchの組み合わせは、相性がよい。例えば、非同期にリクエストを受け付けるサーバで、リクエスト内容に応じた複数の処理を記述する場合などに、両者を組み合わせて簡潔な処理記述ができるだろう。本連載では、応用編に進んだあたりで、このことにトライしたい。
1/2 |
![]() |
Index | |
Goのswitch文で解くFizzBuzz問題と構造体のイントロ | |
![]() |
Page1 goroutine以外の並列処理アプローチについての議論 switch文でFizzBuzz問題 |
Page2 構造体とメソッドの定義 |
![]() |
新世代の並列処理言語Google Goをひもとく |
Coding Edgeお勧め記事 |
![]() |
いまさらアルゴリズムを学ぶ意味 コーディングに役立つ! アルゴリズムの基本(1) コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう |
![]() |
Zope 3の魅力に迫る Zope 3とは何ぞや?(1) Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか? |
![]() |
貧弱環境プログラミングのススメ 柴田 淳のコーディング天国 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く? |
![]() |
Haskellプログラミングの楽しみ方 のんびりHaskell(1) 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう |
![]() |
ちょっと変わったLisp入門 Gaucheでメタプログラミング(1) Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう |

- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
![]() |
|
|
|
![]() |