Day 5: 関数 - 解答例
目次
---
課題1の解答
問題1-1: 挨拶関数
解法1: 基本的な実装
package main
import "fmt"
func greet(name string) {
fmt.Printf("こんにちは、%sさん!\n", name)
}
func main() {
greet("太郎")
greet("花子")
}
解法2: 戻り値として文字列を返す
func greet(name string) string {
return fmt.Sprintf("こんにちは、%sさん!", name)
}
func main() {
message := greet("太郎")
fmt.Println(message)
// 複数の挨拶をまとめて処理
names := []string{"太郎", "花子", "次郎"}
for _, name := range names {
fmt.Println(greet(name))
}
}
利点:
- テストしやすい
- 出力方法を呼び出し側で制御できる
- ログファイルへの書き込みなど、柔軟な使い方が可能
解法3: 時間帯に応じた挨拶
import (
"fmt"
"time"
)
func greetByTime(name string) string {
hour := time.Now().Hour()
var greeting string
switch {
case hour < 12:
greeting = "おはようございます"
case hour < 18:
greeting = "こんにちは"
default:
greeting = "こんばんは"
}
return fmt.Sprintf("%s、%sさん!", greeting, name)
}
func main() {
fmt.Println(greetByTime("太郎"))
}
---
問題1-2: 足し算関数
解法1: 基本的な実装
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
result := add(3, 5)
fmt.Printf("3 + 5 = %d\n", result)
}
解法2: 可変長引数で複数の数値を加算
func add(numbers ...int) int {
sum := 0
for _, n := range numbers {
sum += n
}
return sum
}
func main() {
fmt.Printf("3 + 5 = %d\n", add(3, 5))
fmt.Printf("1 + 2 + 3 + 4 + 5 = %d\n", add(1, 2, 3, 4, 5))
fmt.Printf("10 + 20 + 30 = %d\n", add(10, 20, 30))
}
実用性: この実装は実際のプロジェクトでより有用
解法3: ジェネリクスを使った型安全な実装(Go 1.18+)
func add[T int | int64 | float64](a, b T) T {
return a + b
}
func main() {
fmt.Println(add(3, 5)) // int
fmt.Println(add(3.5, 2.1)) // float64
fmt.Println(add[int64](100, 200)) // int64
}
解法4: エラーハンドリング付き
import "errors"
func addWithValidation(a, b int) (int, error) {
// オーバーフローチェック
if a > 0 && b > 0 && a > (math.MaxInt-b) {
return 0, errors.New("integer overflow")
}
return a + b, nil
}
func main() {
result, err := addWithValidation(3, 5)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("3 + 5 = %d\n", result)
}
---
問題1-3: 最大値関数
解法1: if文を使った基本実装
package main
import "fmt"
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
fmt.Printf("max(3, 7) = %d\n", max(3, 7))
}
解法2: 三項演算子風(Goにはないがこう書ける)
func max(a, b int) int {
if a > b {
return a
}
return b
}
Goには三項演算子がないため、これが最もシンプルな方法です。
解法3: 可変長引数で複数の値から最大値を見つける
import "errors"
func max(numbers ...int) (int, error) {
if len(numbers) == 0 {
return 0, errors.New("no numbers provided")
}
maxVal := numbers[0]
for _, n := range numbers[1:] {
if n > maxVal {
maxVal = n
}
}
return maxVal, nil
}
func main() {
result, _ := max(3, 7, 2, 9, 1)
fmt.Printf("max(3, 7, 2, 9, 1) = %d\n", result)
}
解法4: ジェネリクスを使った汎用実装
import "golang.org/x/exp/constraints"
func max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(max(3, 7)) // int
fmt.Println(max(3.5, 2.1)) // float64
fmt.Println(max("apple", "banana")) // string
}
---
課題2の解答
問題2-1: 割り算関数
解法1: 基本的な実装
package main
import "fmt"
func divide(a, b int) (int, int) {
return a / b, a % b
}
func main() {
q, r := divide(17, 5)
fmt.Printf("17 ÷ 5 = %d 余り %d\n", q, r)
}
解法2: 名前付き戻り値を使った実装
func divide(a, b int) (quotient, remainder int) {
quotient = a / b
remainder = a % b
return
}
func main() {
q, r := divide(17, 5)
fmt.Printf("17 ÷ 5 = %d 余り %d\n", q, r)
}
メリット: 戻り値の意味が明確になる
解法3: エラーハンドリング付き(本番環境推奨)
import (
"errors"
"fmt"
)
func divide(a, b int) (quotient, remainder int, err error) {
if b == 0 {
return 0, 0, errors.New("division by zero")
}
quotient = a / b
remainder = a % b
return quotient, remainder, nil
}
func main() {
q, r, err := divide(17, 5)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("17 ÷ 5 = %d 余り %d\n", q, r)
// ゼロ除算のテスト
_, _, err = divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
}
}
解法4: 構造体で結果を返す
type DivisionResult struct {
Quotient int
Remainder int
Error error
}
func divide(a, b int) DivisionResult {
if b == 0 {
return DivisionResult{
Error: errors.New("division by zero"),
}
}
return DivisionResult{
Quotient: a / b,
Remainder: a % b,
}
}
func main() {
result := divide(17, 5)
if result.Error != nil {
fmt.Println("Error:", result.Error)
return
}
fmt.Printf("17 ÷ 5 = %d 余り %d\n", result.Quotient, result.Remainder)
}
---
問題2-2: 統計関数
解法1: 基本的な実装
package main
import "fmt"
func stats(nums []int) (int, float64) {
sum := 0
for _, n := range nums {
sum += n
}
avg := float64(sum) / float64(len(nums))
return sum, avg
}
func main() {
numbers := []int{10, 20, 30, 40, 50}
sum, avg := stats(numbers)
fmt.Printf("合計: %d, 平均: %.1f\n", sum, avg)
}
解法2: エラーハンドリングと名前付き戻り値
import (
"errors"
"fmt"
)
func stats(nums []int) (sum int, avg float64, err error) {
if len(nums) == 0 {
return 0, 0, errors.New("empty slice")
}
for _, n := range nums {
sum += n
}
avg = float64(sum) / float64(len(nums))
return sum, avg, nil
}
func main() {
numbers := []int{10, 20, 30, 40, 50}
sum, avg, err := stats(numbers)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("合計: %d, 平均: %.2f\n", sum, avg)
}
解法3: より詳細な統計情報
import "math"
type Statistics struct {
Sum int
Average float64
Min int
Max int
Count int
}
func stats(nums []int) (Statistics, error) {
if len(nums) == 0 {
return Statistics{}, errors.New("empty slice")
}
result := Statistics{
Min: nums[0],
Max: nums[0],
Count: len(nums),
}
for _, n := range nums {
result.Sum += n
if n < result.Min {
result.Min = n
}
if n > result.Max {
result.Max = n
}
}
result.Average = float64(result.Sum) / float64(result.Count)
return result, nil
}
func main() {
numbers := []int{10, 20, 30, 40, 50}
stat, err := stats(numbers)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("合計: %d\n", stat.Sum)
fmt.Printf("平均: %.2f\n", stat.Average)
fmt.Printf("最小: %d\n", stat.Min)
fmt.Printf("最大: %d\n", stat.Max)
fmt.Printf("個数: %d\n", stat.Count)
}
解法4: 標準偏差も計算する
import "math"
func statsAdvanced(nums []int) (sum int, avg, stdDev float64, err error) {
if len(nums) == 0 {
return 0, 0, 0, errors.New("empty slice")
}
// 合計と平均
for _, n := range nums {
sum += n
}
avg = float64(sum) / float64(len(nums))
// 分散と標準偏差
var variance float64
for _, n := range nums {
diff := float64(n) - avg
variance += diff * diff
}
variance /= float64(len(nums))
stdDev = math.Sqrt(variance)
return sum, avg, stdDev, nil
}
---
課題3の解答
問題3-1: 再帰関数(階乗)
解法1: 基本的な再帰実装
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
func main() {
fmt.Println(factorial(5)) // 120
fmt.Println(factorial(10)) // 3628800
}
解法2: エラーハンドリング付き
import "errors"
func factorial(n int) (int, error) {
if n < 0 {
return 0, errors.New("negative number not allowed")
}
if n <= 1 {
return 1, nil
}
result, err := factorial(n - 1)
if err != nil {
return 0, err
}
return n * result, nil
}
解法3: 反復版(パフォーマンス重視)
func factorialIterative(n int) int {
if n <= 1 {
return 1
}
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result
}
パフォーマンス比較:
- 再帰版: O(n)時間、O(n)スタック空間
- 反復版: O(n)時間、O(1)スタック空間
解法4: メモ化による最適化
var factorialMemo = map[int]int{0: 1, 1: 1}
func factorialMemoized(n int) int {
if val, ok := factorialMemo[n]; ok {
return val
}
factorialMemo[n] = n * factorialMemoized(n-1)
return factorialMemo[n]
}
解法5: 末尾再帰風(Goでは最適化されないが参考)
func factorialTail(n int) int {
return factorialHelper(n, 1)
}
func factorialHelper(n, accumulator int) int {
if n <= 1 {
return accumulator
}
return factorialHelper(n-1, n*accumulator)
}
---
問題3-2: フィボナッチ
解法1: 基本的な再帰実装
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", fibonacci(i))
}
// 出力: 0 1 1 2 3 5 8 13 21 34
}
警告: この実装は指数時間(O(2^n))で非効率
解法2: メモ化による最適化
func fibonacci(n int) int {
memo := make(map[int]int)
return fibMemo(n, memo)
}
func fibMemo(n int, memo map[int]int) int {
if n <= 1 {
return n
}
if val, ok := memo[n]; ok {
return val
}
memo[n] = fibMemo(n-1, memo) + fibMemo(n-2, memo)
return memo[n]
}
改善: O(n)時間に改善
解法3: 反復版(最も効率的)
func fibonacciIterative(n int) int {
if n <= 1 {
return n
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
return b
}
特徴:
- O(n)時間
- O(1)空間
- 本番環境推奨
解法4: スライスを使ったメモ化
func fibonacciSlice(n int) int {
if n <= 1 {
return n
}
fib := make([]int, n+1)
fib[0] = 0
fib[1] = 1
for i := 2; i <= n; i++ {
fib[i] = fib[i-1] + fib[i-2]
}
return fib[n]
}
解法5: チャネルを使った生成器パターン
func fibonacciGenerator(n int) <-chan int {
ch := make(chan int)
go func() {
defer close(ch)
a, b := 0, 1
for i := 0; i < n; i++ {
ch <- a
a, b = b, a+b
}
}()
return ch
}
func main() {
for num := range fibonacciGenerator(10) {
fmt.Printf("%d ", num)
}
}
---
複数の解法比較
パフォーマンス比較表
| 関数 | 再帰版 | 反復版 | メモ化版 |
|---|---|---|---|
| 階乗(20) | 遅い | 高速 | 高速 |
| フィボナッチ(30) | 非常に遅い | 高速 | 高速 |
| 空間計算量 | O(n) | O(1) | O(n) |
---
エラーハンドリングの追加
全課題に対する堅牢な実装例
package main
import (
"errors"
"fmt"
)
// 入力検証付き挨拶関数
func greet(name string) (string, error) {
if name == "" {
return "", errors.New("name cannot be empty")
}
return fmt.Sprintf("こんにちは、%sさん!", name), nil
}
// オーバーフローチェック付き加算
func add(a, b int) (int, error) {
if (b > 0 && a > (1<<63-1)-b) || (b < 0 && a < (-1<<63)-b) {
return 0, errors.New("integer overflow")
}
return a + b, nil
}
// 安全な除算
func divide(a, b int) (quotient, remainder int, err error) {
if b == 0 {
return 0, 0, errors.New("division by zero")
}
return a / b, a % b, nil
}
// 安全な統計計算
func stats(nums []int) (sum int, avg float64, err error) {
if len(nums) == 0 {
return 0, 0, errors.New("empty slice")
}
for _, n := range nums {
sum += n
}
avg = float64(sum) / float64(len(nums))
return sum, avg, nil
}
---
テストコード
package main
import "testing"
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 3, 5, 8},
{"negative numbers", -3, -5, -8},
{"mixed", -3, 5, 2},
{"zero", 0, 5, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := add(tt.a, tt.b)
if got != tt.want {
t.Errorf("add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestDivide(t *testing.T) {
q, r := divide(17, 5)
if q != 3 || r != 2 {
t.Errorf("divide(17, 5) = (%d, %d); want (3, 2)", q, r)
}
}
func TestFactorial(t *testing.T) {
tests := []struct {
input int
want int
}{
{0, 1},
{1, 1},
{5, 120},
{10, 3628800},
}
for _, tt := range tests {
got := factorial(tt.input)
if got != tt.want {
t.Errorf("factorial(%d) = %d; want %d", tt.input, got, tt.want)
}
}
}
---
ベンチマーク
func BenchmarkFactorialRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
factorial(20)
}
}
func BenchmarkFactorialIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
factorialIterative(20)
}
}
func BenchmarkFibonacciRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
fibonacci(20)
}
}
func BenchmarkFibonacciIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
fibonacciIterative(20)
}
}
実行方法:
go test -bench=.
---
実践的な拡張
電卓プログラム(全機能統合)
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Calculator struct{}
func (c Calculator) Add(a, b float64) float64 {
return a + b
}
func (c Calculator) Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func (c Calculator) Factorial(n int) (int, error) {
if n < 0 {
return 0, fmt.Errorf("negative number")
}
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result, nil
}
func main() {
calc := Calculator{}
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("簡易電卓 - コマンド: add, div, fact, quit")
for {
fmt.Print("> ")
if !scanner.Scan() {
break
}
input := scanner.Text()
parts := strings.Fields(input)
if len(parts) == 0 {
continue
}
switch parts[0] {
case "add":
if len(parts) != 3 {
fmt.Println("使い方: add <数値1> <数値2>")
continue
}
a, _ := strconv.ParseFloat(parts[1], 64)
b, _ := strconv.ParseFloat(parts[2], 64)
fmt.Printf("結果: %.2f\n", calc.Add(a, b))
case "div":
if len(parts) != 3 {
fmt.Println("使い方: div <数値1> <数値2>")
continue
}
a, _ := strconv.ParseFloat(parts[1], 64)
b, _ := strconv.ParseFloat(parts[2], 64)
if result, err := calc.Divide(a, b); err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("結果: %.2f\n", result)
}
case "fact":
if len(parts) != 2 {
fmt.Println("使い方: fact <整数>")
continue
}
n, _ := strconv.Atoi(parts[1])
if result, err := calc.Factorial(n); err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("結果: %d\n", result)
}
case "quit":
fmt.Println("終了します")
return
default:
fmt.Println("不明なコマンド")
}
}
}
---
まとめ
この解答例では以下を網羅しました:
- 複数の解法: 各問題に2〜5つの異なるアプローチ
- エラーハンドリング: 本番環境を想定した堅牢な実装
- パフォーマンス: 再帰vs反復、メモ化などの最適化手法
- テストとベンチマーク: 品質保証の実践
- 実践的な統合例: 学んだ内容を活用したミニプロジェクト
次のステップ:
- これらのコードを実際に動かしてみる
- テストを書いて品質を確認
- ベンチマークでパフォーマンスを測定
- 独自の拡張機能を追加