# 機能設計書 14-グループ化・集約

## 概要

本ドキュメントは、Nushellにおけるグループ化・集約機能の設計について記述する。group-by、chunk-by、chunksコマンドを通じて、リストやテーブルを特定の条件でグループ化する機能を提供する。

### 本機能の処理概要

グループ化・集約機能は、リストやテーブルのデータを指定した条件に基づいてグループ分けする。group-byは共通のキー値を持つ要素をまとめ、chunk-byは連続する同一条件の要素をまとめ、chunksは固定サイズで分割する。

**業務上の目的・背景**：データ分析において、カテゴリ別の集計やバッチ処理のためのデータ分割は基本的な操作である。ログのグループ化、データのバッチ処理、カテゴリ別統計の算出など、多様なユースケースに対応する。

**機能の利用シーン**：
- `ls | group-by type` でファイルを種類別にグループ化
- `[1 3 -2 -2 0 1 2] | chunk-by {|it| $it >= 0}` で符号の連続性でグループ化
- `[1 2 3 4 5 6] | chunks 2` でリストを2要素ずつのチャンクに分割
- `open data.csv | group-by category year` で複数カラムによる階層的グループ化

**主要な処理内容**：
1. グループ化条件（CellPath/Closure）を解析
2. 入力データを収集しイテレート
3. 条件評価結果をキーとしてグループを構築
4. 結果をRecordまたはTableとして返却

**関連システム・外部連携**：nu-engine（クロージャ評価）、indexmap（順序保持ハッシュマップ）と連携。

**権限による制御**：特になし。純粋なデータ操作機能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | メインREPL | 主画面 | グループ化コマンドの入力と結果表示 |

## 機能種別

データ操作（グループ化処理）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| grouper | CellPath/Closure | No (group-by) | グループ化キー | 有効なセルパスまたはクロージャ |
| --to-table | Switch | No (group-by) | テーブル形式で出力 | - |
| closure | Closure | Yes (chunk-by) | チャンク判定クロージャ | 有効なクロージャ |
| chunk_size | Int | Yes (chunks) | チャンクサイズ | 正の整数 |

### 入力データソース

- パイプライン入力: List、Table、Range、Binary
- group-by/chunk-byはリストまたはテーブルを要求
- chunksはList、Binary、ByteStreamをサポート

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| grouped_record | Record | (group-by) グループ名をキーとするレコード |
| grouped_table | Table | (group-by --to-table) group/itemsカラムを持つテーブル |
| chunked_list | List<List> | (chunk-by/chunks) チャンクのリスト |

### 出力先

- パイプライン出力: `PipelineData`として後続コマンドへ渡す

## 処理フロー

### 処理シーケンス

```
1. コマンド引数の解析
   └─ group-by: grouper（CellPath/Closure）を取得
   └─ chunk-by: closureを取得
   └─ chunks: chunk_sizeを取得

2. 入力データの処理
   └─ group-by: 全要素を収集しグループ化
   └─ chunk-by: 連続する同一キー要素をまとめる
   └─ chunks: 固定サイズで分割

3. グループ化ロジック実行
   └─ group-by: IndexMapでキー→値リストを構築
   └─ chunk-by: 前要素のキーと比較して分割
   └─ chunks: Iterator.take()で分割

4. 結果を返却
   └─ group-by: Record/Tableとして返却
   └─ chunk-by/chunks: List<List>として返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{コマンド種別}
    B -->|group-by| C[grouper解析]
    B -->|chunk-by| D[closure解析]
    B -->|chunks| E[サイズ検証]

    C --> F[入力収集]
    F --> G{grouper指定?}
    G -->|Yes| H[キー評価してグループ化]
    G -->|No| I[値自体をキーにグループ化]
    H --> J{--to-table?}
    I --> J
    J -->|Yes| K[Table形式で出力]
    J -->|No| L[Record形式で出力]

    D --> M[入力イテレート]
    M --> N[連続同一キーをチャンク化]
    N --> O[List<List>出力]

    E --> P{サイズ > 0?}
    P -->|No| Q[エラー返却]
    P -->|Yes| R[入力イテレート]
    R --> S[固定サイズでチャンク化]
    S --> O

    K --> T[終了]
    L --> T
    O --> T
    Q --> T
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | Nothing除外 | group-byでNothing値はスキップ | CellPath結果がNothingの場合 |
| BR-02 | キー文字列化 | 非文字列キーは文字列に変換 | group-by使用時 |
| BR-03 | 順序保持 | グループは入力順序を保持 | IndexMap使用により保証 |
| BR-04 | 階層グループ | 複数grouper指定で入れ子Record | group-byで複数キー指定時 |

### 計算ロジック

group-byのグループ化:
- CellPathの場合: `value.follow_cell_path()`で値取得
- Closureの場合: `ClosureEval.run_with_value()`で評価
- 結果を`to_abbreviated_string()`で文字列化しキーとする

chunk-byの分割ロジック:
- 前要素のクロージャ結果と現要素のクロージャ結果を比較
- 結果が異なれば新しいチャンクを開始

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

該当なし（インメモリ処理のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NeedsPositiveValue | パラメータエラー | chunks引数が負数 | 正の整数を指定 |
| IncorrectValue | パラメータエラー | chunks引数がゼロ | 1以上の整数を指定 |
| TypeMismatch | 型エラー | 無効なgrouper型 | CellPathまたはClosureを指定 |
| GenericError | 名前衝突 | grouper名が"items"と衝突 | 別の名前を使用 |

### リトライ仕様

該当なし（同期処理のため）

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

該当なし（状態変更を伴わない）

## パフォーマンス要件

- 順序保持: IndexMapを使用（O(1)挿入、順序維持）
- ストリーミング: chunksはListStreamをストリーミング処理
- メモリ使用: group-byは全入力をメモリに保持

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

- クロージャ実行: group-by/chunk-byのクロージャはエンジンのサンドボックス内で実行

## 備考

- group-byの複数grouper指定で階層的なRecord構造を生成
- chunk-byは連続性に基づくグループ化のため、ソート済みデータでの使用が効果的
- chunksは最後のチャンクがchunk_size未満になる場合がある

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | Grouper列挙型(397-400行目)でグループ化条件の種類を理解 |
| 1-2 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | Tree/Grouped構造(415-422行目)で階層グループを理解 |

**読解のコツ**: `Grouper`には2種類（CellPath, Closure）があり、`Tree`は単一グループ(Leaf)と入れ子グループ(Branch)を表現する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | group_by関数(252-292行目)で全体フローを理解 |
| 2-2 | chunk_by.rs | `crates/nu-command/src/filters/chunk_by.rs` | chunk_by関数(190-223行目)でストリーム処理を理解 |
| 2-3 | chunks.rs | `crates/nu-command/src/filters/chunks.rs` | chunks関数(116-172行目)で入力型ごとの処理を理解 |

**主要処理フロー (group-by)**:
1. **259行目**: `call.rest()`でgroupersを取得
2. **263行目**: `input.into_iter().collect()`で全入力を収集
3. **273-282行目**: groupersに応じてGroupedを構築
4. **284-289行目**: `--to-table`フラグでTable/Recordを切り替え

**主要処理フロー (chunk-by)**:
1. **197行目**: closureを取得
2. **206行目**: `ClosureEval::new()`でクロージャ評価器を作成
3. **208-213行目**: `chunk_value_stream()`でチャンク化

**主要処理フロー (chunks)**:
1. **99-110行目**: chunk_sizeの検証（正の整数）
2. **124-127行目**: ListのChunksIterでチャンク化
3. **133-147行目**: BinaryのChunkReadでバイト分割

#### Step 3: グループ化ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | group_cell_path関数(353-372行目)でCellPathグループ化を理解 |
| 3-2 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | group_closure関数(374-395行目)でClosureグループ化を理解 |
| 3-3 | chunk_by.rs | `crates/nu-command/src/filters/chunk_by.rs` | Chunk構造体(103-172行目)でチャンク化アルゴリズムを理解 |

**主要処理フロー**:
- **361行目**: `value.follow_cell_path()`でキー値を取得
- **367-368行目**: `to_abbreviated_string()`で文字列化しIndexMapに格納
- **162-166行目**: chunk-byで前キーと比較し、異なれば新チャンク開始

#### Step 4: 出力フォーマットを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | into_table関数(487-500行目)でテーブル出力を理解 |
| 4-2 | group_by.rs | `crates/nu-command/src/filters/group_by.rs` | into_record関数(521-537行目)でレコード出力を理解 |

**主要処理フロー**:
- **488-499行目**: Treeを再帰的に展開しテーブル行を生成
- **522-536行目**: Leaf/Branchに応じてRecord/入れ子Recordを生成

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

```
GroupBy::run() / ChunkBy::run() / Chunks::run()
    │
    ├─ GroupBy:
    │      ├─ input.into_iter().collect()
    │      │
    │      └─ Grouped::new(grouper, values)
    │             │
    │             ├─ group_cell_path()
    │             │      └─ value.follow_cell_path()
    │             │             └─ to_abbreviated_string()
    │             │
    │             └─ group_closure()
    │                    └─ ClosureEval::run_with_value()
    │                           └─ to_abbreviated_string()
    │
    ├─ ChunkBy:
    │      ├─ ClosureEval::new()
    │      │
    │      └─ chunk_value_stream()
    │             └─ chunk_iter_by()
    │                    └─ Chunk::next()
    │                           └─ closure(value) 比較
    │
    └─ Chunks:
           ├─ List: ChunksIter::new()
           │         └─ iter.take(size)
           │
           └─ Binary: ChunkRead::new()
                     └─ reader.fill_buf()
```

### データフロー図

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

PipelineData ───────▶ collect() ───────▶ Vec<Value>
     │                                         │
     │                                         ▼
     │                               ┌─────────────────┐
     │                               │ grouper評価     │
     │                               │ CellPath/       │
     │                               │ Closure         │
     │                               └─────────────────┘
     │                                         │
     │                                         ▼
     │                               ┌─────────────────┐
     │                               │ IndexMap構築    │
     │                               │ key → Vec<Val>  │
     │                               └─────────────────┘
     │                                         │
     │              ┌──────────────────────────┼──────────────────────────┐
     │              │                          │                          │
     │              ▼                          ▼                          ▼
     │      ┌───────────────┐          ┌───────────────┐          ┌───────────────┐
     │      │ group-by      │          │ chunk-by      │          │ chunks        │
     │      │ Record/Table  │          │ List<List>    │          │ List<List>    │
     │      │ 階層構造      │          │ 連続チャンク  │          │ 固定サイズ    │
     │      └───────────────┘          └───────────────┘          └───────────────┘
     │              │                          │                          │
     │              └──────────────────────────┼──────────────────────────┘
     │                                         │
     └─────────────────────────────────────────▶ PipelineData (grouped)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| group_by.rs | `crates/nu-command/src/filters/group_by.rs` | ソース | group-byコマンド実装 |
| chunk_by.rs | `crates/nu-command/src/filters/chunk_by.rs` | ソース | chunk-byコマンド実装 |
| chunks.rs | `crates/nu-command/src/filters/chunks.rs` | ソース | chunksコマンド実装 |
| mod.rs | `crates/nu-command/src/filters/mod.rs` | ソース | フィルターモジュールエクスポート |
