Clojure Webアプリケーションで使いそうな関数ハンズオン


## シーケンス操作関数

### 1. `take` - 先頭からN個取得
**概要**: シーケンスの先頭からN個の要素を取得

```clojure
;; 基本例
(take 3 [1 2 3 4 5])
; => (1 2 3)

(take 2 "hello")
; => (\h \e)
```

**ハンズオン演習1**:
```clojure
;; ユーザーリストから最新の3人を取得
(def users ["田中太郎" "佐藤花子" "鈴木次郎" "高橋一郎" "渡辺美咲"])

;; TODO: 最初の3人を取得してください
(def first-three-users (take ??? users))

;; 無限シーケンスでの使用例
(def fibonacci (lazy-seq (cons 0 (cons 1 (map + fibonacci (rest fibonacci))))))

;; TODO: フィボナッチ数列の最初の10個を取得してください
(def first-ten-fib (take ??? fibonacci))
```

### 2. `drop` - 先頭からN個削除
**概要**: シーケンスの先頭からN個の要素を削除

```clojure
;; 基本例
(drop 2 [1 2 3 4 5])
; => (3 4 5)
```

**ハンズオン演習2**:
```clojure
(def page-data [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15])

;; TODO: ページネーション - 最初の5件をスキップして残りを取得
(def skip-first-page (drop ??? page-data))

;; TODO: 2ページ目（6-10件目）を取得してください
(def second-page (->> page-data
                      (drop ???)
                      (take ???)))
```

### 3. `take-while` / `drop-while` - 条件による取得/削除
**概要**: 条件を満たす間だけ取得/削除

```clojure
;; 基本例
(take-while pos? [1 2 3 -4 5])
; => (1 2 3)

(drop-while neg? [-1 -2 3 4 -5])
; => (3 4 -5)
```

**ハンズオン演習3**:
```clojure
(def scores [95 88 92 65 78 84 91])

;; TODO: 80点以上が続く限り取得してください
(def high-scores (take-while ??? scores))

;; TODO: 80点未満をスキップして残りを取得してください
(def passing-scores (drop-while ??? scores))
```

---

## マップ操作関数

### 4. `update` - マップの値を更新
**概要**: マップの特定のキーの値を関数で更新

```clojure
;; 基本例
(def user {:name "田中" :age 25 :score 100})
(update user :age inc)
; => {:name "田中", :age 26, :score 100}

(update user :score + 50)
; => {:name "田中", :age 25, :score 150}
```

**ハンズオン演習4**:
```clojure
(def player {:name "プレイヤー1" :level 5 :exp 1200 :hp 100 :items ["剣" "盾"]})

;; TODO: レベルを1上げてください
(def leveled-up (update player ??? ???))

;; TODO: HPを50回復してください
(def healed (update player ??? ??? ???))

;; TODO: アイテムに"ポーション"を追加してください
(def with-potion (update player ??? ??? "ポーション"))
```

### 5. `update-in` - ネストしたマップの値を更新
**概要**: ネストしたデータ構造の深い位置にある値を更新

```clojure
;; 基本例
(def data {:user {:profile {:name "田中" :age 25}}})
(update-in data [:user :profile :age] inc)
; => {:user {:profile {:name "田中", :age 26}}}
```

**ハンズオン演習5**:
```clojure
(def game-state 
  {:player {:stats {:hp 100 :mp 50 :level 5}
            :inventory {:items ["剣" "盾"] :gold 1000}}
   :enemies [{:name "スライム" :hp 30} {:name "ゴブリン" :hp 50}]})

;; TODO: プレイヤーのHPを10回復してください
(def hp-recovered (update-in game-state ??? ???))

;; TODO: プレイヤーの所持金を500増やしてください
(def gold-increased (update-in game-state ??? ??? ???))

;; TODO: 最初の敵のHPを10減らしてください
(def enemy-damaged (update-in game-state ??? ???))
```

### 6. `assoc` / `assoc-in` - 値の設定
**概要**: マップに新しいキー・値のペアを設定

```clojure
;; 基本例
(assoc {:a 1} :b 2)
; => {:a 1, :b 2}

(assoc-in {} [:a :b :c] "deep value")
; => {:a {:b {:c "deep value"}}}
```

**ハンズオン演習6**:
```clojure
(def user-profile {:id 1 :name "田中太郎"})

;; TODO: emailフィールドを追加してください
(def with-email (assoc user-profile ??? ???))

;; TODO: 設定情報をネストして追加してください
;; 結果: {:id 1 :name "田中太郎" :settings {:theme "dark" :notifications true}}
(def with-settings (assoc-in user-profile ??? ???))
```

---

## 条件分岐・フィルタリング

### 7. `filter` - 条件に合う要素を抽出
**概要**: 述語関数がtrueを返す要素のみを抽出

```clojure
;; 基本例
(filter even? [1 2 3 4 5 6])
; => (2 4 6)

(filter #(> % 10) [5 12 8 15 3 20])
; => (12 15 20)
```

**ハンズオン演習7**:
```clojure
(def products 
  [{:name "iPhone" :price 100000 :category "electronics"}
   {:name "MacBook" :price 200000 :category "electronics"}
   {:name "コーヒー" :price 500 :category "food"}
   {:name "本" :price 1500 :category "books"}
   {:name "iPad" :price 80000 :category "electronics"}])

;; TODO: 50000円以上の商品を抽出してください
(def expensive-products (filter ??? products))

;; TODO: electronicsカテゴリの商品を抽出してください
(def electronics (filter ??? products))

;; TODO: 名前に"i"が含まれる商品を抽出してください
(def with-i (filter ??? products))
```

### 8. `remove` - 条件に合わない要素を抽出
**概要**: filterの逆で、述語関数がfalseを返す要素を抽出

```clojure
;; 基本例
(remove even? [1 2 3 4 5 6])
; => (1 3 5)
```

**ハンズオン演習8**:
```clojure
(def numbers [1 2 3 4 5 6 7 8 9 10])

;; TODO: 偶数を除外してください
(def odd-numbers (remove ??? numbers))

;; TODO: 5以下の数を除外してください
(def greater-than-five (remove ??? numbers))
```

---

## 変換・集約関数

### 9. `map` - 各要素に関数を適用
**概要**: シーケンスの各要素に関数を適用して新しいシーケンスを作成

```clojure
;; 基本例
(map inc [1 2 3 4 5])
; => (2 3 4 5 6)

(map #(* % 2) [1 2 3 4 5])
; => (2 4 6 8 10)
```

**ハンズオン演習9**:
```clojure
(def users 
  [{:name "田中" :age 25 :salary 400000}
   {:name "佐藤" :age 30 :salary 500000}
   {:name "鈴木" :age 28 :salary 450000}])

;; TODO: 名前だけのリストを作成してください
(def names (map ??? users))

;; TODO: 年収を1.1倍（昇給）したユーザーリストを作成してください
(def with-raise (map ??? users))

;; TODO: 自己紹介文を作成してください（例："田中さん（25歳）"）
(def introductions (map ??? users))
```

### 10. `reduce` - シーケンスを単一の値に集約
**概要**: シーケンスの要素を順次処理して単一の値を生成

```clojure
;; 基本例
(reduce + [1 2 3 4 5])
; => 15

(reduce str ["Hello" " " "World" "!"])
; => "Hello World!"
```

**ハンズオン演習10**:
```clojure
(def orders 
  [{:id 1 :amount 1000 :status "completed"}
   {:id 2 :amount 1500 :status "completed"}
   {:id 3 :amount 800 :status "pending"}
   {:id 4 :amount 2000 :status "completed"}])

;; TODO: 完了した注文の合計金額を計算してください
(def total-completed 
  (->> orders
       (filter ???)
       (map ???)
       (reduce ???)))

;; TODO: 注文IDのリストを文字列で結合してください（例："1,2,3,4"）
(def order-ids-string
  (->> orders
       (map ???)
       (map str)
       (reduce ???)))
```

### 11. `group-by` - 条件でグループ分け
**概要**: 関数の結果に基づいて要素をグループ分け

```clojure
;; 基本例
(group-by even? [1 2 3 4 5 6])
; => {false [1 3 5], true [2 4 6]}

(group-by count ["a" "bb" "ccc" "dd"])
; => {1 ["a"], 2 ["bb" "dd"], 3 ["ccc"]}
```

**ハンズオン演習11**:
```clojure
(def employees
  [{:name "田中" :department "engineering" :level "senior"}
   {:name "佐藤" :department "sales" :level "junior"}
   {:name "鈴木" :department "engineering" :level "junior"}
   {:name "高橋" :department "marketing" :level "senior"}
   {:name "渡辺" :department "sales" :level "senior"}])

;; TODO: 部署別にグループ分けしてください
(def by-department (group-by ??? employees))

;; TODO: レベル別にグループ分けしてください
(def by-level (group-by ??? employees))
```

---

## 実践演習

### Webアプリケーション実践例

```clojure
;; ユーザー管理システム
(def users-db
  [{:id 1 :name "田中太郎" :email "tanaka@example.com" :role "admin" :active true :created-at "2023-01-15"}
   {:id 2 :name "佐藤花子" :email "sato@example.com" :role "user" :active true :created-at "2023-02-20"}
   {:id 3 :name "鈴木次郎" :email "suzuki@example.com" :role "user" :active false :created-at "2023-01-10"}
   {:id 4 :name "高橋一郎" :email "takahashi@example.com" :role "moderator" :active true :created-at "2023-03-05"}])

;; 1. ページネーション機能を実装してください
(defn paginate [items page-size page-number]
  ;; TODO: 指定されたページの項目を返す関数を実装
  )

;; 2. ユーザー検索機能を実装してください
(defn search-users [users search-term]
  ;; TODO: 名前またはメールに検索語が含まれるユーザーを返す
  )

;; 3. ユーザーのアクティブ状態を切り替える機能
(defn toggle-user-active [users user-id]
  ;; TODO: 指定されたユーザーのactiveフラグを反転させる
  )

;; 4. 統計情報を計算する機能
(defn user-statistics [users]
  ;; TODO: 以下の統計を含むマップを返す
  ;; - 総ユーザー数
  ;; - アクティブユーザー数
  ;; - 役職別ユーザー数
  )

;; 5. APIレスポンス形式に変換
(defn format-user-response [user]
  ;; TODO: セキュリティのため、公開してよい情報のみを含むマップに変換
  ;; 含めるフィールド: id, name, role, active
  )
```

### 解答例

```clojure
;; 解答例（実際に試してみてください）

;; 1. ページネーション
(defn paginate [items page-size page-number]
  (->> items
       (drop (* page-size (dec page-number)))
       (take page-size)))

;; 使用例
(paginate users-db 2 1) ; 最初の2件
(paginate users-db 2 2) ; 次の2件

;; 2. 検索機能
(defn search-users [users search-term]
  (let [search-lower (clojure.string/lower-case search-term)]
    (filter (fn [user]
              (or (clojure.string/includes? 
                   (clojure.string/lower-case (:name user)) 
                   search-lower)
                  (clojure.string/includes? 
                   (clojure.string/lower-case (:email user)) 
                   search-lower)))
            users)))

;; 3. アクティブ状態切り替え
(defn toggle-user-active [users user-id]
  (map (fn [user]
         (if (= (:id user) user-id)
           (update user :active not)
           user))
       users))

;; 4. 統計情報
(defn user-statistics [users]
  {:total-users (count users)
   :active-users (count (filter :active users))
   :users-by-role (group-by :role users)
   :role-counts (reduce-kv (fn [acc role users] 
                            (assoc acc role (count users))) 
                          {} 
                          (group-by :role users))})

;; 5. APIレスポンス形式
(defn format-user-response [user]
  (select-keys user [:id :name :role :active]))

;; すべてのユーザーを公開形式に変換
(def public-users (map format-user-response users-db))
```

### 高度な演習

```clojure
;; ショッピングカート機能
(def cart-items
  [{:product-id 1 :name "iPhone" :price 100000 :quantity 1}
   {:product-id 2 :name "MacBook" :price 200000 :quantity 1}
   {:product-id 3 :name "AirPods" :price 30000 :quantity 2}])

;; TODO: 以下の機能を実装してください

;; 1. カート内の商品数量を更新
(defn update-quantity [cart product-id new-quantity])

;; 2. カートの合計金額を計算
(defn calculate-total [cart])

;; 3. 商品をカートから削除
(defn remove-item [cart product-id])

;; 4. カートに商品を追加（既存商品は数量を加算）
(defn add-to-cart [cart product])

;; 5. 在庫チェック機能
(defn check-availability [cart inventory])
```

このハンズオン教材を使って、Clojureのwebアプリケーション開発で重要な関数を実際に動かしながら学習してください。REPLで一つずつ試してみることをお勧めします！