🐹
Go言語のサンプルコード
難易度が段階的に上がる3本の実践的サンプルを紹介します。構造体を使った小規模プログラムから、アルゴリズムの実装、goroutineによる並行処理まで、Goらしい書き方を確認してみましょう。
サンプル①:構造体とメソッドを使う家計簿プログラム
構造体(struct)でデータをまとめ、ポインタレシーバのメソッドで状態を更新する基本パターンです。
収入・支出を記録し、レコードのスライスから合計や残高を計算します。エラーハンドリングとして、不正な金額が渡された場合にはerrorを返します。
SAMPLE PROGRAM
package main
import (
"errors"
"fmt"
)
// Record は1件の収支レコードを表す
type Record struct {
Label string
Amount int // 収入は正の値、支出は負の値
}
// Ledger は家計簿本体(複数のRecordを保持する)
type Ledger struct {
records []Record
}
// Add は収支レコードを追加する(ポインタレシーバでフィールドを更新)
func (l *Ledger) Add(label string, amount int) error {
if label == "" {
return errors.New("項目名を入力してください")
}
l.records = append(l.records, Record{Label: label, Amount: amount})
return nil
}
// Balance は現在の残高を計算する
func (l *Ledger) Balance() int {
total := 0
for _, r := range l.records {
total += r.Amount
}
return total
}
// Summary は収入合計・支出合計をまとめて返す(複数戻り値)
func (l *Ledger) Summary() (income int, expense int) {
for _, r := range l.records {
if r.Amount >= 0 {
income += r.Amount
} else {
expense += r.Amount
}
}
return
}
func main() {
ledger := &Ledger{}
entries := []struct {
label string
amount int
}{
{"給与", 250000},
{"家賃", -70000},
{"食費", -35000},
{"書籍", -3000},
}
for _, e := range entries {
if err := ledger.Add(e.label, e.amount); err != nil {
fmt.Println("登録エラー:", err)
continue
}
}
income, expense := ledger.Summary()
fmt.Println("収入合計:", income)
fmt.Println("支出合計:", expense)
fmt.Println("残高:", ledger.Balance())
}
サンプル②:クイックソートの実装と計算量
整列アルゴリズムの代表例であるクイックソートを実装します。基準値(ピボット)を選び、それより小さい要素・大きい要素に分割して再帰的にソートする分割統治法です。 平均計算量はO(n log n)ですが、ピボットの選び方が悪いと最悪計算量はO(n²)になります。比較として、実装が単純なバブルソート(O(n²))も併記します。
SAMPLE PROGRAM
package main
import "fmt"
// quickSort: 平均計算量 O(n log n)、最悪計算量 O(n^2)
func quickSort(data []int) []int {
if len(data) <= 1 {
return data
}
pivot := data[len(data)/2]
var less, equal, greater []int
for _, v := range data {
switch {
case v < pivot:
less = append(less, v)
case v == pivot:
equal = append(equal, v)
default:
greater = append(greater, v)
}
}
result := quickSort(less)
result = append(result, equal...)
result = append(result, quickSort(greater)...)
return result
}
// bubbleSort: 計算量 O(n^2)(隣接要素を比較して交換を繰り返す)
func bubbleSort(data []int) {
n := len(data)
for i := 0; i < n-1; i++ {
for j := 0; j < n-1-i; j++ {
if data[j] > data[j+1] {
data[j], data[j+1] = data[j+1], data[j]
}
}
}
}
func main() {
data := []int{5, 2, 8, 1, 9, 3, 7}
fmt.Println("クイックソート結果:", quickSort(data))
data2 := []int{5, 2, 8, 1, 9, 3, 7}
bubbleSort(data2)
fmt.Println("バブルソート結果:", data2)
}
サンプル③:goroutine/channelを使った並行ダウンロードシミュレーション
複数の「ダウンロード処理」を模したタスクをgoroutineで並行実行し、結果をchannelで集約する例です。sync.WaitGroupで全goroutineの終了を待ち、
time.Sleepで処理時間の違いを再現しています。逐次実行に比べ、並行実行することで全体の処理時間を短縮できることがポイントです。
SAMPLE PROGRAM
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
// Result はダウンロード結果を表す
type Result struct {
URL string
Duration time.Duration
}
// download は1件のダウンロード処理を模擬する
func download(url string, ch chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
start := time.Now()
// 処理時間をランダムに再現(100〜300ミリ秒)
time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond)
ch <- Result{URL: url, Duration: time.Since(start)}
}
func main() {
urls := []string{
"https://example.com/a",
"https://example.com/b",
"https://example.com/c",
"https://example.com/d",
}
var wg sync.WaitGroup
ch := make(chan Result, len(urls))
start := time.Now()
for _, url := range urls {
wg.Add(1)
go download(url, ch, &wg) // 各URLの取得を並行実行
}
// 別goroutineで全ての完了を待ってからchannelを閉じる
go func() {
wg.Wait()
close(ch)
}()
// channelがcloseされるまで受信し続ける
for result := range ch {
fmt.Printf("%s : %v\n", result.URL, result.Duration)
}
fmt.Println("全体の処理時間:", time.Since(start))
}