# 機能設計書 90-unique_by

## 概要

本ドキュメントは、Hack Standard Library (HSL) の Dict モジュールにおける `unique_by` 関数の機能設計を記述する。この関数は、スカラー関数によって一意性を判定し、各変換値が1回のみ出現する新しい dict を返す機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：`unique` 関数は値が arraykey（int または string）である場合にのみ使用できるが、実際のアプリケーションでは、オブジェクトやより複雑な値の一意性を判定したいケースがある。例えば、ユーザーオブジェクトのIDプロパティで重複を判定する、商品の価格帯で重複を判定する、日付オブジェクトの年月で重複を判定するなどである。本関数は、任意の値から arraykey を抽出するスカラー関数を指定することで、非 arraykey 値に対しても重複除去を行えるようにする。

**機能の利用シーン**：
- オブジェクトの特定プロパティで重複を判定する場合
- 複合的な条件で一意性を判定する場合（例：カテゴリ+日付の組み合わせ）
- 値を正規化してから重複を判定する場合
- 非 arraykey 値（オブジェクト、配列など）の重複を除去する場合

**主要な処理内容**：
1. pull_with_key を使用して dict[スカラー値 => 元のキー] を生成
2. スカラー値が重複する場合、後のキーで上書きされる
3. pull を使用して dict[元のキー => 元の値] を再構築
4. 結果の dict を返却

**関連システム・外部連携**：本関数は純粋な計算処理であり、外部システムとの連携はない。

**権限による制御**：本関数は汎用ユーティリティであり、権限による制御は行わない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | 本プロジェクトはUIを持たないライブラリのため画面なし |

## 機能種別

計算処理（重複除去）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| $container | KeyedContainer<Tk, Tv> | Yes | 重複除去対象のコンテナ | なし |
| $scalar_func | (function(Tv)[_]: Ts) | Yes | 値から一意性判定用のスカラー値を抽出する関数。Ts は arraykey の制約がある | なし |

### 入力データソース

関数引数として直接渡される KeyedContainer オブジェクトとスカラー関数。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | dict<Tk, Tv> | 各スカラー値が1回のみ出現する dict。重複時は後のキーが優先 |

### 出力先

関数の戻り値として呼び出し元に返却。

## 処理フロー

### 処理シーケンス

```
1. pull_with_key で中間 dict を生成
   └─ dict[scalar_func(value) => original_key] を構築
       └─ 同じスカラー値がある場合、後のキーで上書き
2. pull で結果 dict を生成
   └─ dict[original_key => container[original_key]] を構築
3. 結果の dict を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[pull_with_key で dict<Ts, Tk> を生成]
    B --> C[同じスカラー値は後のキーで上書き]
    C --> D[pull で dict<Tk, Tv> を再構築]
    D --> E[元のキーを使って元のコンテナから値を取得]
    E --> F[結果を返却]
    F --> G[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-90-01 | 後のキー優先 | 同じスカラー値を持つエントリがある場合、後に出現したキーが保持される | 重複スカラー値が存在する場合 |
| BR-90-02 | スカラー値の型制約 | スカラー関数の戻り値 Ts は arraykey（int または string）でなければならない | 常時 |
| BR-90-03 | 元の値保持 | 結果には元の値がそのまま含まれる（スカラー値ではない） | 常時 |
| BR-90-04 | KeyedContainer 制約 | 入力は KeyedContainer でなければならない（イテレータは不可） | 常時 |

### 計算ロジック

スカラー関数による重複除去:
1. 中間 dict: dict[$scalar_func(v) => k] for (k, v) in $container
   - 同じスカラー値に対して複数のキーがある場合、後のキーで上書き
2. 結果 dict: dict[k => $container[k]] for k in values(中間 dict)

## データベース操作仕様

### 操作別データベース影響一覧

本関数はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 型エラー | Ts が arraykey でない場合 | Hack型システムによりコンパイル時に検出 |

### リトライ仕様

本関数は純粋関数であり、リトライの概念は適用されない。

## トランザクション仕様

本関数はステートレスな純粋関数であり、トランザクション管理は不要。

## パフォーマンス要件

- 時間計算量: O(n * s)、n は $container のサイズ、s は $scalar_func の計算量
- 空間計算量: O(n)

## セキュリティ考慮事項

- 純粋関数であり、副作用がないためセキュリティリスクは低い
- スカラー関数のコンテキスト（ctx）が伝播されるため、副作用も追跡可能

## 備考

- 値が arraykey の場合は `Dict\unique()` の方がシンプルで効率的
- KeyedContainer を使用するため、イテレータは直接渡せない
- スカラー関数は各値に対して2回呼ばれる可能性がある（最適化の余地あり）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | select.php | `src/dict/select.php` | unique_by 関数のシグネチャと型パラメータ |
| 1-2 | transform.php | `src/dict/transform.php` | pull_with_key と pull 関数の実装 |

**読解のコツ**: `Ts as arraykey` の制約に注目。スカラー関数の戻り値は中間 dict のキーとして使用される。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | select.php | `src/dict/select.php` | unique_by 関数の実装（235-244行目） |

**主要処理フロー**:
1. **235-238行目**: 関数シグネチャの定義
2. **239-240行目**: コメント - 処理の説明
3. **241-243行目**: パイプ演算子（|>）を使用した変換処理

#### Step 3: 依存関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | transform.php | `src/dict/transform.php` | pull_with_key 関数の実装（324-334行目） |
| 3-2 | transform.php | `src/dict/transform.php` | pull 関数の実装（301-311行目） |

**主要処理フロー**:
- **pull_with_key**: キーと値を使って新しいキーと値を生成
- **pull**: 値を使って新しいキーと値を生成

### プログラム呼び出し階層図

```
Dict\unique_by($container, $scalar_func)
    |
    +-- pull_with_key($container, ($k, $_) ==> $k, ($_, $v) ==> $scalar_func($v))
    |       |
    |       +-- foreach で dict[scalar_value => original_key] を生成
    |       +-- 重複するスカラー値は後のキーで上書き
    |
    +-- pull(中間結果, $orig_key ==> $container[$orig_key], $x ==> $x)
            |
            +-- foreach で dict[original_key => original_value] を生成
```

### データフロー図

```
[入力]                         [処理]                           [出力]

$container ────────────> pull_with_key ────────> pull ────────> dict<Tk, Tv>
(KeyedContainer<Tk, Tv>)    |                     |              (一意のスカラー値
                            v                     v               を持つエントリのみ)
                       dict<Ts, Tk>          dict<Tk, Tv>
                       (スカラー値 =>         (元のキー =>
                        元のキー)              元の値)

$scalar_func ──────────> スカラー値の計算
(function(Tv): Ts)

例（Set の firstKey でグループ化）:
  Vector {Set{'foo'}, Set{'bar'}, Set{'foo'}, Set{'baz'}}
  -> pull_with_key -> dict['foo' => 2, 'bar' => 1, 'baz' => 3]
  -> pull -> dict[2 => Set{'foo'}, 1 => Set{'bar'}, 3 => Set{'baz'}]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| select.php | `src/dict/select.php` | ソース | unique_by 関数の実装 |
| transform.php | `src/dict/transform.php` | ソース | pull_with_key, pull 関数の実装 |
| DictSelectTest.php | `tests/dict/DictSelectTest.php` | テスト | unique_by のユニットテスト（466-492行目） |
