Day 7: マップ - 解答例
課題1の解答
問題1-1: 商品価格マップ
アプローチ1: マップリテラルによる初期化(推奨)
package main
import "fmt"
func main() {
// マップリテラルで初期値を設定
// この方法が最も読みやすく、簡潔
prices := map[string]int{
"りんご": 100,
"みかん": 80,
"ぶどう": 200,
}
// rangeで全要素を反復処理
// 順序は保証されないことに注意
for name, price := range prices {
fmt.Printf("%s: %d円\n", name, price)
}
}
出力例:
りんご: 100円
みかん: 80円
ぶどう: 200円
アプローチ2: makeで初期化後に追加
package main
import "fmt"
func main() {
// 空のマップを作成
prices := make(map[string]int)
// 一つずつ追加
prices["りんご"] = 100
prices["みかん"] = 80
prices["ぶどう"] = 200
for name, price := range prices {
fmt.Printf("%s: %d円\n", name, price)
}
}
使い分け:
- 初期値が決まっている → リテラル
- 動的に追加する → make
アプローチ3: ソート済み出力
package main
import (
"fmt"
"sort"
)
func main() {
prices := map[string]int{
"りんご": 100,
"みかん": 80,
"ぶどう": 200,
}
// キーをスライスに抽出してソート
names := make([]string, 0, len(prices))
for name := range prices {
names = append(names, name)
}
sort.Strings(names)
// ソートされた順序で出力
for _, name := range names {
fmt.Printf("%s: %d円\n", name, prices[name])
}
}
出力(アルファベット順):
ぶどう: 200円
みかん: 80円
りんご: 100円
---
問題1-2: 存在確認
アプローチ1: カンマokイディオム(推奨)
package main
import "fmt"
func main() {
prices := map[string]int{
"りんご": 100,
"みかん": 80,
}
// 存在するキーの確認
// okがtrueの場合のみ処理
if price, ok := prices["りんご"]; ok {
fmt.Printf("りんご: %d円\n", price)
}
// 存在しないキーの確認
// okがfalseなので、elseブロックが実行される
if price, ok := prices["バナナ"]; ok {
fmt.Printf("バナナ: %d円\n", price)
} else {
fmt.Println("バナナは見つかりません")
}
}
よくある間違い:
// 間違い: 存在確認なしでアクセス
price := prices["バナナ"]
fmt.Println(price) // 0が出力される(エラーではない!)
// 問題: 0が「存在しないキー」なのか「値が0」なのか区別できない
アプローチ2: ヘルパー関数化
package main
import "fmt"
// マップから値を安全に取得する関数
// 存在しない場合はデフォルト値を返す
func getOrDefault(m map[string]int, key string, defaultValue int) int {
if value, ok := m[key]; ok {
return value
}
return defaultValue
}
func main() {
prices := map[string]int{
"りんご": 100,
"みかん": 80,
}
// 存在するキー
fmt.Printf("りんご: %d円\n", getOrDefault(prices, "りんご", -1))
// 存在しないキー(デフォルト値を返す)
fmt.Printf("バナナ: %d円\n", getOrDefault(prices, "バナナ", -1))
}
アプローチ3: 複数キーの存在確認
package main
import "fmt"
func main() {
prices := map[string]int{
"りんご": 100,
"みかん": 80,
}
// 複数のキーを確認
items := []string{"りんご", "バナナ", "みかん", "ぶどう"}
for _, item := range items {
if price, ok := prices[item]; ok {
fmt.Printf("✓ %s: %d円\n", item, price)
} else {
fmt.Printf("✗ %s: 見つかりません\n", item)
}
}
}
出力:
✓ りんご: 100円
✗ バナナ: 見つかりません
✓ みかん: 80円
✗ ぶどう: 見つかりません
---
課題2の解答
問題2-1: CRUD操作
基本的な実装
package main
import "fmt"
func main() {
// Create(作成): 空のマップを作成
prices := make(map[string]int)
// Create(追加): 新しい要素を追加
prices["りんご"] = 100
prices["みかん"] = 80
fmt.Println("追加後:", prices)
// Update(更新): 既存の値を変更
prices["りんご"] = 120
fmt.Println("更新後:", prices)
// Delete(削除): 要素を削除
delete(prices, "みかん")
fmt.Println("削除後:", prices)
}
より実践的な実装(エラーハンドリング付き)
package main
import (
"errors"
"fmt"
)
// PriceManager は商品価格を管理する構造体
type PriceManager struct {
prices map[string]int
}
// NewPriceManager は新しいPriceManagerを作成
func NewPriceManager() *PriceManager {
return &PriceManager{
prices: make(map[string]int),
}
}
// Add は新しい商品を追加(既存の場合はエラー)
func (pm *PriceManager) Add(name string, price int) error {
if _, exists := pm.prices[name]; exists {
return errors.New("商品は既に存在します")
}
if price < 0 {
return errors.New("価格は0以上である必要があります")
}
pm.prices[name] = price
return nil
}
// Update は既存の商品価格を更新
func (pm *PriceManager) Update(name string, price int) error {
if _, exists := pm.prices[name]; !exists {
return errors.New("商品が見つかりません")
}
if price < 0 {
return errors.New("価格は0以上である必要があります")
}
pm.prices[name] = price
return nil
}
// Delete は商品を削除
func (pm *PriceManager) Delete(name string) error {
if _, exists := pm.prices[name]; !exists {
return errors.New("商品が見つかりません")
}
delete(pm.prices, name)
return nil
}
// Get は商品価格を取得
func (pm *PriceManager) Get(name string) (int, error) {
if price, exists := pm.prices[name]; exists {
return price, nil
}
return 0, errors.New("商品が見つかりません")
}
// List は全商品をリスト表示
func (pm *PriceManager) List() {
if len(pm.prices) == 0 {
fmt.Println("商品がありません")
return
}
for name, price := range pm.prices {
fmt.Printf("%s: %d円\n", name, price)
}
}
func main() {
pm := NewPriceManager()
// 追加
if err := pm.Add("りんご", 100); err != nil {
fmt.Println("エラー:", err)
}
if err := pm.Add("みかん", 80); err != nil {
fmt.Println("エラー:", err)
}
fmt.Println("=== 追加後 ===")
pm.List()
// 更新
if err := pm.Update("りんご", 120); err != nil {
fmt.Println("エラー:", err)
}
fmt.Println("\n=== 更新後 ===")
pm.List()
// 削除
if err := pm.Delete("みかん"); err != nil {
fmt.Println("エラー:", err)
}
fmt.Println("\n=== 削除後 ===")
pm.List()
// エラーケースのテスト
fmt.Println("\n=== エラーケース ===")
if err := pm.Add("りんご", 150); err != nil {
fmt.Println("エラー:", err) // 既に存在する
}
if err := pm.Update("バナナ", 120); err != nil {
fmt.Println("エラー:", err) // 存在しない
}
}
---
問題2-2: 反復処理
アプローチ1: 基本的な反復
package main
import "fmt"
func main() {
scores := map[string]int{
"太郎": 85,
"花子": 92,
"次郎": 78,
}
// 全員のスコアを表示
for name, score := range scores {
fmt.Printf("%s: %d点\n", name, score)
}
}
アプローチ2: 統計情報付き
package main
import "fmt"
func main() {
scores := map[string]int{
"太郎": 85,
"花子": 92,
"次郎": 78,
"美咲": 88,
}
// 合計点と平均点を計算
total := 0
count := 0
fmt.Println("=== 成績表 ===")
for name, score := range scores {
fmt.Printf("%s: %d点\n", name, score)
total += score
count++
}
if count > 0 {
average := float64(total) / float64(count)
fmt.Printf("\n合計: %d点\n", total)
fmt.Printf("平均: %.1f点\n", average)
}
}
アプローチ3: ランキング表示
package main
import (
"fmt"
"sort"
)
// スコア情報を保持する構造体
type ScoreEntry struct {
Name string
Score int
}
func main() {
scores := map[string]int{
"太郎": 85,
"花子": 92,
"次郎": 78,
"美咲": 88,
}
// マップをスライスに変換
entries := make([]ScoreEntry, 0, len(scores))
for name, score := range scores {
entries = append(entries, ScoreEntry{Name: name, Score: score})
}
// スコアの降順でソート
sort.Slice(entries, func(i, j int) bool {
return entries[i].Score > entries[j].Score
})
// ランキング表示
fmt.Println("=== ランキング ===")
for rank, entry := range entries {
fmt.Printf("%d位: %s (%d点)\n", rank+1, entry.Name, entry.Score)
}
}
出力:
=== ランキング ===
1位: 花子 (92点)
2位: 美咲 (88点)
3位: 太郎 (85点)
4位: 次郎 (78点)
---
課題3の解答
問題3-1: 単語カウンター
アプローチ1: 基本実装
package main
import (
"fmt"
"strings"
)
func main() {
text := "apple orange apple banana orange apple"
// スペースで分割
words := strings.Split(text, " ")
// 単語の出現回数をカウント
counter := make(map[string]int)
for _, word := range words {
counter[word]++ // 存在しない場合は0から開始される
}
// 結果を表示
for word, count := range counter {
fmt.Printf("%s: %d回\n", word, count)
}
}
アプローチ2: 大文字小文字を区別しない
package main
import (
"fmt"
"strings"
)
func main() {
text := "Apple orange APPLE Banana Orange apple"
words := strings.Split(text, " ")
counter := make(map[string]int)
for _, word := range words {
// 小文字に正規化してカウント
normalizedWord := strings.ToLower(word)
counter[normalizedWord]++
}
for word, count := range counter {
fmt.Printf("%s: %d回\n", word, count)
}
}
出力:
apple: 3回
orange: 2回
banana: 1回
アプローチ3: 高度な実装(句読点除去、ソート表示)
package main
import (
"fmt"
"sort"
"strings"
"unicode"
)
// 単語を正規化(小文字化、句読点除去)
func normalizeWord(word string) string {
// 句読点を除去
word = strings.TrimFunc(word, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})
// 小文字化
return strings.ToLower(word)
}
func main() {
text := "Hello, world! Hello again. World of Go!"
// 単語に分割
words := strings.Fields(text)
// カウント
counter := make(map[string]int)
for _, word := range words {
normalized := normalizeWord(word)
if normalized != "" { // 空文字列は除外
counter[normalized]++
}
}
// 出現回数でソート
type wordCount struct {
word string
count int
}
entries := make([]wordCount, 0, len(counter))
for word, count := range counter {
entries = append(entries, wordCount{word, count})
}
// 出現回数の降順でソート
sort.Slice(entries, func(i, j int) bool {
if entries[i].count == entries[j].count {
return entries[i].word < entries[j].word
}
return entries[i].count > entries[j].count
})
// 結果表示
fmt.Println("=== 単語の出現頻度 ===")
for _, entry := range entries {
fmt.Printf("%s: %d回\n", entry.word, entry.count)
}
}
出力:
=== 単語の出現頻度 ===
hello: 2回
world: 2回
again: 1回
go: 1回
of: 1回
---
問題3-2: 電話帳
アプローチ1: 基本実装
package main
import "fmt"
func main() {
phoneBook := map[string]string{
"太郎": "090-1111-2222",
"花子": "090-3333-4444",
}
// 追加
phoneBook["次郎"] = "090-5555-6666"
// 検索
name := "花子"
if phone, ok := phoneBook[name]; ok {
fmt.Printf("%s: %s\n", name, phone)
}
// 全件表示
fmt.Println("\n=== 電話帳 ===")
for name, phone := range phoneBook {
fmt.Printf("%s: %s\n", name, phone)
}
}
アプローチ2: 構造体を使った実装
package main
import (
"fmt"
"sort"
)
// 連絡先情報
type Contact struct {
Name string
Phone string
Email string
}
func main() {
// 名前をキーとしたマップ
contacts := map[string]Contact{
"太郎": {Name: "太郎", Phone: "090-1111-2222", Email: "taro@example.com"},
"花子": {Name: "花子", Phone: "090-3333-4444", Email: "hanako@example.com"},
}
// 新規追加
contacts["次郎"] = Contact{
Name: "次郎",
Phone: "090-5555-6666",
Email: "jiro@example.com",
}
// 検索
name := "花子"
if contact, ok := contacts[name]; ok {
fmt.Printf("名前: %s\n", contact.Name)
fmt.Printf("電話: %s\n", contact.Phone)
fmt.Printf("メール: %s\n", contact.Email)
}
// ソート済みで全件表示
names := make([]string, 0, len(contacts))
for name := range contacts {
names = append(names, name)
}
sort.Strings(names)
fmt.Println("\n=== 電話帳(五十音順)===")
for _, name := range names {
contact := contacts[name]
fmt.Printf("%s: %s (%s)\n", contact.Name, contact.Phone, contact.Email)
}
}
アプローチ3: 完全な電話帳システム
package main
import (
"fmt"
"sort"
"strings"
)
type Contact struct {
Name string
Phone string
Email string
}
type PhoneBook struct {
contacts map[string]Contact
}
func NewPhoneBook() *PhoneBook {
return &PhoneBook{
contacts: make(map[string]Contact),
}
}
// 連絡先を追加
func (pb *PhoneBook) Add(contact Contact) {
pb.contacts[contact.Name] = contact
}
// 名前で検索
func (pb *PhoneBook) FindByName(name string) (Contact, bool) {
contact, ok := pb.contacts[name]
return contact, ok
}
// 電話番号で検索
func (pb *PhoneBook) FindByPhone(phone string) []Contact {
var results []Contact
for _, contact := range pb.contacts {
if contact.Phone == phone {
results = append(results, contact)
}
}
return results
}
// 部分一致検索
func (pb *PhoneBook) Search(query string) []Contact {
query = strings.ToLower(query)
var results []Contact
for _, contact := range pb.contacts {
if strings.Contains(strings.ToLower(contact.Name), query) ||
strings.Contains(strings.ToLower(contact.Phone), query) ||
strings.Contains(strings.ToLower(contact.Email), query) {
results = append(results, contact)
}
}
return results
}
// 削除
func (pb *PhoneBook) Delete(name string) bool {
if _, ok := pb.contacts[name]; ok {
delete(pb.contacts, name)
return true
}
return false
}
// 全件表示(ソート済み)
func (pb *PhoneBook) List() {
names := make([]string, 0, len(pb.contacts))
for name := range pb.contacts {
names = append(names, name)
}
sort.Strings(names)
fmt.Println("=== 電話帳 ===")
for _, name := range names {
contact := pb.contacts[name]
fmt.Printf("名前: %s\n", contact.Name)
fmt.Printf("電話: %s\n", contact.Phone)
fmt.Printf("メール: %s\n", contact.Email)
fmt.Println("---")
}
}
func main() {
pb := NewPhoneBook()
// データを追加
pb.Add(Contact{Name: "太郎", Phone: "090-1111-2222", Email: "taro@example.com"})
pb.Add(Contact{Name: "花子", Phone: "090-3333-4444", Email: "hanako@example.com"})
pb.Add(Contact{Name: "次郎", Phone: "090-5555-6666", Email: "jiro@example.com"})
// 全件表示
pb.List()
// 名前で検索
fmt.Println("\n=== 名前検索: 花子 ===")
if contact, found := pb.FindByName("花子"); found {
fmt.Printf("%s: %s\n", contact.Name, contact.Phone)
}
// 部分一致検索
fmt.Println("\n=== 検索: 'ro' を含む ===")
results := pb.Search("ro")
for _, contact := range results {
fmt.Printf("%s: %s\n", contact.Name, contact.Email)
}
// 削除
fmt.Println("\n=== 次郎を削除 ===")
if pb.Delete("次郎") {
fmt.Println("削除しました")
}
pb.List()
}
---
最適化とベストプラクティス
1. 容量の事前確保
// 悪い例
counter := make(map[string]int)
for i := 0; i < 10000; i++ {
counter[fmt.Sprintf("key%d", i)]++
}
// 良い例(サイズがわかっている場合)
counter := make(map[string]int, 10000)
for i := 0; i < 10000; i++ {
counter[fmt.Sprintf("key%d", i)]++
}
2. ゼロ値の活用
// カウンターの実装
counter := make(map[string]int)
for _, word := range words {
// 存在チェック不要!ゼロ値(0)から開始される
counter[word]++
}
3. 安全な削除
// delete()は存在しないキーでも安全
delete(m, "nonexistent") // エラーなし
// 削除前の存在確認が必要な場合
if _, ok := m[key]; ok {
delete(m, key)
fmt.Println("削除しました")
}
---
まとめ
学んだこと
- 基本操作: 追加、取得、更新、削除
- カンマokイディオム: 安全な値の取得
- 反復処理: rangeでの全要素アクセス
- 実践パターン: カウンター、検索、CRUD操作
よくある間違いと対策
| 間違い | 対策 |
|---|---|
| nilマップへの書き込み | makeで初期化 |
| 存在確認なしでアクセス | カンマokイディオム使用 |
| 順序を期待する | キーをソートして反復 |
| 並行アクセス | sync.Mutexを使用 |