0
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?

Ninda 5日目: パターンマッチング 〜ワイルドカードと条件検索〜

Posted at

今日のゴール

  • パターンマッチングの仕組みを深く理解する
  • 効率的なパターン設計を学ぶ
  • 第1フィールドインデックスの活用法を知る

パターンとは何か

タプルスペースの真の力は、パターンマッチングにあります。

従来のデータアクセス:
  data[42]  →  特定のアドレスで取得

タプルスペース:
  ("task", ?)  →  「taskに関する何か」という条件で検索

アドレスではなく「形」で検索できることが、タプルスペースの革新的な点です。


ワイルドカードの使い方

nilValue() がワイルドカードを表します。

# 完全マッチ: 両方の値が一致するタプルのみ
let exact = toPattern(strVal("task"), intVal(42))

# 部分マッチ: "task"で始まる任意のタプル
let partial = toPattern(strVal("task"), nilValue())

# 全ワイルドカード: 2要素の任意のタプル
let any2 = toPattern(nilValue(), nilValue())

マッチングのルール

3つの基本ルール

ルール 説明
長さの一致 パターンとタプルの要素数は同じ
型の一致 ワイルドカード以外は型と値が一致
ワイルドカード nilValue()は何でもマッチ

図解

コード例

# 型が違うと別のタプル
await ts.writeAsync(toTuple(strVal("num"), intVal(42)))    # 整数の42
await ts.writeAsync(toTuple(strVal("num"), strVal("42")))  # 文字列の"42"

# 整数の42だけを検索
let intPattern = toPattern(strVal("num"), intVal(42))
let result = await ts.tryReadAsync(intPattern)  # 整数版のみマッチ

第1フィールドインデックス

nindaは第1フィールドでインデックスを持っています。これが検索効率の鍵です。

効率的なパターン vs 非効率なパターン

パターン 効率 理由
("task", *) ✅ O(1) 第1フィールドでインデックス参照
(*, 42) ❌ O(n) 全タプルをスキャン
(*, *, "target") ❌ O(n) 全タプルをスキャン

タイプタグパターン

第1フィールドを「タイプタグ」として使うのがベストプラクティスです。

# ✅ 良い設計: タイプタグが第1フィールド
("order", 1001, "pending")
("order", 1002, "shipped")
("inventory", "SKU-001", 50)
("user", "alice", "admin")

# ❌ 避けるべき設計: タイプタグが第2フィールド
(1001, "order", "pending")  # 検索が非効率

利点

  1. 高速検索: インデックスが効く
  2. 整理されたスペース: 種類ごとにグループ化
  3. 可読性: タプルの意味が明確

パターン設計のガイドライン

1. 第1フィールドをタイプタグに

("log", level, component, message)    # ✅
(timestamp, "log", level, message)    # ❌

2. 頻繁に検索する値を前方に

# levelで頻繁に検索するなら
("log", level, component, message)

# componentで頻繁に検索するなら
("log", component, level, message)

3. 適切な粒度を選ぶ

# ❌ 細かすぎ
("user", "field", "name", "alice")
("user", "field", "age", 25)

# ✅ 適切
("user", "alice", 25, "admin")

実践例: ログフィルタリング

# ログを書き込み
await ts.writeAsync(toTuple(strVal("log"), strVal("error"), strVal("auth"), strVal("Invalid token")))
await ts.writeAsync(toTuple(strVal("log"), strVal("info"), strVal("server"), strVal("Started")))
await ts.writeAsync(toTuple(strVal("log"), strVal("error"), strVal("db"), strVal("Connection lost")))

# エラーログだけを取得
let errors = await ts.readAllAsync(
  toPattern(strVal("log"), strVal("error"), nilValue(), nilValue())
)
# → 2件のエラーログ

# authコンポーネントのログだけを取得
let authLogs = await ts.readAllAsync(
  toPattern(strVal("log"), nilValue(), strVal("auth"), nilValue())
)
# → 1件のauthログ

まとめ

学んだこと

トピック ポイント
ワイルドカード nilValue() は何でもマッチ
マッチングルール 長さ一致、型一致
インデックス 第1フィールドが鍵
タイプタグ 第1フィールドを識別子として使う

設計の原則

第1フィールド = タイプタグ(効率的な検索の鍵)
第2フィールド以降 = よく検索する値を前方に

演習問題

問題5-1: 商品検索システム

商品タプル ("product", name, price, category) を10個作成し、以下を実装してください:

  • 特定カテゴリの商品一覧取得
  • 特定価格の商品検索
ヒント
  • カテゴリ検索: toPattern(strVal("product"), nilValue(), nilValue(), strVal("electronics"))
  • 価格検索: toPattern(strVal("product"), nilValue(), intVal(1000), nilValue())
  • 「1000円以下」のような範囲検索はパターンマッチングだけでは実現できません。readAllで取得後にフィルタリングが必要です。

問題5-2: イベントフィルタ

イベントタプル ("event", type, source, timestamp) を作成し、typeとsourceの両方で絞り込む検索を実装してください。

ヒント
  • 両方指定: toPattern(strVal("event"), strVal("click"), strVal("button"), nilValue())
  • パターンマッチングでは、指定した位置の値が完全一致するタプルのみがマッチします。

問題5-3: 効率比較

以下のパターンを使って1万件のタプルから検索し、処理時間を比較してください:

  1. 第1フィールドが具体値のパターン
  2. 第1フィールドがワイルドカードのパターン
ヒント
  • cpuTime()で計測: let start = cpuTime(); ...; echo cpuTime() - start
  • 差が出にくい場合は、検索を1000回ループして合計時間を比較

前回: 基本操作 | 目次 | 次回: 非同期プログラミング

0
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
0
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?