1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Clojure- map関数と基本構文

1
Last updated at Posted at 2025-06-04

1. 基本的な構文構造

リスト記法(前置記法)

(関数名 引数1 引数2 引数3 ...)

視覚的表現:

    ┌─────────────────────────────┐
    │  (+ 1 2 3)                  │
    │   │ │ │ │                   │
    │   │ └─┴─┴── 引数          │
    │   └─────── 関数             │
    └─────────────────────────────┘

2. map関数の構文

基本形

(map 関数 コレクション)

(map inc [1 2 3 4])
;; => (2 3 4 5)

視覚的な動作:

入力: [1 2 3 4]
      │ │ │ │
      ▼ ▼ ▼ ▼
     inc inc inc inc
      │ │ │ │
      ▼ ▼ ▼ ▼
出力: (2 3 4 5)

3. map関数の詳細パターン

パターン1: 単一関数、単一コレクション

(map #(* % 2) [1 2 3 4])
;; => (2 4 6 8)
[1 2 3 4] ──┐
            │    map
        #(* % 2)──┘
            │
            ▼
        (2 4 6 8)

パターン2: 複数コレクション

(map + [1 2 3] [4 5 6])
;; => (5 7 9)
[1 2 3] ──┐
          │ map +
[4 5 6] ──┘
          │
          ▼
       (5 7 9)

詳細:
1+4=5, 2+5=7, 3+6=9

パターン3: 無名関数の使用

(map (fn [x] (* x x)) [1 2 3 4])
;; => (1 4 9 16)

4. 関連する高階関数

filter - 条件に合う要素を抽出

(filter even? [1 2 3 4 5 6])
;; => (2 4 6)
[1 2 3 4 5 6]
 │ │ │ │ │ │
 ▼ ▼ ▼ ▼ ▼ ▼
even? チェック
 × ○ × ○ × ○
   │   │   │
   ▼   ▼   ▼
  (2   4   6)

reduce - コレクションを単一の値に集約

(reduce + [1 2 3 4])
;; => 10

5. 実用的な例

データ変換の例

;; 人のデータを変換
(def people [{:name "田中" :age 25}
             {:name "佐藤" :age 30}
             {:name "鈴木" :age 35}])

;; 名前だけを抽出
(map :name people)
;; => ("田中" "佐藤" "鈴木")

;; 年齢を5歳増やす
(map #(update % :age + 5) people)
;; => ({:name "田中", :age 30} 
;;     {:name "佐藤", :age 35} 
;;     {:name "鈴木", :age 40})

チェーン処理の例

(->> [1 2 3 4 5 6 7 8 9 10]
     (filter odd?)        ;; 奇数のみ
     (map #(* % %))       ;; 二乗
     (reduce +))          ;; 合計
;; => 165

視覚的な流れ:

[1 2 3 4 5 6 7 8 9 10]
         │
    filter odd?
         │
    [1 3 5 7 9]
         │
   map #(* % %)
         │
   [1 9 25 49 81]
         │
     reduce +
         │
        165

6. 無名関数の記法

3つの記法

;; 1. fn記法
(map (fn [x] (* x 2)) [1 2 3])

;; 2. ショートハンド記法
(map #(* % 2) [1 2 3])

;; 3. 名前付き関数
(defn double [x] (* x 2))
(map double [1 2 3])

ショートハンド記法の詳細

#(...)     ;; 無名関数の開始
%          ;; 第1引数
%1         ;; 第1引数(明示的)
%2         ;; 第2引数
%&         ;; 残りの引数(可変長)

7. 実践的な使用パターン

パターン1: データクリーニング

(->> ["  hello  " "  world  " "  clojure  "]
     (map clojure.string/trim)
     (map clojure.string/upper-case))
;; => ("HELLO" "WORLD" "CLOJURE")

パターン2: ネストしたデータの処理

(def orders [{:items [{:name "商品A" :price 100}
                      {:name "商品B" :price 200}]}
             {:items [{:name "商品C" :price 150}]}])

;; 各注文の合計金額を計算
(map (fn [order]
       (->> (:items order)
            (map :price)
            (reduce +)))
     orders)
;; => (300 150)

8. パフォーマンスと遅延評価

遅延シーケンス

;; mapは遅延評価される
(def lazy-seq (map println [1 2 3 4 5]))
;; この時点では何も出力されない

;; 実際に値が必要になったときに評価
(doall lazy-seq)
;; この時点で1 2 3 4 5が出力される

無限シーケンス

;; 無限の数列
(def fibonacci-seq
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

;; 最初の10個を取得
(take 10 fibonacci-seq)
;; => (0 1 1 2 3 5 8 13 21 34)

9. mapの返り値の型について

重要なポイント:mapは常に遅延シーケンスを返す

;; 入力がベクターでも
(map inc [1 2 3])
;; => (2 3 4)  ← これは遅延シーケンス(lazy sequence)

;; 入力がリストでも
(map inc '(1 2 3))
;; => (2 3 4)  ← これも遅延シーケンス

;; 型を確認
(type (map inc [1 2 3]))
;; => clojure.lang.LazySeq

視覚的な比較

入力の型     │  map処理   │  出力の型
─────────────────────────────────────
[1 2 3]   │    map     │  LazySeq
ベクター     │    inc     │  (2 3 4)
            │            │
'(1 2 3)    │    map     │  LazySeq  
リスト       │    inc     │  (2 3 4)
            │            │
#{1 2 3}    │    map     │  LazySeq
セット       │    inc     │  (2 3 4)

10. Clojureのコレクション型の違い

基本的な4つのコレクション型

1. ベクター(Vector)[]

[1 2 3 4]

特徴:

  • インデックスアクセスが高速 O(1)
  • 末尾への追加が高速
  • 順序あり
  • 重複あり

視覚的構造:

インデックス: 0  1  2  3
ベクター:    [1][2][3][4]
             ↑  ↑  ↑  ↑
          高速アクセス可能

2. リスト(List)'()

'(1 2 3 4)

特徴:

  • 先頭への追加が高速 O(1)
  • 先頭アクセスが高速、末尾は遅い O(n)
  • 順序あり
  • 重複あり

視覚的構造:

'(1 2 3 4)
  ↓ ↓ ↓ ↓
  1→2→3→4→nil
  ↑
先頭アクセス高速

3. セット(Set)#{}

#{1 2 3 4}

特徴:

  • 重複なし
  • 順序なし(ハッシュセット)
  • メンバーシップテストが高速 O(1)

視覚的構造:

#{1 2 3 4}
┌─────────────┐
│  3   1      │  ← 順序は保証されない
│     4   2   │
└─────────────┘

4. マップ(Map){}

{:a 1 :b 2 :c 3}

特徴:

  • キー・値のペア
  • キーによる高速検索 O(1)
  • 順序なし(ハッシュマップ)

視覚的構造:

{:a 1, :b 2, :c 3}
 ↓     ↓     ↓
:a ──→ 1
:b ──→ 2  
:c ──→ 3

5. 遅延シーケンス(Lazy Sequence)

(map inc [1 2 3])  ; これが遅延シーケンス

特徴:

  • 必要時に計算される
  • 無限シーケンスを扱える
  • メモリ効率が良い

11. 型変換の方法

遅延シーケンスから他の型への変換

(def lazy-result (map inc [1 2 3]))

;; ベクターに変換
(vec lazy-result)
;; => [2 3 4]

;; リストに変換(実際は既にシーケンス)
(seq lazy-result)
;; => (2 3 4)

;; セットに変換
(set lazy-result)
;; => #{2 3 4}

;; 強制評価(型は変わらない)
(doall lazy-result)
;; => (2 3 4)

視覚的変換フロー

map処理
   ↓
LazySeq (2 3 4)
   ├─ vec ──→ [2 3 4]     ベクター
   ├─ set ──→ #{2 3 4}    セット  
   └─ doall → (2 3 4)     評価済みシーケンス

12. パフォーマンスの比較

アクセス速度比較表

操作            │ ベクター │ リスト │ セット │
─────────────────────────────────────────
先頭アクセス     │   O(1)  │ O(1)  │  N/A  │
末尾アクセス     │   O(1)  │ O(n)  │  N/A  │
インデックス     │   O(1)  │ O(n)  │  N/A  │
先頭追加        │   O(1)  │ O(1)  │  N/A  │
末尾追加        │   O(1)  │ O(n)  │  N/A  │
メンバー検索     │   O(n)  │ O(n)  │ O(1)  │

13. 実用的な使い分け

使用場面別推奨

ベクター [] を使う場面

;; インデックスアクセスが必要
(get [10 20 30 40] 2)  ; => 30

;; 末尾への追加が多い
(conj [1 2 3] 4)       ; => [1 2 3 4]

リスト '() を使う場面

;; 先頭への追加が多い
(conj '(2 3 4) 1)      ; => (1 2 3 4)

;; 関数の引数リストやマクロ
'(defn my-func [x] (+ x 1))

セット #{} を使う場面

;; 重複排除
(set [1 2 2 3 3 4])    ; => #{1 2 3 4}

;; メンバーシップテスト
(#{:admin :user :guest} :admin)  ; => :admin (truthy)

14. LazySeq(遅延シーケンス)とリストの違い

重要:LazySeqとリストは別物

;; リスト(List)
(def my-list '(1 2 3 4))
(type my-list)
;; => clojure.lang.PersistentList

;; 遅延シーケンス(LazySeq)
(def my-lazy (map inc [1 2 3]))
(type my-lazy)
;; => clojure.lang.LazySeq

;; 見た目は同じでも...
my-list     ; => (1 2 3 4)
my-lazy     ; => (2 3 4)

視覚的な違い

リスト '(1 2 3 4)          遅延シーケンス (map inc [1 2 3])
┌─────────────────┐        ┌─────────────────────────────┐
│ [1]→[2]→[3]→[4] │        │ 未計算 → 未計算 → 未計算    │
│  ↑              │        │   ↓       ↓       ↓       │
│ 即座に全て       │        │  計算    計算    計算      │
│ アクセス可能     │        │ 必要時   必要時   必要時    │
└─────────────────┘        └─────────────────────────────┘

内部構造の違い

リスト(PersistentList)

'(1 2 3 4)

特徴:

  • すべての要素が既に存在
  • メモリに全て格納済み
  • 即座にアクセス可能
  • 有限

内部構造:

先頭 → 1 → 2 → 3 → 4 → nil
       ↑   ↑   ↑   ↑
    すべて実体として存在

遅延シーケンス(LazySeq)

(map inc [1 2 3])

特徴:

  • 必要になるまで計算されない
  • 一度計算されるとキャッシュ
  • 無限シーケンスも可能
  • メモリ効率が良い

内部構造:

LazySeq
├─ 関数: (fn [] (map inc [1 2 3]))
├─ 状態: 未実現
└─ キャッシュ: nil

↓ 最初のアクセス時

LazySeq
├─ 関数: (fn [] (map inc [1 2 3]))  
├─ 状態: 実現済み
└─ キャッシュ: (2 3 4)

実際の動作比較

;; リストの場合
(def my-list '(1 2 3 4))
(println "リスト作成完了")  ; すぐに出力される

;; 遅延シーケンスの場合
(def my-lazy (map #(do (println "計算中:" %) 
                      (* % 2)) 
                 [1 2 3 4]))
(println "遅延シーケンス作成完了")  ; すぐに出力される(まだ計算されていない)

;; 実際にアクセスするまで計算されない
(first my-lazy)  ; この時点で「計算中: 1」が出力される
;; => 2

(doall my-lazy)  ; この時点で残りの「計算中: 2, 3, 4」が出力される
;; => (2 4 6 8)

パフォーマンスの違い

メモリ使用量

;; 大きなリスト(すべてメモリに保持)
(def big-list (range 1000000))  ; 100万個の数値をメモリに保持

;; 遅延シーケンス(必要な分だけ)
(def big-lazy (map inc (range 1000000)))  ; 計算はアクセス時のみ

;; 最初の10個だけ使用
(take 10 big-lazy)  ; メモリ使用量は最小限

計算タイミング

;; リスト:作成時にすべて計算
(time (def eager-list (doall (map #(Thread/sleep 1) (range 5)))))
;; => 5秒かかる

;; 遅延シーケンス:アクセス時に計算
(time (def lazy-seq (map #(Thread/sleep 1) (range 5))))
;; => 即座に完了(計算はまだされていない)

(time (doall lazy-seq))  ; この時点で5秒かかる

無限シーケンスの例

;; リストでは不可能(メモリ不足でクラッシュ)
;; (def infinite-list (range))  ; これは危険

;; 遅延シーケンスなら可能
(def infinite-lazy (iterate inc 0))  ; 0, 1, 2, 3, ...

;; 必要な分だけ取得
(take 5 infinite-lazy)  ; => (0 1 2 3 4)

実用的な違いの例

ファイル処理での違い

;; リスト方式(危険:大ファイルでメモリ不足)
(defn process-file-eager [filename]
  (doall (map process-line (line-seq (reader filename)))))

;; 遅延シーケンス方式(安全:一行ずつ処理)
(defn process-file-lazy [filename]
  (map process-line (line-seq (reader filename))))

;; 使用例
(take 10 (process-file-lazy "huge-file.txt"))  ; 最初の10行のみ処理

15. いつリストを使い、いつ遅延シーケンスを使うか

リスト '() を使う場面

;; 1. 小さなデータセット
(def colors '(:red :green :blue))

;; 2. 関数の引数(コード as データ)
'(defn my-func [x] (+ x 1))

;; 3. 先頭操作が多い場合
(conj '(2 3 4) 1)  ; => (1 2 3 4)

遅延シーケンス(map等の結果)を使う場面

;; 1. 大きなデータセット
(map process-data huge-dataset)

;; 2. 無限シーケンス
(take 100 (iterate inc 0))

;; 3. パイプライン処理
(->> data
     (map transform1)
     (filter predicate)
     (map transform2)
     (take 10))

;; 4. メモリ効率が重要
(map expensive-computation large-collection)

変換のタイミング

;; 遅延シーケンス → リスト(通常は不要、seqのまま使える)
(seq lazy-sequence)

;; 遅延シーケンス → ベクター(ランダムアクセスが必要)
(vec lazy-sequence)

;; 遅延シーケンス → 強制評価(副作用がある場合)
(doall lazy-sequence)

16. map使用時の実践的パターン

パターン1: 結果をベクターで欲しい場合

;; 一般的なパターン
(mapv inc [1 2 3 4])    ; => [2 3 4 5] (ベクター)

;; または
(vec (map inc [1 2 3 4]))  ; => [2 3 4 5]

パターン2: セットで重複排除したい場合

(set (map #(mod % 3) [1 2 3 4 5 6]))
;; => #{0 1 2}

パターン3: 遅延評価を活用する場合

;; 無限シーケンス
(def fibonacci (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

;; 必要な分だけ取得
(take 10 fibonacci)
;; => (0 1 1 2 3 5 8 13 21 34)

まとめ

mapの返り値について

  • mapは常に遅延シーケンスを返す
  • 入力の型に関係なく、出力はLazySeq
  • 必要に応じて他の型に変換する

コレクション型の選択指針

  1. インデックスアクセスが必要 → ベクター
  2. 先頭操作が多い → リスト
  3. 重複排除・メンバー検索セット
  4. キー・値の関連付けマップ
  5. 遅延評価・無限シーケンス遅延シーケンス

練習のポイント:

  1. まずは単純な例から始める
  2. 無名関数の記法に慣れる
  3. 他の高階関数と組み合わせる
  4. 実際のデータで試してみる
  5. 返り値の型を意識する
  6. 適切なコレクション型を選択する
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?