Go言語でスライス内を検索する方法:基本から効率的な手法まで

Go

Go言語でプログラミングをしていると、こんな場面によく出会います:

  • 「このユーザーリストの中に、田中さんはいるかな?」
  • 「商品リストの中に、この商品IDは含まれているかな?」
  • 「このスコアは上位10位に入っているかな?」

他の言語(PythonやJavaScriptなど)では、in演算子やincludesメソッドを使って簡単に検索できますが、Go言語にはそのような便利な機能が標準では用意されていません。

でも心配いりません!Go言語には、シンプルで理解しやすい方法で検索機能を自分で作ることができます。

そして、一度作ってしまえば、どんなプロジェクトでも再利用できるのです。

この記事では、「プログラミング初心者」「Go言語を始めたばかり」という方でも、スライス内検索の仕組みが理解できるように、基本的な方法から効率的な手法まで、実際に動かせるコード例とともに丁寧に説明します。

スポンサーリンク

スライスって何?検索って何?

スライスを身近な例で理解しよう

スライスとは、複数のデータを順番に並べて保存できる「データの入れ物」です。

身近な例で考えてみましょう

  • クラスの出席簿 → 生徒の名前が順番に並んでいる
  • 買い物リスト → 買うものが順番に書いてある
  • 音楽プレイリスト → 曲が順番に並んでいる
  • Go言語のスライス → データが順番に並んでいる

検索とは何か?

検索とは、「たくさんのデータの中から、目的のデータを見つけること」です。

身近な例

  • 辞書で単語を探す
  • 電話帳で人の連絡先を探す
  • 図書館で本を探す
  • プログラムでデータを探す

Go言語での基本的なスライス

// 文字列のスライス(名前のリスト)
names := []string{"田中", "佐藤", "山田", "鈴木"}

// 数字のスライス(点数のリスト)
scores := []int{85, 92, 78, 95, 88}

// 年齢のスライス
ages := []int{25, 30, 22, 35, 28}

コードの説明

  • []string:文字列を複数入れられる箱
  • []int:整数を複数入れられる箱
  • {}の中に、実際のデータをカンマで区切って入れる

スライスの基本がわかったら、次は実際に検索機能を作ってみましょう。

基本的な検索機能を作ってみよう

最もシンプルな検索機能

まずは、「指定した要素がスライスに含まれているかどうか」を調べる関数を作ってみましょう:

package main

import "fmt"

// 文字列スライスに特定の文字列が含まれているかチェックする関数
func contains(slice []string, target string) bool {
    for _, value := range slice {
        if value == target {
            return true  // 見つかったらtrueを返す
        }
    }
    return false  // 最後まで見つからなかったらfalseを返す
}

func main() {
    // テスト用のデータ
    names := []string{"田中", "佐藤", "山田", "鈴木"}
    
    // 検索してみる
    fmt.Println("田中さんはいますか?", contains(names, "田中"))     // true
    fmt.Println("高橋さんはいますか?", contains(names, "高橋"))     // false
    fmt.Println("山田さんはいますか?", contains(names, "山田"))     // true
}

実行結果

田中さんはいますか? true
高橋さんはいますか? false
山田さんはいますか? true

コードの仕組みを詳しく解説

func contains(slice []string, target string) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

各部分の説明

  1. 関数の定義
    • func contains:「contains」という名前の関数を作る
    • (slice []string, target string):2つの引数を受け取る
      • slice:検索対象のスライス
      • target:探したい文字列
    • bool:この関数はtrueまたはfalseを返す
  2. forループ
    • for _, value := range slice:スライスの中身を1つずつ取り出す
    • _:インデックス(位置)は使わないので、アンダースコアで無視
    • value:現在見ている要素の値
  3. 条件チェック
    • if value == target:現在の要素が探している文字列と同じか?
    • return true:同じだったら、すぐに「見つかった」を報告
  4. 見つからなかった場合
    • return false:最後まで探しても見つからなかったら「見つからなかった」を報告

より実用的な例

package main

import "fmt"

func contains(slice []string, target string) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

func main() {
    // 会員リスト
    members := []string{"田中太郎", "佐藤花子", "山田一郎", "鈴木美咲"}
    
    // ユーザー入力をシミュレート
    checkNames := []string{"田中太郎", "高橋次郎", "佐藤花子", "伊藤光"}
    
    fmt.Println("=== 会員チェック結果 ===")
    for _, name := range checkNames {
        if contains(members, name) {
            fmt.Printf("✓ %s さんは会員です\n", name)
        } else {
            fmt.Printf("✗ %s さんは会員ではありません\n", name)
        }
    }
}

実行結果

=== 会員チェック結果 ===
✓ 田中太郎 さんは会員です
✗ 高橋次郎 さんは会員ではありません
✓ 佐藤花子 さんは会員です
✗ 伊藤光 さんは会員ではありません

基本的な検索ができるようになったら、次は要素の位置(インデックス)も一緒に取得する方法を学んでみましょう。

要素の位置を知りたい:インデックス検索

なぜ位置が必要?

「要素が含まれているかどうか」だけでなく、「何番目にあるか」も知りたい場面があります:

  • 成績表で「自分は何位?」
  • 待ち行列で「自分は何番目?」
  • 商品リストで「この商品は何番目に表示されている?」

インデックスを返す検索関数

package main

import "fmt"

// 要素の位置(インデックス)を返す関数
func indexOf(slice []string, target string) int {
    for i, value := range slice {
        if value == target {
            return i  // 見つかったら位置を返す
        }
    }
    return -1  // 見つからなかったら-1を返す
}

func main() {
    names := []string{"田中", "佐藤", "山田", "鈴木", "高橋"}
    
    // いろんな名前の位置を調べてみる
    targets := []string{"山田", "鈴木", "伊藤", "田中"}
    
    for _, target := range targets {
        index := indexOf(names, target)
        if index >= 0 {
            fmt.Printf("%s さんは %d 番目にいます\n", target, index+1)  // +1で1番目から開始
        } else {
            fmt.Printf("%s さんは見つかりませんでした\n", target)
        }
    }
}

実行結果

山田 さんは 3 番目にいます
鈴木 さんは 4 番目にいます
伊藤 さんは見つかりませんでした
田中 さんは 1 番目にいます

なぜ-1を返すの?

return -1  // 見つからなかったら-1を返す

理由

  • インデックスは0から始まる(0、1、2、3…)
  • -1は「存在しないインデックス」を表す
  • 多くのプログラミング言語で使われる慣習

より実用的な例:成績ランキング

package main

import "fmt"

func indexOf(slice []string, target string) int {
    for i, value := range slice {
        if value == target {
            return i
        }
    }
    return -1
}

func main() {
    // 成績上位者リスト(1位から順番)
    ranking := []string{"佐藤", "山田", "田中", "鈴木", "高橋", "伊藤", "渡辺"}
    
    // 自分の順位を調べてみる
    myName := "田中"
    rank := indexOf(ranking, myName)
    
    if rank >= 0 {
        fmt.Printf("おめでとうございます!%s さんは %d 位です!\n", myName, rank+1)
        
        // 順位に応じたメッセージ
        switch rank {
        case 0:
            fmt.Println("🥇 1位!素晴らしい成績です!")
        case 1:
            fmt.Println("🥈 2位!とても優秀です!")
        case 2:
            fmt.Println("🥉 3位!上位入賞です!")
        default:
            fmt.Printf("上位 %d 位以内です!\n", len(ranking))
        }
    } else {
        fmt.Printf("残念ながら、%s さんはランキングに入っていません。\n", myName)
    }
}

実行結果

おめでとうございます!田中 さんは 3 位です!
🥉 3位!上位入賞です!

インデックス検索がわかったら、次は数字や構造体など、いろんなデータ型での検索方法を学んでみましょう。

いろんなデータ型での検索

整数スライスの検索

文字列だけでなく、数字のスライスでも検索したいことがあります:

package main

import "fmt"

// 整数スライス用の検索関数
func containsInt(slice []int, target int) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

// 整数スライス用のインデックス検索
func indexOfInt(slice []int, target int) int {
    for i, value := range slice {
        if value == target {
            return i
        }
    }
    return -1
}

func main() {
    // テストの点数
    scores := []int{85, 92, 78, 95, 88, 76, 90}
    
    // 特定の点数があるかチェック
    checkScores := []int{95, 80, 78, 100}
    
    fmt.Println("=== 点数チェック ===")
    for _, score := range checkScores {
        if containsInt(scores, score) {
            index := indexOfInt(scores, score)
            fmt.Printf("%d点を取った人がいます(%d番目)\n", score, index+1)
        } else {
            fmt.Printf("%d点を取った人はいません\n", score)
        }
    }
    
    // 満点や高得点の確認
    fmt.Println("\n=== 成績分析 ===")
    if containsInt(scores, 100) {
        fmt.Println("満点の人がいます!")
    } else {
        fmt.Println("満点の人はいませんでした")
    }
    
    if containsInt(scores, 95) {
        fmt.Println("95点以上の優秀な人がいます!")
    }
}

実行結果

=== 点数チェック ===
95点を取った人がいます(4番目)
80点を取った人はいません
78点を取った人がいます(3番目)
100点を取った人はいません

=== 成績分析 ===
満点の人はいませんでした
95点以上の優秀な人がいます!

構造体での検索

実際のプログラムでは、単純な文字列や数字だけでなく、複数の情報をまとめた「構造体」を検索することが多いです:

package main

import "fmt"

// ユーザー情報を表す構造体
type User struct {
    ID   int
    Name string
    Age  int
    City string
}

// 名前でユーザーを検索する関数
func findUserByName(users []User, name string) *User {
    for i, user := range users {
        if user.Name == name {
            return &users[i]  // 見つかったユーザーのポインタを返す
        }
    }
    return nil  // 見つからなかったらnilを返す
}

// IDでユーザーを検索する関数
func findUserByID(users []User, id int) *User {
    for i, user := range users {
        if user.ID == id {
            return &users[i]
        }
    }
    return nil
}

// 年齢でユーザーをフィルタリングする関数
func findUsersByAge(users []User, age int) []User {
    var result []User
    for _, user := range users {
        if user.Age == age {
            result = append(result, user)
        }
    }
    return result
}

func main() {
    // ユーザーリスト
    users := []User{
        {ID: 1, Name: "田中太郎", Age: 25, City: "東京"},
        {ID: 2, Name: "佐藤花子", Age: 30, City: "大阪"},
        {ID: 3, Name: "山田一郎", Age: 25, City: "名古屋"},
        {ID: 4, Name: "鈴木美咲", Age: 28, City: "東京"},
    }
    
    // 名前で検索
    fmt.Println("=== 名前検索 ===")
    if user := findUserByName(users, "佐藤花子"); user != nil {
        fmt.Printf("見つかりました: %+v\n", *user)
    } else {
        fmt.Println("ユーザーが見つかりません")
    }
    
    // IDで検索
    fmt.Println("\n=== ID検索 ===")
    if user := findUserByID(users, 3); user != nil {
        fmt.Printf("ID3のユーザー: %s さん(%d歳、%s在住)\n", 
                   user.Name, user.Age, user.City)
    }
    
    // 同じ年齢のユーザーを探す
    fmt.Println("\n=== 年齢検索 ===")
    sameAge := findUsersByAge(users, 25)
    fmt.Printf("25歳のユーザー: %d人\n", len(sameAge))
    for _, user := range sameAge {
        fmt.Printf("- %s さん(%s在住)\n", user.Name, user.City)
    }
}

実行結果

=== 名前検索 ===
見つかりました: {ID:2 Name:佐藤花子 Age:30 City:大阪}

=== ID検索 ===
ID3のユーザー: 山田一郎 さん(25歳、名古屋在住)

=== 年齢検索 ===
25歳のユーザー: 2人
- 田中太郎 さん(東京在住)
- 山田一郎 さん(名古屋在住)

複数条件での検索

// 複数の条件でユーザーを検索
func findUsersByCityAndAge(users []User, city string, age int) []User {
    var result []User
    for _, user := range users {
        if user.City == city && user.Age == age {
            result = append(result, user)
        }
    }
    return result
}

func main() {
    users := []User{
        {ID: 1, Name: "田中太郎", Age: 25, City: "東京"},
        {ID: 2, Name: "佐藤花子", Age: 30, City: "大阪"},
        {ID: 3, Name: "山田一郎", Age: 25, City: "名古屋"},
        {ID: 4, Name: "鈴木美咲", Age: 28, City: "東京"},
        {ID: 5, Name: "高橋健太", Age: 25, City: "東京"},
    }
    
    // 東京在住で25歳のユーザーを検索
    tokyoUsers25 := findUsersByCityAndAge(users, "東京", 25)
    
    fmt.Printf("東京在住で25歳のユーザー: %d人\n", len(tokyoUsers25))
    for _, user := range tokyoUsers25 {
        fmt.Printf("- %s さん\n", user.Name)
    }
}

実行結果

東京在住で25歳のユーザー: 2人
- 田中太郎 さん
- 高橋健太 さん

基本的な検索方法がわかったら、次はもっと効率的で高速な検索方法を学んでみましょう。

効率的な検索:マップを使った高速化

問題:大量のデータでの検索が遅い

スライスが大きくなると、forループでの検索は遅くなります:

// 1万人のユーザーの中から1人を探す場合
users := make([]string, 10000)  // 1万人のユーザー
// 最悪の場合、1万回の比較が必要

解決策:マップ(Map)を使う

マップを使うと、検索がとても高速になります:

package main

import (
    "fmt"
    "time"
)

// スライスでの検索(遅い方法)
func containsSlice(slice []string, target string) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

// マップでの検索(速い方法)
func containsMap(mapping map[string]bool, target string) bool {
    return mapping[target]
}

func main() {
    // 大量のデータを準備
    size := 100000  // 10万件のデータ
    
    // スライス版
    slice := make([]string, size)
    for i := 0; i < size; i++ {
        slice[i] = fmt.Sprintf("user_%d", i)
    }
    
    // マップ版
    mapping := make(map[string]bool)
    for i := 0; i < size; i++ {
        mapping[fmt.Sprintf("user_%d", i)] = true
    }
    
    // 検索する要素(最後の方にある要素で最悪ケースをテスト)
    target := "user_99999"
    
    // スライス検索の時間測定
    start := time.Now()
    found1 := containsSlice(slice, target)
    duration1 := time.Since(start)
    
    // マップ検索の時間測定
    start = time.Now()
    found2 := containsMap(mapping, target)
    duration2 := time.Since(start)
    
    fmt.Printf("スライス検索: %v (時間: %v)\n", found1, duration1)
    fmt.Printf("マップ検索: %v (時間: %v)\n", found2, duration2)
    fmt.Printf("速度差: 約 %.0f 倍速い\n", float64(duration1)/float64(duration2))
}

実行結果例

スライス検索: true (時間: 1.234ms)
マップ検索: true (時間: 0.001ms)
速度差: 約 1234 倍速い

実用的なマップの使い方

会員チェックシステム

package main

import "fmt"

// 会員管理システム
type MembershipSystem struct {
    members map[string]bool
}

// 新しい会員システムを作成
func NewMembershipSystem() *MembershipSystem {
    return &MembershipSystem{
        members: make(map[string]bool),
    }
}

// 会員を追加
func (ms *MembershipSystem) AddMember(name string) {
    ms.members[name] = true
    fmt.Printf("✓ %s さんを会員に追加しました\n", name)
}

// 会員を削除
func (ms *MembershipSystem) RemoveMember(name string) {
    delete(ms.members, name)
    fmt.Printf("✗ %s さんを会員から削除しました\n", name)
}

// 会員かどうかチェック
func (ms *MembershipSystem) IsMember(name string) bool {
    return ms.members[name]
}

// 全会員を表示
func (ms *MembershipSystem) ListMembers() {
    fmt.Println("=== 会員一覧 ===")
    for name := range ms.members {
        fmt.Printf("- %s\n", name)
    }
}

func main() {
    // 会員システムを作成
    system := NewMembershipSystem()
    
    // 会員を追加
    system.AddMember("田中太郎")
    system.AddMember("佐藤花子")
    system.AddMember("山田一郎")
    
    // 会員一覧を表示
    system.ListMembers()
    
    // 会員チェック
    fmt.Println("\n=== 会員チェック ===")
    checkNames := []string{"田中太郎", "高橋次郎", "佐藤花子"}
    
    for _, name := range checkNames {
        if system.IsMember(name) {
            fmt.Printf("✓ %s さんは会員です\n", name)
        } else {
            fmt.Printf("✗ %s さんは会員ではありません\n", name)
        }
    }
    
    // 会員を削除
    fmt.Println()
    system.RemoveMember("山田一郎")
    
    // 削除後の確認
    if system.IsMember("山田一郎") {
        fmt.Println("山田一郎さんはまだ会員です")
    } else {
        fmt.Println("山田一郎さんは会員ではありません")
    }
}

実行結果

✓ 田中太郎 さんを会員に追加しました
✓ 佐藤花子 さんを会員に追加しました
✓ 山田一郎 さんを会員に追加しました
=== 会員一覧 ===
- 田中太郎
- 佐藤花子
- 山田一郎

=== 会員チェック ===
✓ 田中太郎 さんは会員です
✗ 高橋次郎 さんは会員ではありません
✓ 佐藤花子 さんは会員です

✗ 山田一郎 さんを会員から削除しました
山田一郎さんは会員ではありません

より高度なマップの活用

カウンター機能付きマップ

package main

import "fmt"

func main() {
    // 文字の出現回数をカウント
    text := "hello world"
    charCount := make(map[rune]int)
    
    for _, char := range text {
        charCount[char]++
    }
    
    fmt.Println("=== 文字の出現回数 ===")
    for char, count := range charCount {
        if char != ' ' {  // スペースは除外
            fmt.Printf("'%c': %d回\n", char, count)
        }
    }
    
    // 特定の文字の出現回数をチェック
    fmt.Printf("\n'l'の出現回数: %d回\n", charCount['l'])
    fmt.Printf("'o'の出現回数: %d回\n", charCount['o'])
}

実行結果

=== 文字の出現回数 ===
'h': 1回
'e': 1回
'l': 3回
'o': 2回
'w': 1回
'r': 1回
'd': 1回

'l'の出現回数: 3回
'o'の出現回数: 2回

マップを使った効率的な検索方法がわかったら、最後に実際のプロジェクトで使える汎用的な検索機能を作ってみましょう。

実用的な検索ライブラリを作ろう

汎用的な検索関数の作成

Go 1.18以降では、ジェネリクス(総称型)を使って、どんな型でも使える汎用的な関数を作ることができます:

package main

import "fmt"

// ジェネリクスを使った汎用的な検索関数
func Contains[T comparable](slice []T, target T) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

func IndexOf[T comparable](slice []T, target T) int {
    for i, value := range slice {
        if value == target {
            return i
        }
    }
    return -1
}

// 複数の要素を検索
func ContainsAny[T comparable](slice []T, targets ...T) bool {
    for _, target := range targets {
        if Contains(slice, target) {
            return true
        }
    }
    return false
}

// すべての要素が含まれているかチェック
func ContainsAll[T comparable](slice []T, targets ...T) bool {
    for _, target := range targets {
        if !Contains(slice, target) {
            return false
        }
    }
    return true
}

func main() {
    // 文字列での使用例
    fruits := []string{"りんご", "バナナ", "みかん", "ぶどう"}
    
    fmt.Println("=== 果物検索 ===")
    fmt.Println("りんごはありますか?", Contains(fruits, "りんご"))
    fmt.Println("いちごはありますか?", Contains(fruits, "いちご"))
    fmt.Println("みかんは何番目?", IndexOf(fruits, "みかん")+1)
    
    // 複数検索
    fmt.Println("りんごかバナナはありますか?", ContainsAny(fruits, "りんご", "バナナ"))
    fmt.Println("りんごとバナナ両方ありますか?", ContainsAll(fruits, "りんご", "バナナ"))
    fmt.Println("いちごとメロン両方ありますか?", ContainsAll(fruits, "いちご", "メロン"))
    
    // 数値での使用例
    numbers := []int{1, 3, 5, 7, 9}
    
    fmt.Println("\n=== 数値検索 ===")
    fmt.Println("5はありますか?", Contains(numbers, 5))
    fmt.Println("6はありますか?", Contains(numbers, 6))
    fmt.Println("7は何番目?", IndexOf(numbers, 7)+1)
    
    // 偶数または奇数の検索
    fmt.Println("偶数(2,4,6)のいずれかはありますか?", ContainsAny(numbers, 2, 4, 6))
    fmt.Println("1と9両方ありますか?", ContainsAll(numbers, 1, 9))
}

実行結果

=== 果物検索 ===
りんごはありますか? true
いちごはありますか? false
みかんは何番目? 3

りんごかバナナはありますか? true
りんごとバナナ両方ありますか? true
いちごとメロン両方ありますか? false

=== 数値検索 ===
5はありますか? true
6はありますか? false
7は何番目? 4

偶数(2,4,6)のいずれかはありますか? false
1と9両方ありますか? true

条件付き検索機能

特定の条件を満たす要素を検索する機能も作ってみましょう:

package main

import "fmt"

// 条件を満たす最初の要素を検索
func Find[T any](slice []T, condition func(T) bool) *T {
    for i, value := range slice {
        if condition(value) {
            return &slice[i]
        }
    }
    return nil
}

// 条件を満たすすべての要素を検索
func Filter[T any](slice []T, condition func(T) bool) []T {
    var result []T
    for _, value := range slice {
        if condition(value) {
            result = append(result, value)
        }
    }
    return result
}

// 条件を満たす要素の数をカウント
func Count[T any](slice []T, condition func(T) bool) int {
    count := 0
    for _, value := range slice {
        if condition(value) {
            count++
        }
    }
    return count
}

// 学生の情報を表す構造体
type Student struct {
    Name  string
    Score int
    Grade string
}

func main() {
    students := []Student{
        {Name: "田中", Score: 85, Grade: "A"},
        {Name: "佐藤", Score: 92, Grade: "S"},
        {Name: "山田", Score: 78, Grade: "B"},
        {Name: "鈴木", Score: 95, Grade: "S"},
        {Name: "高橋", Score: 67, Grade: "C"},
    }
    
    // 90点以上の学生を検索
    fmt.Println("=== 90点以上の学生 ===")
    highScorers := Filter(students, func(s Student) bool {
        return s.Score >= 90
    })
    
    for _, student := range highScorers {
        fmt.Printf("%s: %d点 (評価: %s)\n", student.Name, student.Score, student.Grade)
    }
    
    // 最初に見つかるS評価の学生
    fmt.Println("\n=== 最初のS評価学生 ===")
    if topStudent := Find(students, func(s Student) bool {
        return s.Grade == "S"
    }); topStudent != nil {
        fmt.Printf("最初のS評価: %s (%d点)\n", topStudent.Name, topStudent.Score)
    }
    
    // 各評価の人数をカウント
    fmt.Println("\n=== 評価別人数 ===")
    grades := []string{"S", "A", "B", "C"}
    for _, grade := range grades {
        count := Count(students, func(s Student) bool {
            return s.Grade == grade
        })
        fmt.Printf("%s評価: %d人\n", grade, count)
    }
    
    // 数値スライスでの例
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    fmt.Println("\n=== 数値の条件検索 ===")
    
    // 偶数のみを検索
    evenNumbers := Filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Printf("偶数: %v\n", evenNumbers)
    
    // 5より大きい数のカウント
    countAbove5 := Count(numbers, func(n int) bool {
        return n > 5
    })
    fmt.Printf("5より大きい数: %d個\n", countAbove5)
    
    // 最初の3の倍数を検索
    if firstMultiple3 := Find(numbers, func(n int) bool {
        return n%3 == 0
    }); firstMultiple3 != nil {
        fmt.Printf("最初の3の倍数: %d\n", *firstMultiple3)
    }
}

実行結果

=== 90点以上の学生 ===
佐藤: 92点 (評価: S)
鈴木: 95点 (評価: S)

=== 最初のS評価学生 ===
最初のS評価: 佐藤 (92点)

=== 評価別人数 ===
S評価: 2人
A評価: 1人
B評価: 1人
C評価: 1人

=== 数値の条件検索 ===
偶数: [2 4 6 8 10]
5より大きい数: 5個
最初の3の倍数: 3

実用的な検索ツールキット

最後に、実際のプロジェクトで使える検索ツールキットを作ってみましょう:

package main

import (
    "fmt"
    "strings"
    "time"
)

// 商品情報
type Product struct {
    ID       int
    Name     string
    Category string
    Price    int
    InStock  bool
}

// 商品検索システム
type ProductSearcher struct {
    products []Product
    nameMap  map[string]*Product  // 名前による高速検索用
    idMap    map[int]*Product     // IDによる高速検索用
}

// 新しい検索システムを作成
func NewProductSearcher(products []Product) *ProductSearcher {
    ps := &ProductSearcher{
        products: products,
        nameMap:  make(map[string]*Product),
        idMap:    make(map[int]*Product),
    }
    
    // インデックスを構築(高速検索のため)
    for i := range products {
        ps.nameMap[products[i].Name] = &products[i]
        ps.idMap[products[i].ID] = &products[i]
    }
    
    return ps
}

// IDで検索(高速)
func (ps *ProductSearcher) FindByID(id int) *Product {
    return ps.idMap[id]
}

// 名前で検索(高速)
func (ps *ProductSearcher) FindByName(name string) *Product {
    return ps.nameMap[name]
}

// 名前による部分一致検索
func (ps *ProductSearcher) SearchByName(keyword string) []Product {
    var results []Product
    keyword = strings.ToLower(keyword)
    
    for _, product := range ps.products {
        if strings.Contains(strings.ToLower(product.Name), keyword) {
            results = append(results, product)
        }
    }
    return results
}

// カテゴリで絞り込み
func (ps *ProductSearcher) FilterByCategory(category string) []Product {
    var results []Product
    for _, product := range ps.products {
        if product.Category == category {
            results = append(results, product)
        }
    }
    return results
}

// 価格範囲で絞り込み
func (ps *ProductSearcher) FilterByPriceRange(minPrice, maxPrice int) []Product {
    var results []Product
    for _, product := range ps.products {
        if product.Price >= minPrice && product.Price <= maxPrice {
            results = append(results, product)
        }
    }
    return results
}

// 在庫ありの商品のみ
func (ps *ProductSearcher) FilterInStock() []Product {
    var results []Product
    for _, product := range ps.products {
        if product.InStock {
            results = append(results, product)
        }
    }
    return results
}

// 複合検索(カテゴリ + 価格範囲 + 在庫状況)
func (ps *ProductSearcher) AdvancedSearch(category string, minPrice, maxPrice int, inStockOnly bool) []Product {
    var results []Product
    
    for _, product := range ps.products {
        // カテゴリチェック
        if category != "" && product.Category != category {
            continue
        }
        
        // 価格範囲チェック
        if product.Price < minPrice || product.Price > maxPrice {
            continue
        }
        
        // 在庫チェック
        if inStockOnly && !product.InStock {
            continue
        }
        
        results = append(results, product)
    }
    
    return results
}

func main() {
    // サンプル商品データ
    products := []Product{
        {ID: 1, Name: "iPhone 15", Category: "スマートフォン", Price: 120000, InStock: true},
        {ID: 2, Name: "MacBook Air", Category: "ノートPC", Price: 150000, InStock: true},
        {ID: 3, Name: "iPad Pro", Category: "タブレット", Price: 100000, InStock: false},
        {ID: 4, Name: "Apple Watch", Category: "ウェアラブル", Price: 50000, InStock: true},
        {ID: 5, Name: "AirPods Pro", Category: "オーディオ", Price: 30000, InStock: true},
        {ID: 6, Name: "Galaxy S24", Category: "スマートフォン", Price: 110000, InStock: true},
        {ID: 7, Name: "Surface Laptop", Category: "ノートPC", Price: 140000, InStock: false},
    }
    
    // 検索システムを初期化
    searcher := NewProductSearcher(products)
    
    // ID検索のテスト
    fmt.Println("=== ID検索 ===")
    if product := searcher.FindByID(1); product != nil {
        fmt.Printf("ID1の商品: %s (¥%d)\n", product.Name, product.Price)
    }
    
    // 名前検索のテスト
    fmt.Println("\n=== 名前検索 ===")
    if product := searcher.FindByName("iPhone 15"); product != nil {
        fmt.Printf("iPhone 15: %s (¥%d)\n", product.Name, product.Price)
    }
    
    // 部分一致検索
    fmt.Println("\n=== 部分一致検索('Pro'を含む商品) ===")
    proProducts := searcher.SearchByName("Pro")
    for _, product := range proProducts {
        fmt.Printf("- %s (¥%d)\n", product.Name, product.Price)
    }
    
    // カテゴリ検索
    fmt.Println("\n=== カテゴリ検索(スマートフォン) ===")
    smartphones := searcher.FilterByCategory("スマートフォン")
    for _, product := range smartphones {
        status := "在庫あり"
        if !product.InStock {
            status = "在庫なし"
        }
        fmt.Printf("- %s (¥%d) - %s\n", product.Name, product.Price, status)
    }
    
    // 価格範囲検索
    fmt.Println("\n=== 価格範囲検索(5万円~12万円) ===")
    priceRangeProducts := searcher.FilterByPriceRange(50000, 120000)
    for _, product := range priceRangeProducts {
        fmt.Printf("- %s (¥%d)\n", product.Name, product.Price)
    }
    
    // 在庫ありの商品のみ
    fmt.Println("\n=== 在庫ありの商品 ===")
    inStockProducts := searcher.FilterInStock()
    fmt.Printf("在庫ありの商品数: %d個\n", len(inStockProducts))
    
    // 複合検索
    fmt.Println("\n=== 複合検索(在庫ありで10万円以下) ===")
    affordableInStock := searcher.AdvancedSearch("", 0, 100000, true)
    for _, product := range affordableInStock {
        fmt.Printf("- %s (%s) - ¥%d\n", product.Name, product.Category, product.Price)
    }
    
    // パフォーマンステスト
    fmt.Println("\n=== パフォーマンステスト ===")
    start := time.Now()
    for i := 0; i < 10000; i++ {
        searcher.FindByID(1)  // 1万回のID検索
    }
    duration := time.Since(start)
    fmt.Printf("ID検索1万回の実行時間: %v\n", duration)
}

実行結果

=== ID検索 ===
ID1の商品: iPhone 15 (¥120000)

=== 名前検索 ===
iPhone 15: iPhone 15 (¥120000)

=== 部分一致検索('Pro'を含む商品) ===
- iPad Pro (¥100000)
- AirPods Pro (¥30000)

=== カテゴリ検索(スマートフォン) ===
- iPhone 15 (¥120000) - 在庫あり
- Galaxy S24 (¥110000) - 在庫あり

=== 価格範囲検索(5万円~12万円) ===
- iPhone 15 (¥120000)
- iPad Pro (¥100000)
- Apple Watch (¥50000)
- Galaxy S24 (¥110000)

=== 在庫ありの商品 ===
在庫ありの商品数: 5個

=== 複合検索(在庫ありで10万円以下) ===
- Apple Watch (ウェアラブル) - ¥50000
- AirPods Pro (オーディオ) - ¥30000

=== パフォーマンステスト ===
ID検索1万回の実行時間: 245.5µs

まとめ

Go言語でのスライス検索について、基本から応用まで詳しく解説しました。

この記事で学んだこと

基本的な検索

  • for rangeループを使った要素の存在確認
  • インデックス検索で要素の位置を取得
  • 文字列、数値、構造体など様々なデータ型での検索

効率的な検索

  • マップを使った高速検索(O(1)の時間計算量)
  • 大量データでのパフォーマンス向上
  • インデックス構築による検索速度の最適化

高度な検索機能

  • ジェネリクスを使った汎用的な検索関数
  • 条件付き検索(Filter、Find、Count)
  • 複合検索条件の実装
  • 実用的な商品検索システムの構築

覚えておきたい基本パターン

// 基本的な存在確認
func contains(slice []string, target string) bool {
    for _, value := range slice {
        if value == target {
            return true
        }
    }
    return false
}

// インデックス検索
func indexOf(slice []string, target string) int {
    for i, value := range slice {
        if value == target {
            return i
        }
    }
    return -1
}

// マップを使った高速検索
mapping := make(map[string]bool)
mapping["key"] = true
exists := mapping["key"]  // 高速

コメント

タイトルとURLをコピーしました