Skip to content

第 11 章: 永続データ構造とパイプライン処理

11.1 はじめに

この章では Clojure の 永続データ構造(Persistent Data Structures)と、それを活用した パイプライン処理 を学びます。

11.2 永続データ構造

Clojure のデータ構造は 不変(Immutable)です。「変更」操作は常に新しいデータ構造を返し、元のデータは変更されません。

構造共有

永続データ構造は 構造共有(Structural Sharing)により、効率的にコピーなしで「変更」を実現します。

(def original [1 2 3 4 5])
(def modified (conj original 6))

original  ;=> [1 2 3 4 5]   ← 変更されない
modified  ;=> [1 2 3 4 5 6] ← 新しいベクタ

主要なデータ構造

データ構造 リテラル 特徴
ベクタ [1 2 3] インデックスアクセス O(log32 N)
リスト '(1 2 3) 先頭追加 O(1)
マップ {:a 1 :b 2} キーによるアクセス O(log32 N)
セット #{1 2 3} 一意な値の集合

FizzBuzz での活用

;; ベクタとして FizzBuzz リストを生成
(def fizzbuzz-results
  (mapv fizzbuzz (range 1 101)))

;; マップで統計情報を集計
(def stats
  (->> fizzbuzz-results
       (group-by identity)
       (map (fn [[k v]] [k (count v)]))
       (into {})))

;; => {"1" 1, "2" 1, "Fizz" 27, "4" 1, "Buzz" 14, ...}

11.3 シーケンス抽象

Clojure のシーケンス抽象は、すべてのコレクションに統一的なインターフェースを提供します。

遅延シーケンス

;; 無限の FizzBuzz シーケンス(遅延評価)
(def infinite-fizzbuzz
  (map fizzbuzz (iterate inc 1)))

;; 必要な分だけ取得
(take 15 infinite-fizzbuzz)
;=> ("1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz" "Buzz"
;    "11" "Fizz" "13" "14" "FizzBuzz")

iterate は無限シーケンスを生成しますが、遅延評価のため実際にはアクセスした要素のみが計算されます。

トランスデューサー

トランスデューサーは変換処理をコレクションから分離し、再利用可能にします。

;; トランスデューサーの定義
(def fizzbuzz-xf
  (comp
    (map fizzbuzz)
    (filter #(re-matches #"[A-Za-z]+" %))))

;; ベクタに適用
(into [] fizzbuzz-xf (range 1 16))
;=> ["Fizz" "Buzz" "Fizz" "Fizz" "Buzz" "Fizz" "FizzBuzz"]

;; 文字列に適用
(transduce fizzbuzz-xf str (range 1 16))
;=> "FizzBuzzFizzFizzBuzzFizzFizzBuzz"

11.4 パイプライン処理

実践的なパイプライン

(defn fizzbuzz-report
  "FizzBuzz の統計レポートを生成する"
  [max-n]
  (->> (range 1 (inc max-n))
       (map fizzbuzz)
       (group-by (fn [s]
                   (cond
                     (= s "FizzBuzz") :fizzbuzz
                     (= s "Fizz") :fizz
                     (= s "Buzz") :buzz
                     :else :number)))
       (map (fn [[k v]] [k (count v)]))
       (into (sorted-map))))

(fizzbuzz-report 100)
;=> {:buzz 14, :fizz 27, :fizzbuzz 6, :number 53}

11.5 アトムによる状態管理

不変データ構造を使いつつ、必要な場合にはアトムで状態を管理します。

;; 処理カウンターの例
(def process-count (atom 0))

(defn counted-fizzbuzz [n]
  (swap! process-count inc)
  (fizzbuzz n))

(map counted-fizzbuzz (range 1 6))
@process-count ;=> 5

atom は Clojure の参照型の 1 つで、swap! で安全に状態を更新できます。

11.6 まとめ

この章では以下のことを学びました。

  • 永続データ構造構造共有 の仕組み
  • ベクタ、マップ、セットなど主要なデータ構造の使い分け
  • 遅延シーケンス による無限データの効率的な処理
  • トランスデューサー による変換処理の合成と再利用
  • スレッディングマクロ を使ったパイプライン処理
  • アトム による安全な状態管理