全 15 問 で Rust & SQL 入門！【電脳少女プログラミング2088 ─壊レタ君を再構築─】

# はじめに

**paiza**[^1] の新作プログラミングゲーム「電脳少女プログラミング2088 ─壊レタ君を再構築─」を題材に、**`Rust`** と **`SQL`** の基本的な用語や使い方を解説します。

https://ppg.paiza.jp/denno2088/

[^1]: **paiza** は、IT エンジニア・プログラマ向けの総合的な転職・学習プラットフォームです。

このゲーム[^2]は、「プログラミング問題」と「データベース問題」を解きながらストーリーを進めていく形式で、プログラミング初心者でも楽しく学ぶことができます。

[^2]:ゲームに参加するには、アカウント登録とニックネーム設定が必要です。



新しく言語を学ぶときに**最初のハードル** になるのが『環境構築』ですが、paiza では環境構築が不要で、ブラウザ上で実行できるため、**手軽に**プログラミング言語やデータベース言語を学ぶことができます。


## **対象読者**
- 他のプログラミング言語の経験がある方を対象としています。
- `SQL` は経験がなくても問題ありません。


## **本記事の構成**

- 本記事は、大きく以下の 2 つのセクションで構成されています。
    - **プログラミング問題**
    - **データベース問題**
- 各セクションの冒頭で「**前提知識**」を解説し、その後、問題に取り組みます。
- 各問題は、以下の 3 つのステップで進めます。
    - **必要な知識**：問題を解くために必要な概念を解説
    - **問題**：実際の問題を提示
    - **解答**：正解例と詳しい解説

:::note
説明が長くなりましたので、全体の見やすさを考慮し、一部を折りたたんで表示しています。
:::

# プログラミング問題
プログラミング問題は全 10 問で、Python や Go などの 28 言語に対応しています。今回は近年注目を集める [Rust](https://www.rust-lang.org/ja) を使用します。


## 前提知識
プログラミング問題を解くには以下の 3 つの要素が重要になります。
- **アルゴリズムとデータ構造**
    - 効率的に問題と解くために必要
- **プログラミング言語の基礎**
    - アルゴリズムを実装するために必要
- **標準入出力**
    - 入力値の受け取りと解答の出力のために必要


以下では前提知識としてこれらを解説していきます。

### アルゴリズムとデータ構造

著名な計算機科学者であるニクラウス・ヴィルト氏の著書『**Algorithms + Data Structures = Programs**』のタイトルが示すように、プログラムを作成する上で**アルゴリズム**と**データ構造**は不可欠な要素です。

<details><summary>データ構造とは</summary>

『データ構造』は、データを**一定の規則に基づいて整理**し、**特定の操作を効率的に実行**できるよう設計された構造です。例えば、『配列』ではデータを連続して並べることで高速なアクセスを実現しています。

</details> 

<details><summary>アルゴリズムとは</summary>

一方、『アルゴリズム』とは、並べ替えや探索などの**特定の目的を達成**するための**処理手順**を指します。多くのアルゴリズムは、最適な性能を引き出すために特定のデータ構造と組み合わせて設計されています。

</details> 

:::note
プログラミング問題はある程度**パターンがある**ため、 問題文中に特定の**キーワード** (例: 最短経路、最大値) を手がかりに適切なアルゴリズムとデータ構造を選択することができます。
:::


### Rust の基礎
Rust は学習難易度の高い言語とされています。その大きな要因のひとつが Rust 独自の概念である『**所有権**』です。所有権を正しく理解するためには、『**メモリ管理**』の仕組みについて理解が重要です。

<details><summary><strong>メモリ管理</strong></summary>

通常、メモリの管理は OS とアプリケーションで役割を分担して実現しています。

『**物理的な**メモリ』の管理は OS が担当します。OS は、実行中の各プログラムに対して、それぞれ独立した『**仮想アドレス空間**（仮想的なメモリ）』を割り当てます。

一方、アプリケーションは OS から割り当てられた仮想アドレス空間**内**で、プログラミング言語の機能を利用し、メモリの管理を行います。


:::note
仮想アドレス空間は『**静的領域**』や『**スタック領域**』、『**ヒープ領域**』などに分かれており、それぞれ異なる目的で使用されます。
- **静的領域**
    - 静的領域に格納する値はプログラム実行中に**常に存在**し、**変更されません**。定数の値などが格納されます。
- **スタック領域**
    - スタック領域に格納する値はコンパイル時に**サイズが確定**し、その値を使用する**関数が実行されている間だけ**有効です。
    - ローカル変数や引数の値などが格納されます。
- **ヒープ領域**
    - ヒープ領域に格納する値はコンパイル時に**サイズが確定**しません。
    - プログラムの実行中に必要な時に必要な量のメモリを割り当てることが可能です。
:::


C や C++ ではヒープ領域のメモリを不要時に**明示的に**解放する必要がありますが、Rust では所有権の仕組みにより、メモリの管理が**自動化**されているため、解放忘れや二重解放などから生じる問題を防ぐことができます。所有権の主な目的がヒープ領域の管理です。[^3]

[^3]: 参考: [What is Ownership? - The Rust Programming Language](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap) 

</details> 

<details><summary><strong>所有権</strong></summary>

**所有権**はメモリ上に格納された値の解放のタイミングを自動化し、効率的で安全なメモリ管理が実現する仕組みです。


『**変数と値の関係**』に注目すると、この仕組みが理解しやすくなります。

:::note
**変数**は、メモリ内の特定の位置に保存された**値**に識別子（**変数名**）を付けることで、**ソースコード上**でその値を利用しやすくするものです。

変数には**型**があり、これは変数に関連付ける**値の種類**（例: 整数、文字列など）を示します。

Rust では `let 変数名: 型` で変数を**宣言**し、`=` で変数と値を**結び付け**ます。変数はデフォルトで**不変**ですが、`let mut 変数名: 型` とすることで後から値を変更できます。
:::

**所有権**は基本的に以下の **3 つのルール**によって成り立っています。

1. 各**値**には**変数**がただ 1 つだけ結びつき、その変数がその値の**所有者**となります。
    ```rust
    let s: String = String::from("hello"); // s が String 型の値の所有権を持つ
    ```
    　
2. **変数**（所有者）が**スコープ**（変数が有効な範囲）を抜けると、その変数が所有する**値**は自動的にメモリから削除されます。
    ```rust
    {
        let s: String = String::from("hello"); // スコープの開始時に所有権を持つ
    } // s がスコープを抜けると、自動的にメモリから値が削除される
    // println!("{}", s); // エラー：s はもう使用できない
    ```
    　
3. ある変数が所有する**値**の所有権を別の変数に『**ムーブ**（移動）』できます。ムーブされた後、元の変数はその値にアクセスできなくなります。

    ```rust
    let s1: String = String::from("hello"); // s1 が所有権を持つ
    let s2: String = s1; // s1 の所有権が s2 にムーブされる
    
    // println!("{}", s1); // エラー：s1 はもう使用できない
    println!("{}", s2); // 正常に動作する
    ```
    ただし、整数、浮動小数点数、論理値などはムーブではなく**コピー**されるため、元の変数もそのまま使えます。
     ```rust
    let x: i32 = 42;
    let y: i32 = x; // `i32` は値がコピーされる

    println!("{}", x); // `x` はそのまま使える
    println!("{}", y); // `y` にコピーされた値を使用
    ```

このように所有権は、**各値を所有する変数を明確にする**ことで、値のライフサイクル（生成から破棄まで）をコンパイル時に決定し、メモリリークや二重解放といった問題を防ぎます。

さらに、所有権をムーブするのではなく、変数の値を借りる（**参照**を作る）こともできます。

参照には**不変参照**と**可変参照**があり、借用中に値の変更やアクセスが制限されます。

- **不変参照**（`&`）: 複数の変数が値を同時に借りることができるが、値は変更できない。
- **可変参照**（`&mut`）: 1つの変数が値を可変に借りることができ、借用中は他の変数がその値を変更できない。

```rust:不変参照
let s1: String = String::from("hello");
let s2: &String = &s1; // 不変参照
let s3: &String = &s1; // 不変参照

println!("{}", s1); // 正常に動作
println!("{}", s2); // 正常に動作
println!("{}", s3); // 正常に動作
```
```rust:可変参照
let mut s1: String = String::from("hello");
let s2: &mut String = &mut s1; // 可変参照

s2.push_str(", world");
// println!("{}", s1); // エラー：所有権を持つ s1 も値にアクセスできない
println!("{}", s2); // "hello, world"
```


</details> 

:::note
最初は難しく感じるかもしれませんが、**どの変数がどの値の所有しているか**を意識してコードを書くことで所有権に慣れてくると思います。
:::


### 標準入出力
プログラミング問題では**標準入力**からデータを受け取り、**標準出力**に解答を出力する形式が基本となります。


標準入出力は、ユーザーや他のプログラムと簡単にやり取りするための仕組みです。プログラムの実行が開始されると、OS によって 3 つの**ストリーム**（標準入力・標準出力・標準エラー）を自動的に用意され、プログラムはすぐに入出力を行えるようになります。[^4][^5]

:::note
**ストリーム**とはファイルやデバイスとの入出力を統一的に扱うためのインターフェースです。これにより、プログラムがデータの**具体的な入力元や出力先**（例えば、ファイルから来るのか、キーボードから来るのか）**を意識せず**に、一連のバイトや文字を**順番に**読み書きできます。
:::

[^4]: 参考: [Standard I/O Streams | The Open Group Base Specifications Issue 8](https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_05)
[^5]: 参考: [Stream I/O | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/c-runtime-library/stream-i-o?view=msvc-170)


Rust では、`std::io` モジュールを使って標準入出力を扱います。

<details><summary><strong>標準入力</strong></summary>

**役割**
標準入力は、プログラムが外部からデータを受け取るためのストリームです。通常、キーボード入力がこのストリームを通じてプログラムに渡されます。

**操作方法**
Rust では [`std::io::stdin()`](https://doc.rust-lang.org/stable/std/io/struct.Stdin.html) を使用して標準入力を操作し、データを受け取ることができます。
特に、[`lines()`](https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.lines) メソッドを利用すると、標準入力から 1 行ずつデータを読み取るイテレータが得られ、行単位での処理が簡単に行えます。



```rust:具体例
use std::io; // 標準ライブラリ (std) に含まれる io モジュール をインポート

let stdin = io::stdin(); // 標準入力のハンドルを取得
for line in stdin.lines() { // 標準入力から1行ずつ取得
    match line {
        Ok(input) => {
            println!("入力された行: {}", input); // 入力された内容を表示
        }
        Err(e) => {
            eprintln!("エラーが発生しました: {}", e); // エラーハンドリング
        }
    }
}
```
- `line` は `Result` 型なので、その値が `Ok` か `Err` かで分岐し、`Ok` の場合は読み取った行（`String`）が `input` に格納され、`Err` の場合はエラーメッセージが表示されます。

:::note
`Result` 型は、ファイルの読み込みなど、エラーが発生する可能性がある関数の返り値の型です。
`match` を使って成功と失敗のパターンを分岐させて処理を行います。
:::

また、文字列から数値への変換 ([`parse::<i32>()`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.parse)) などの処理を理解しておくと、スムーズにコードが書けます。

</details> 


<details><summary><strong>標準出力</strong></summary>

**役割**
標準出力は、プログラムの実行結果を出力するためのストリームです。通常、画面に結果を表示したり、ファイルにデータを書き込んだりする際に使います。

**操作方法**
Rust では、[`println!`](https://doc.rust-lang.org/std/macro.println.html) や [`std::io::stdout()`](https://doc.rust-lang.org/beta/std/io/fn.stdout.html) を使用して標準出力を操作できます。

**具体例** `println!("Hello, world!");`
- `println!`: 末尾に改行を付け、標準出力にデータを送ります。

</details> 


## 街外れの戦場跡（E ランク）
文字列を結合に関する問題です。

**必要な知識**
- [`&str` 型](https://doc.rust-lang.org/std/primitive.str.html): **不変**の文字列を扱う場合に利用
- [`String` 型](https://doc.rust-lang.org/std/string/struct.String.html): **変更可能**な文字列を扱う場合に利用
- [`format!`](https://doc.rust-lang.org/std/macro.format.html): **文字列を結合**する場合などで利用


<details><summary><strong>問題</strong></summary>

文字列を結合した結果を求める問題です。Python でコードが記述されています。
```python
a = "RE"
b = "IMI"
print(a+b)
```
</details> 

<details><summary><strong>解答</strong></summary>

```:解答
“REIMI”
```
特に説明することもないので、Rust のコードに書きかえた以下の例について解説します。

```rust
// エントリポイント は `main` 関数
fn main() {
    let a: &str = "RE"; // &str 型
    let b: &str = "IMI"; // &str 型
    let result: String = format!("{}{}", a, b); // 文字列を連結
    println!("{}", result); // 結果を出力
    // println!("{}", a+b); // error: cannot add `&str` to `&str`
}
```
- Rust のプログラムは実行されると、まず `main` 関数が呼び出され、その内部のコードが実行されます。
- Rust で文字列を扱う場合、`&str` 型と `String` 型の 2 つがよく使用されます。
    - `&str` 型は Python のように `+` 演算子で連結できません。
    - 代わりに、`format!` を使うと、複数の文字列を簡単に連結できます。連結後の値は `String` 型になります。

</details> 

:::note
**`String` 型 と `&str` 型**

Rust で文字列は `UTF-8` の配列（各文字は 1 ~ 4 バイト）として表現します。例えば `"Rust"` はメモリ上で `52 75 73 74`（16進数表記）というバイト列として表現されます。

Rust で最もよく使用される 2 つの文字列型は `String` と `&str` です。


<details><summary><strong>String 型</strong></summary>

- **可変長**の文字列型です。ユーザー入力など、実行時に変化する文字列を扱う場合に使用します。
- 文字列データはヒープ領域に格納され、そのデータのメタデータ（データの開始位置、長さ、容量）は通常スタック領域に格納されます。
</details>

<details><summary><strong>&str 型</strong></summary>

- 既存の文字列データへの**不変の参照**です。
- 文字列データのメタデータ（データの開始位置、長さ、容量）は通常スタック領域に格納され、実際のデータは別の場所（静的領域、ヒープ領域）に格納されます。
</details>

<details><summary><strong>String 型と &str 型の例</strong></summary>

```rust
// String 型の例
// `Hello` はヒープ領域に格納される
let mut s: String = String::from("Hello");
s.push_str(", world!");  // 文字列を変更できる
println!("String 型の s: {}", s);

// &str 型の例
// `Hello, Rust!` は静的領域に格納される
let literal: &str = "Hello, Rust!";
// literal.push_str(" more");  // コンパイルエラー：&str は変更できない
println!("&str 型の literal: {}", literal);
```

```markdown:String 型のメモリ配置
`let mut s = String::from("Hello");` で、
- 実際のデータ（`Hello`）はヒープ領域に配置
- このデータへアクセスするためのポインタはスタック領域に配置

　　　スタック領域
+-------+------------+
| 変数   |     値     |
+-------+------------+
|   s   | Pointer    |  -> ヒープ上の `Hello` の先頭アドレス（ポインタ）
|       | Length: 5  |  -> `Hello` の長さ (バイト数)
|       | Capacity: 5|  -> ヒープ上に確保された容量
+-------+------------+

     ヒープ領域
+---+---+---+---+---+
| H | e | l | l | o |
+---+---+---+---+---+

`s.push_str(", world!");` により、ヒープ上のデータが拡張される可能性があります。
新しい文字列の長さが既存の `Capacity` を超える場合、メモリが再確保されることがあります。

```
```markdown:&str 型のメモリ配置
`let literal: &str = "Hello, Rust!";` で、
- `Hello, Rust!` は静的領域に格納
- `literal` はこのデータへの参照を保持

　　　スタック領域
+----------+------------+
|   変数    |      値    |
+----------+------------+
| literal  | Pointer    |  -> 静的領域上の `Hello, Rust!`の先頭アドレス（ポインタ）
|          | Length: 12 |  -> `Hello, Rust!` の長さ
+----------+------------+

     静的領域
+---+---+---+---+---+---+---+---+---+---+---+---+
| H | e | l | l | o | , |   | R | u | s | t | ! |
+---+---+---+---+---+---+---+---+---+---+---+---+
```

</details> 
:::

## 郊外のスラム街（D ランク）
整数の演算に関する問題です。

**必要な知識**
- [`.read_line()`](https://doc.rust-lang.org/std/io/struct.Stdin.html#method.read_line): 標準入力の読み取り
- [`.trim()`](https://doc.rust-lang.org/std/primitive.str.html#method.trim): 行頭・末尾の空白を削除
- [`.parse()`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.parse): 文字列を数値（整数、浮動小数点数）に変換
- [`.unwrap()`](https://doc.rust-lang.org/rust-by-example/error/result.html): エラー処理を簡略化

<details><summary><strong>問題</strong></summary>

- 入力される値: `n`（`300 ≦ n ≦ 10,000` かつ `n は 10 の倍数`）
- 期待する出力: 入力値を `1/2` にして、`+100`した値

</details> 
<details><summary><strong>解答</strong></summary>

```rust
use std::io; // std::io モジュールをインポート

fn main() {
    let mut input: String = String::new();
    io::stdin().read_line(&mut input);

    let n: u32 = input.trim().parse::<u32>().unwrap();
    println!("{}", n / 2 + 100); // nを1/2にして、100足した結果を出力
}
```
- ユーザーの入力を保持するために `String` 型の変数 `input` を作成します。
- [`read_line()`](https://doc.rust-lang.org/std/io/struct.Stdin.html#method.read_line) メソッド は標準入力から 1 行を読み込み、`input` に格納します。
- `input` は `String` 型であるため、[`trim()`](https://doc.rust-lang.org/std/primitive.str.html#method.trim) メソッドで行頭・末尾の空白を削除し、 [`parse()`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.parse) メソッドで数値に変換します。
    - 今回は `32` ビットの符号なし整数型に変換するため、`::<u32>` と記述します。
    - `parse`は文字列を数値に変換しますが、**失敗**する可能性があるため `Result` 型で返します。`Result` 型の値に対するエラー処理を簡略化する便利な方法として、[`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/result.html) メソッドが利用できます。

</details> 

:::note
[**エラー処理**](https://doc.rust-lang.org/nightly/book/ch09-00-error-handling.html)

Rustでエラーは、**回復可能**なエラーと**回復不可能**なエラーの大きく 2 つに分類されます。

ファイルの読み込みや文字列の型変換など、**回復可能**なエラーが発生する可能性がある関数は **`Result` 型**を返します。`Result` 型は成功 **`Ok`** か、失敗 **`Err`** のいずれか 2 つの結果を返します。

`match` を使用してエラーを処理することが一般的ですが、`expect()` や `unwrap()` を利用することで簡潔に記述できます。


<details><summary><strong>エラー処理の例</strong></summary>

```rust
let number_str: &str = "15";

match number_str.parse::<i32>() {
    // 変換が成功した場合、Ok の中に変換された数値が入って返されます。
    Ok(num) => println!("{}", num),
    // 変換が失敗した場合（例えば、number_str が "abc" のように数値に変換できない文字列だった場合）、
    // Err の中にエラー情報（`e`）が入って返されます。
    Err(e) => eprintln!("数値の変換に失敗しました: {}", e),
}

// .expect() は、
// Result型が Ok の場合は、Ok の中身を取り出して返します。
// Result型が Err の場合、パニック（回復不可能なエラー）を引き起こします。
let num_expect: i32 = number_str.parse::<i32>().expect("数値の変換に失敗しました");
println!("{}", num_expect);

// .unwrap() は、.expect() とほとんど同じですが、
// エラーメッセージをカスタマイズできないところが異なります。
let num_unwrap: i32 = number_str.parse::<i32>().unwrap();
println!("{}", num_unwrap);
```
</details> 

:::

## カジノ（D ランク）
整数の演算に関する問題です。複数行の入力を処理します。

**必要な知識**
- [`Vec` 型](https://doc.rust-lang.org/std/vec/struct.Vec.html): 同じ型の要素からなる可変長の配列
- [`for in` 文](https://doc.rust-jp.rs/rust-by-example-ja/flow_control/for.html): [イテレータ](https://doc.rust-lang.org/rust-by-example/trait/iter.html)のそれぞれの要素に対して処理に使用する
- [`.lines()`](https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.lines): 標準入力を行単位で処理するためのイテレータにする
- [`if` 文](https://doc.rust-lang.org/std/keyword.if.html): 条件分岐

<details><summary><strong>問題</strong></summary>

- 入力される値: 
    - 1 行目: 1 ドルの枚数
    - 2 行目: 5 ドルの枚数
    - 3 行目: 10 ドルの枚数
- 期待する出力: 合計の金額


</details> 
<details><summary><strong>解答</strong></summary>

```rust
use std::io;

fn main() {
    let mut inputs: Vec<u32> = vec![]; // 可変のベクタを定義
    let stdin = io::stdin(); // 標準入力のハンドルを取得
    
    // 標準入力を 1 行ずつ処理（各行は末尾に改行を含まない）
    for line in stdin.lines() {
        let number_str: String = line.unwrap();

        match number_str.trim().parse::<u32>() {
            Ok(num) => inputs.push(num), // ベクターに要素を追加
            Err(_) => {
                eprintln!("無効な入力: 数値を入力してください");
            }
        }
    }
    // Rust では条件式を括弧でくくる必要がない
    if inputs.len() == 3 {
        println!("{}", inputs[0] + 5 * inputs[1] + 10 * inputs[2]);
    }
}
```
- `vec![]`: ベクターを作成。
- `push()`: ベクターに要素を追加
- `len()`: ベクターの要素数を取得
- `v[index]`: 指定したインデックスの要素にアクセス



</details> 

:::note
**Vec 型**
- **同じ型**の要素からなる**可変長**の配列
- ヒープ領域に要素を格納し、要素の追加・削除が可能
:::
:::note
**イテレータ**とは、データ列を順番に処理するための仕組みで `for` ループなどで使用します。

`Vec` 型は `for in` 文で使用すると、自動的にイテレータに変換されます。[^6]

[^6]: 参考: [std::iter - Rust](https://doc.rust-lang.org/std/iter/index.html#for-loops-and-intoiterator)


<details><summary><strong>コード例</strong></summary>

```rust
let values: Vec<i32> = vec![0, 1, 2, 3];

// valuesはi32型を要素とするVec型ですが、自動的にイテレータに変換される
for v in values { // vはi32型
    println!("{}", v);
}
```
</details>
:::
## ネオン街の裏路地（D ランク）
最大値を求める問題です。

**必要な知識**
- 比較演算子（`==` ,`!=` ,`<` ,`<=` ,`>` , `>=`）: 
    比較する数値は**同じ型**である必要がある

<details><summary><strong>問題</strong></summary>

- 入力される値: 
    - 1 行目: 整数 `n`（`1 ≦ n ≦ 20`）
    - 残り `n` 行: 整数 `m`（`1 ≦ m ≦ 999`）
- 期待する出力: 2 行目以降の整数の中から最大の値

</details> 
<details><summary><strong>解答</strong></summary>

```rust
use std::io;

fn main() {
    let mut n_str = String::new(); // 数値の個数(文字列)を格納する変数
    let stdin = io::stdin(); // 標準入力へのハンドルを取得

    stdin.read_line(&mut n_str).unwrap(); // 1 行目の読み取り
    let n: u32 = n_str.trim().parse::<u32>().unwrap(); // 文字列を数値に変換

    let mut max_value: u32 = 0;
    let mut i: u32 = 0;

    for line in stdin.lines() {
        let number_str: String = line.unwrap();

        match number_str.trim().parse::<u32>() {
            Ok(num) => {
                if num > max_value { // 最大値か求める
                    max_value = num;
                }
            }
            Err(_) => {
                continue; // 残りの処理をスキップして次のループに移る
            }
        }
        i += 1; // Rustではi++のような書き方はできない
        if i >= n {
            break; // 3行読み込んだらループを抜ける
        }
    }

    println!("{}", max_value); // 最大値を出力
}
```
- 入力の 1 行目は `read_line`、残りの行は `line` で読み取ります。
- Rust でも `for in` 文の中で、`continue` と `break` を使用することができます。
</details> 

## 自然の残る公園（C ランク）
現在の座標と複数のビーコンの座標が与えられ、現在の座標から**最も近い**ビーコンを求める問題です。具体的には、ユークリッド距離（直線距離）を計算し、最小のビーコンを特定します。


**必要な知識**
- [`.split_whitespace()`](https://doc.rust-lang.org/std/str/struct.SplitWhitespace.html):  文字列を空白で分割し、イテレータを作成
- [`Option` 型](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html#the-option-enum-and-its-advantages-over-null-values): 値が存在しない可能性を表現するための型
- [`.next()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next): イテレータを進め、次の値を`Some` で包んで返す。次の値がない場合 `None` を返す。
- [範囲式](https://doc.rust-lang.org/reference/expressions/range-expr.html): 連続した整数列を生成

<details><summary><strong>問題</strong></summary>

- 入力される値:
    - 1 行目: 以下の要素が半角スペース区切りで与えられる
        - ビーコンの数を表す整数 N
        - 現在の位置の y 座標を表す整数
        - 現在の位置の x 座標を表す整数
    - 残り N 行: 以下の要素が半角スペース区切りで与えられる
        - ビーコンの位置の y 座標を表す整数
        - ビーコンの位置の x 座標を表す整数
- 期待する出力: 最も近いビーコンの番号
    - ※最も近いビーコンが複数存在することはない
</details> 

<details><summary><strong>解答</strong></summary>

```rust
use std::io;

fn main() {
    let mut input = String::new();
    let stdin = io::stdin(); // 標準入力へのハンドルを取得
    // 1 行目の読み取り
    stdin.read_line(&mut input).unwrap();
    // 読み込んだ文字列を空白で分割し、イテレータを作成
    let mut iter = input.split_whitespace();

    let n: usize = iter.next().unwrap().parse::<usize>().unwrap();
    let current_y: i32 = iter.next().unwrap().parse::<i32>().unwrap();
    let current_x: i32 = iter.next().unwrap().parse::<i32>().unwrap();

    let mut closest_index = 0;
    let mut min_distance = i32::MAX; // i32 型の最大値

    for i in 0..n { // 0..n: 範囲式（range expression）
        let mut line = String::new();
        stdin.read_line(&mut line).unwrap();
        let mut iter = line.split_whitespace();

        let beacon_y: i32 = iter.next().unwrap().parse::<i32>().unwrap();
        let beacon_x: i32 = iter.next().unwrap().parse::<i32>().unwrap();
        // ユークリッド距離の平方を計算し、最小のものを探す
        let distance: i32 = (beacon_y - current_y).pow(2) + (beacon_x - current_x).pow(2);

        if distance < min_distance {
            min_distance = distance;
            closest_index = i + 1;
        }
    }

    println!("{}", closest_index);
}
```
- [`next()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next): 帰り値は `Option` 型です。イテレータを進め、次の値を`Some` で包んで返す。次の値がない場合 `None` を返す。
- `unwrap()`: `Option` 型に `unwrap` を使用すると値が `Some` 型の場合は、`Some` の中身を取り出して返します。`None`だった場合パニックを引き起こします。
- `0..n`: Rust の for ループで使われる[範囲式](https://doc.rust-lang.org/reference/expressions/range-expr.html)です。この式は、`0` から `n-1` までの連続した整数列を生成します。
</details> 

:::note
**Option 型**[^7]

`Option` 型は「値が存在するかもしれないし、存在しないかもしれない」という状況を表現するための型です。

`Option` 型は、以下の2つのいずれかの状態を持つことができます。

- `Some(値)`: 値が存在する場合、その値を `Some` で包んで表現します。
- `None`: 値が存在しない場合、`None` で表現します。

`Result` 型と同様に、`match` を使用して `Option` 型 を処理することが一般的ですが、`unwrap()` や `unwrap_or()` を利用することで簡潔に記述できます。


<details><summary><strong>コード例</strong></summary>

```rust
let some_number = Some(5);
match some_number {
    Some(value) => println!("{}", value),
    None => println!("None"),
}

// .unwrap() は、
// Option型が Some の場合は、Some の中身を取り出して返します。
// Option型が None の場合、パニック（回復不可能なエラー）を引き起こします。
let value = some_number.unwrap();
println!("{}", value); // 出力: 5
```
</details> 
:::
[^7]: 参考: [Defining an Enum - The Rust Programming Language](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html#the-option-enum-and-its-advantages-over-null-values)

## 廃マンションの一室（C ランク）
与えられた整数を**特殊な三進法**に変換する問題です。

**必要な知識**
- 平衡三進法

<details><summary><strong>問題</strong></summary>

- 入力される値: 整数 `N`（`-100,000 ≦ N ≦ 100,000`）
- 期待する出力: `N` を特殊な三進法（具体的には `2` を `-1` の意味として扱う特殊な三進法）での数値表現に変換した値

</details> 
<details><summary><strong>解答</strong></summary>

- この特殊な三進法は**平衡三進法**と呼ばれるものです。平衡三進法では、各桁の数字として `-1`, `0`, `1` の3つの値を用います。
- 通常の三進法と同じく、`n` 桁目の数 `m` は十進法で $m \times 3^{n-1}$（平衡三進法の場合、`m` は `-1`, `0`, `1` のいずれか）の数を表します。例えば、三進法で `100` は十進法で $1 \times 3^{3-1}$ (= `9`) です。
- この問題は、`2` は本来の意味ではなく `-1` の意味として扱っています。例えば、平衡三進法で `200` は十進法で $-1 \times 3^{3-1}$ (= `-9`) です。

:::note
平衡三進法が持つ**対称性**を理解すると問題が解きやすくなります。

以下のように、十進法で、ある数を**符号反転**させる（**反数**にする）と、平衡三進法で **`1`** と **`2`** が入れ替わる形になります。
- 十進法で `6` は平衡三進法で `120`
- 十進法で `-6` は平衡三進法で `210`

なぜなら、「ある数の**反数**とは、元の数と反転後の数を足すと `0` になる数」であり、 平衡三進法で `1` と `2`（= `-1`）は互いに「反数」であり、足し合わせると `0` になるからです。

`6` と `-6` の平衡三進法を各桁に注目して足すと、下記のようになり、すべての桁が `0` になります。
```
  120  ...  6
+ 210  ... -6
------
  000  ...  0
```
:::
- 入力値が負の整数の場合、正の整数として平衡三進法に変換します。
- 十進法から平衡三進法への変換は、一般的な進数変換を同じように、元の数が `0` になるまで `3` で割り続け、余りで下の桁から確定させていきますが、
    - 余りが `2` の場合、本来の意味通りに `2` を使えないので、`2=3-1` のように、次の桁に繰り上げて、その桁の数字は `2`（つまり、`-1`）とします。
```rust
use std::io;

fn main() {
    let mut input = String::new();
    let stdin = io::stdin();
    stdin.read_line(&mut input).expect("読込失敗");

    let mut num: i32 = input.trim().parse().expect("整数を入力してください");
    let is_negative: bool = num < 0;

    if num == 0 { // 数値が0になるまで繰り返す
        println!("0");
    } else {
        let mut result = String::new();

        if is_negative {
            num = -num;
        }

        while num != 0 {
            let remainder: i32 = num % 3;
            num /= 3; // num = num / 3;

            if remainder == 2 {
                num += 1; // 次の桁に繰り上げ
                if is_negative {
                    result.push('1'); // 2 --> 1
                } else {
                    result.push('2');
                }
            } else if remainder == 1 {
                if is_negative {
                    result.push('2'); // 1 --> 2
                } else {
                    result.push('1');
                }
            } else {
                result.push('0');
            }
        }
        println!("{}", result.chars().rev().collect::<String>());
    }
}
```
:::note
この問題では、平衡三進法が持つ**対称性**を利用することで問題が解きやすくしました。プログラミング問題では、**扱う対象の性質**に注目することが重要です。
:::
</details> 


## ギャングのアジト（B ランク）
`N` × `N` のグリッドを黒と白で塗ったピクセルアートが**左右対称**か判断する問題です。

**必要な知識**
- [`.skip(n)`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.skip): `n` 個スキップしたイテレータを作成
- [`.rev()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.rev): イテレータを反転

<details><summary><strong>問題</strong></summary>

- 入力される値: 
    - 1 行目: ピクセルアートの大きさを表す整数 `N` 
    - 残り `N` 行: 以下の要素が半角スペース区切りで `N` 個与えられる
        - `0` または `1` の数字
- 期待する出力: ピクセルアートが左右対称なら `Yes` をそうでないなら `No` を出力

</details> 

<details><summary><strong>解答</strong></summary>

ピクセルアートが**左右対称**であるということは、各行の文字列を反転させても同じままということです。

```rust
use std::io;

fn main(){
    // .skip(1)でイテレータの要素を1つスキップ
    let inputs = io::stdin().lines().skip(1);
    
    for input in inputs {
        let line: String = input.unwrap();
        
        if line != line.chars().rev().collect::<String>() {
            println!("No");
            return;
        }
    }
    println!("Yes");
}
```
- `.chars().rev().collect::<String>()` で文字列を反転させています。
    - [`.chars()`](https://doc.rust-lang.org/std/string/struct.String.html#method.chars) で`char` 型（1 文字）のイテレータに変換
    - [`.rev()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.rev) でイテレータを反転
    - [`.collect::<String>()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect) でイテレータを `String` 型に戻す。

</details> 



## 一番通りの繁華街（B ランク）
`N` × `N` の広さの地域の中にある建物同士を線で繋いだ時、正方形になる数を出力する問題です。

**必要な知識**
- [`.enumerate()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.enumerate): イテレータの要素をインデックス付きで返す。
- [`.nth(n)`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.nth): イテレータの `n` 番目の要素を返します。返り値は `Ootion` 型です。

<details><summary><strong>問題</strong></summary>

- 入力される値:
    - 1 行目: 地域の大きさを表す整数 `N`
    - 残り N 行: `#` もしくは `.` からなる `N` 文字の文字列
        - `.` は、その場所は建物であることを表します。
        - `#` は、その場所はなにもないことを表します。
- 期待する出力: `.` の場所を線で縦横に繋いだ時、正方形になる数（斜めは含まない）


```:入力例
5
.#.#.
#####
.#.#.
##.##
.###.
```
```:出力例
3
```
</details> 
<details><summary><strong>解答</strong></summary>

1. 左上から順に各マスを調べます。
2. 建物発見したら、その位置を基準に、同じ行で基準の位置より右に建物があるか調べます。（左側を調べないのは正方形のカウントの重複を防ぐため）
3. 見つかった場合、基準点と右側の点から同じ距離だけ下に離れた 2 つの建物を確認します。（上側を調べないのは正方形のカウントの重複を防ぐため）
4. 基準の位置と同じ行に、他に建物があった場合、`2.` の処理に戻り、なかった場合は、次の行に移って `2.` の処理を行います。

```rust
use std::io;

fn main() {
    let mut n_str = String::new(); // 数値の個数(文字列)を格納する変数
    let stdin = io::stdin(); // 標準入力へのハンドルを取得

    stdin.read_line(&mut n_str).unwrap(); // 1 行目の読み取り
    let n: usize = n_str.trim().parse::<usize>().unwrap(); // 文字列を数値に変換

    let inputs = stdin.lines();
    let mut rows: Vec<String> = Vec::new();

    for input in inputs {
        rows.push(input.unwrap());
    }

    let mut count: i32 = 0;

    for (i, row) in rows.iter().enumerate() { // 行ごとに処理
        for (j, cell) in row.chars().enumerate() { // 行内の文字を一つずつ処理
            if cell == '.' { // 建物を発見した場合
                for k in (j + 1)..n {
                    // 発見した位置よりも左で、新たに建物を発見した場合、
                    if row.chars().nth(k) == Some('.') {
                        let distance = k - j;
                        let new_row = i + distance;
                        // 正方形になるか確認
                        if new_row < n
                            && rows[new_row].chars().nth(j) == Some('.')
                            && rows[new_row].chars().nth(k) == Some('.')
                        {
                            count += 1;
                        }
                    }
                }
            }
        }
    }
    println!("{}", count);
}
```
上記のコードは `for` ループが 3 重になっているので戒厳の余地があります。（先に、建物位置を `Vec` に保存するなど）


</details> 


## 新都心のハイウェイ（A ランク）
最短経路を求める問題です。

**必要な知識**
- 幅優先探索: 最短経路を求めるのに適しています。
- 関数

<details><summary><strong>問題</strong></summary>

縦 `H` マス、横 `W` マスのグリッドがあり、「道マス」と「壁マス」で構成されています。`A` と `B` は道マスのいずれかに配置されています。

`A` は、今いるマスの上下左右方向のいずれかのマスに `B` がいて、かつ `A` と `B` の間に壁マスがない時に限り、`B` を見つけることができます。（なお、1 回の移動により上下左右に隣接する道マスに移動することができます。）

- 入力される値: 
    - 1 行目: 縦方向のマス数 `H` と横方向のマス数を表す整数 `W` が半角スペース区切りで与えられる
    - 残り `H` 行:　
        - 英字大文字 `A`, `B` および半角記号 `.`, `#` からなる長さ `W` の文字列
        - "." は道マスであることを表し、"#" は壁マスであることを表します。"A" と "B" は入力に 1 つずつ存在します。
- 期待する出力: 
    - `A` が `B` を見つけるために必要な最小の移動回数
    - 見つけることができない場合は `-1` を出力

```:入力例
3 5
A....
.#.#.
....B
```
```:出力の例
2
```

</details> 
<details><summary><strong>解答</strong></summary>

- **幅優先探索**を使用して、開始点 `A` からゴール `B` までの最短距離を求めます。
- ただし、ゴールは単なる点ではなく、`B` から上下左右に壁 `#` に当たるまでの範囲が探索対象となります。
- そのため、下記のコードでは `determine_goals` 関数で、ゴール位置 `B` から上下左右に障害物 `#` に当たるまでの範囲を探索対象のゴール集合として求めています。
- その後、`bfs_maze` 関数を呼び出し、開始点から最も近いゴールまでの距離を計算します。

```rust
use std::io;
use std::collections::{VecDeque, HashSet};


// DXとDYを定数配列として定義し、4方向の移動を記述。
const DX: [isize; 4] = [1, 0, -1, 0]; // const で定数を宣言できます。型を省略することは不可です。
const DY: [isize; 4] = [0, 1, 0, -1]; // [] は配列で、Vec と異なり、不変です。

fn main(){
    let mut inputs = io::stdin().lines();
    let first_line = inputs.next().unwrap().unwrap();

    let parts: Vec<usize> = first_line.split_whitespace()
                                    .map(|s| s.parse().unwrap())
                                    .collect();
    let (h, w) = (parts[0], parts[1]);

    let mut maze = vec![vec![' '; w]; h];
    let mut start = (0, 0);
    let mut goal = (0, 0);

    for i in 0..h {
        let line = inputs.next().unwrap().unwrap();
        for (j, c) in line.chars().enumerate() {
            maze[i][j] = c;
            if c == 'A' {
                start = (i, j);
            } else if c == 'B' {
                goal = (i, j);
            }
        }
    }

    let goals = determine_goals(&maze, goal);

    let result = bfs_maze(&maze, start, &goals);
    println!("{}", result);
}
// 関数の返り値は -> の後に記述します。
fn determine_goals(maze: &Vec<Vec<char>>, goal: (usize, usize)) -> HashSet<(usize, usize)> {
    // HashSet 型を使用して、ゴールの座標を記録します。
    let mut goals = HashSet::new();
    let (gx, gy) = goal;
    let h = maze.len();
    let w = maze[0].len();


    let mut y = gy;
    while y > 0 && maze[gx][y - 1] != '#' {
        y -= 1;
        goals.insert((gx, y));
    }

    let mut y = gy;
    while y + 1 < w && maze[gx][y + 1] != '#' {
        y += 1;
        goals.insert((gx, y));
    }

    let mut x = gx;
    while x > 0 && maze[x - 1][gy] != '#' {
        x -= 1;
        goals.insert((x, gy));
    }

    let mut x = gx;
    while x + 1 < h && maze[x + 1][gy] != '#' {
        x += 1;
        goals.insert((x, gy));
    }

    goals
}


fn bfs_maze(maze: &Vec<Vec<char>>, start: (usize, usize), goals: &HashSet<(usize, usize)>) -> isize {
    let h = maze.len();
    let w = maze[0].len();
    let mut queue = VecDeque::new();
    let mut distances = vec![vec![-1; w]; h];
    let mut visited = vec![vec![false; w]; h];

    queue.push_back(start);
    distances[start.0][start.1] = 0;
    visited[start.0][start.1] = true; // 訪問済みフラグ。

    while let Some((x, y)) = queue.pop_front() {
        let current_distance = distances[x][y];

        if goals.contains(&(x, y)) {
            return current_distance;
        }

        for d in 0..4 {
            let nx = x as isize + DX[d];
            let ny = y as isize + DY[d];

            if nx >= 0 && ny >= 0 && (nx as usize) < h && (ny as usize) < w {
                let nx = nx as usize;
                let ny = ny as usize;

                if maze[nx][ny] != '#' && !visited[nx][ny] {
                    queue.push_back((nx, ny));
                    distances[nx][ny] = current_distance + 1;
                    visited[nx][ny] = true;
                }
            }
        }
    }

    -1
}
```
</details> 

## 思い出の屋上（S ランク）

**必要な知識**
- マンハッタン距離

<details><summary><strong>問題</strong></summary>
街は縦 `H` マス、横 `W` マスのグリッドで表され、既存の `M` 個のギャングの縄張りがあります。各縄張りは中心 `(r, c)` からマンハッタン距離 `S` 以下の座標で構成されます。

新たに縄張りを作る際、既存の縄張りと重ならず、可能な限りサイズ `S` を大きくしたいです。

- 入力される値:
    - 1 行目: エリアの行数 `H` と列数 `W`、すでにある縄張りの中心の個数 `M` がこの順に半角スペース区切りで与えられる
    - 残り M 行: 以下の要素が半角スペース区切りで与えられる
        - 張りの中心の座標を表す行番号
        - 張りの中心の座標を表す列番号
        - 縄張りのサイズ
- 期待する出力: 新しい縄張りの最大サイズ `S`（作れない場合は `-1`）。

```:入力例
5 6 3
3 2 2
4 4 0
5 5 1
```
```:出力の例
3
```


</details> 
<details><summary><strong>解答</strong></summary>

```rust
use std::io;

fn main() {
    let mut inputs = io::stdin().lines();
    let first_line = inputs.next().unwrap().unwrap();
    let nums: Vec<isize> = first_line
        .split_whitespace()
        .map(|s| s.parse().unwrap())
        .collect();

    let (h, w, m) = (nums[0], nums[1], nums[2]);
    let mut grid = vec![vec![0; w as usize]; h as usize]; // 0: 空き, 1: 縄張り

    // 縄張り（領域）のマーキング
    for _ in 0..m {
        let line = inputs.next().unwrap().unwrap();
        let nums: Vec<isize> = line
            .split_whitespace()
            .map(|s| s.parse().unwrap())
            .collect();
        let (r, c, s) = (nums[0] - 1, nums[1] - 1, nums[2]);

        grid[r as usize][c as usize] = 1;

        for i in 0..=s {
            for d in 0..i {
                let moves = [
                    (r - i + d, c - d), // 1
                    (r + d, c - i + d), // 2
                    (r + i - d, c + d), // 3
                    (r - d, c + i - d), // 4
                ];
                for (x, y) in moves {
                    if x >= 0 && x < h && y >= 0 && y < w {
                        grid[x as usize][y as usize] = 1;
                    }
                }
            }
        }
    }

    // 新たな縄張りの最大サイズの計算
    let mut max_size = -1;
    for r in 0..h {
        for c in 0..w {
            if grid[r as usize][c as usize] == 0 {
                // 空いている場所
                let mut s = 0;

                'outer: loop {
                    for d in 0..=s {
                        let moves = [
                            (r - s + d, c - d), // 1
                            (r + d, c - s + d), // 2
                            (r + s - d, c + d), // 3
                            (r - d, c + s - d), // 4
                        ];

                        for (x, y) in moves {
                            if x >= 0
                                && x < h
                                && y >= 0
                                && y < w
                                && grid[x as usize][y as usize] == 1
                            {
                                break 'outer;
                            }
                        }
                    }

                    s += 1;
                }

                if max_size < s - 1 {
                    max_size = s - 1;
                }
            }
        }
    }
    println!("{}", max_size);
}
```

</details> 


# データベース問題

データベース問題は全 5 問で、すべて `SQL` に関する問題です。

## 前提知識
まずは `SQL` の概要を紹介します。

`SQL` は『**リレーショナルデータベース**』のデータを操作するための言語です。

リレーショナルデータベースは**関連するデータ**を『**テーブル**』という形式で管理するデータベースです。

テーブルは『**レコード**（行）』と『**カラム**（列）』で構成されています。表計算ソフトを利用したことがある方は馴染みのある形式だと思います。

![テーブル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2801269/d67bf715-6870-2451-1de6-2e1cc92e4099.png)



`SQL` を利用することでデータの追加や削除、更新などの様々な操作を実行できますが、今回は『**テーブルからデータを抽出**する際に使用する `SELECT` 』について解説します。

例えば、以下のような `employees` テーブルがあるとします。
```:例: employees テーブル
+-----+-----------+---------------+------------+---------+
| id  | name      | department_id | hire_date  | salary  |
+-----+-----------+---------------+------------+---------+
| 1   | 田中 太郎  | 1             | 2023-04-01 | 300000  |
+-----+-----------+---------------+------------+---------+
| 2   | 山田 花子  | 2             | 2024-06-15 | 350000  |
+-----+-----------+---------------+------------+---------+
```

`employees` テーブルから `id`, `name`, `salary` のカラム（列）を取得するには以下のような SQL 文を実行します。

```sql
SELECT id, name, salary FROM employees;
```
- `SELECT カラム名1, カラム名2, ...`: 取得したいカラムの名前を指定
- `FROM テーブル名`: データを取得するテーブルの名前を指定

（`SELECT` や `FROM` は**キーワード**と呼ばれ、SQL 文で**特別な意味を持つ単語**です。キーワードは記述する順番に決まりがあります。）

```:結果 
+-----+-----------+---------+
| id  | name      | salary  |
+-----+-----------+---------+
| 1   | 田中 太郎  | 300000  |
+-----+-----------+---------+
| 2   | 山田 花子  | 350000  |
+-----+-----------+---------+
```


それでは、実際に問題を解きながらより複雑な `SQL` について解説していきます。

## データセンター（D ランク）

**必要な知識**
- カラムの指定: `SELECT カラム名`
- テーブルの指定: `FROM テーブル名`

<details><summary><strong>問題</strong></summary>

`person` テーブルの `id` と `name` をすべて出力してください。


```:person テーブルの概要
+-------------+------------+-------------------+
| カラム名     | データ型     | 説明               |
+-------------+------------+-------------------+
| id          | int        | ID                |
| name        | text       | 人物名             |
| biography   | text       | 詳細               |
| created_at  | date       | 出会った日          |
| deleted_at  | date       | 亡くなった日        |
+-------------+------------+-------------------+
```
（問題で `memory` テーブルも与えられていますが今回は使用しません。）

</details> 
<details><summary><strong>解答</strong></summary>

さきほどの例と同じく `SELECT カラム名 FROM テーブル名` でデータを抽出します。
```sql:解答
SELECT id, name FROM person;
```
`テーブル名.カラム名` という形式で、どのテーブルのどのカラムを取得するのかを**明示的に指定**することもできます。

```sql:別解
SELECT person.id, person.name FROM person;
```
</details> 

## 二人が通った教室（Cランク）
**必要な知識**
- 並びかえ: `ORDER BY カラム名 ASC|DESC`
- レコード数の制限: `LIMIT レコード数`

<details><summary><strong>問題</strong></summary>

`memory` テーブルのうち `id` の小さい順（昇順）に 10 件レコードを取得し、それぞれの `id` と `talk` を出力してください。


```:memory テーブルの概要
+-------------+------------+-------------------+
| カラム名     | データ型     | 説明               |
+-------------+------------+-------------------+
| id          | int        | ID                |
| talk        | text       | 会話内容           |
| importance  | int        | 重要度             |
| person_id   | int        | 人物のID           |
| category_id | int        | カテゴリのID        |
| created_at  | date       | 記録日             |
+-------------+------------+-------------------+
```
（問題で `person` テーブルも与えられていますが今回は使用しません。）

</details> 
<details><summary><strong>解答</strong></summary>

今まではすべての**レコード**（行）を取得していましたが、この問題では `id` で昇順に並びかえた後、先頭の 10 件のレコードを抽出します。

```sql:解答
SELECT memory.id, memory.talk FROM memory ORDER BY id ASC LIMIT 10;
```
- レコードの順番の並びかえは `ORDER BY カラム名 ASC|DESC` という形式で行います。（`ASC` は昇順、`DESC` は降順）
- 取得するレコード数の制限は `LIMIT レコード数` という形式で行います。

</details> 

## 会員制ジム（C ランク）
**必要な知識**
- 主キー・外部キー
- テーブル結合: `JOIN テーブル名 ON 結合条件`
- データを絞り込むための条件を指定: `WHERE 条件式`

<details><summary><strong>問題</strong></summary>

`memory` テーブルのうち `importance` が 3 以上で、 `category` の `name` が "悲しみ" の `memory.id`, `memory.talk` を出力してください。

```:category テーブルの概要
+-------------+------------+-------------------+
| カラム名     | データ型     | 説明               |
+-------------+------------+-------------------+
| id          | int        | ID                |
| name        | text       | 記憶カテゴリ名       |
+-------------+------------+-------------------+
```
（`memory` テーブルの概要は『二人が通った教室（Cランク）』で書いたので省略）

</details> 
<details><summary><strong>解答</strong></summary>

この問題は**複数のテーブル**（`memory` と `category`）を結合し、特定の条件（`memory.importance >= 3` かつ `category.name = "悲しみ"`）を満たすレコードを抽出するというものです。

データを絞り込むための**条件が複数のテーブルに渡っているため、テーブルを結合してからデータを絞り込む必要があります。**

```sql:解答
SELECT memory.id, memory.talk
FROM memory 
JOIN category ON memory.category_id = category.id
WHERE category.name = "悲しみ" AND memory.importance >= 3
```

- `JOIN テーブル名 ON 結合条件`: 2つのテーブルに**共通のデータ**が存在する場合に、そのデータを持つレコードのみを結合（**内部結合**）
- `WHERE 条件式`：データを絞り込むための条件を指定

テーブルの**結合条件**として、`memory` テーブルのカラムに `category_id` を使用しています。これは `category` テーブルの `id` を指し示す**外部キー**です。

:::note
**主キー・外部キー**
各レコードには、テーブル内の各レコードを**一意に識別**するための**主キー**があります。（`category` テーブルでは `id` が主キーです。）

また、主キーは他のテーブルとの**リレーションシップ**（関係性）を表現するためにも使用されます。他のテーブルの主キーを指し示すカラムは**外部キー**と呼ばれます。（`memory` テーブルでは `category_id` が外部キーです。）
:::

外部キーは複数のテーブルを結合する上で重要な役割を果たします。

以下は `memory` テーブルの `category_id` と `category` テーブルの `id` をもとに結合（JOIN）した例です。


```sql:テーブルの結合例
SELECT memory.id, memory.talk, memory.importance, memory.category_id, category.name 
FROM memory 
JOIN category ON memory.category_id = category.id;


-- memory テーブルのデータ --
+----+--------------------+------------+-----------+-------------+------------+
| id | talk               | importance | person_id | category_id | created_at |
+----+--------------------+------------+-----------+-------------+------------+
| 1  | 失敗して落ち込んでいる | 4          | 101       | 2           | 2024-01-10 |
+----+--------------------+------------+-----------+-------------+------------+
| 2  | 楽しかった旅行の話    | 5          | 102       | 1           | 2024-01-11 |
+----+--------------------+------------+-----------+-------------+------------+
| 3  | 友達と喧嘩してしまった | 3          | 103       | 2           | 2024-01-12 |
+----+--------------------+------------+-----------+-------------+------------+

-- category テーブルのデータ --
+----+--------+
| id | name   |
+----+--------+
| 1  | 喜び    |
+----+--------+
| 2  | 悲しみ  |
+----+--------+

-- テーブル結合の結果 --
+----+--------------------+------------+-------------+-------+
| id | talk               | importance | category_id | name  |
+----+--------------------+------------+-------------+-------+
| 1  | 失敗して落ち込んでいる | 4          | 2           | 悲しみ |
+----+--------------------+------------+-------------+-------+
| 2  | 楽しかった旅行の話    | 5          | 1           | 喜び   |
+----+--------------------+------------+-------------+-------+
| 3  | 友達と喧嘩してしまった | 4          | 2           | 悲しみ |
+----+--------------------+------------+-------------+-------+
```

`WHERE` を使用することでデータを絞り込むことができます。複数の条件をすべて満たすデータを抽出したい場合、`AND` を使います。
```sql
WHERE category.name = "悲しみ" AND memory.importance >= 3
```
</details> 


## 打ち捨てられた図書館（Bランク）
**必要な知識**
- テーブル結合: `JOIN テーブル名 ON 結合条件`

<details><summary><strong>問題</strong></summary>

`log` テーブルの `created_at` が 2085-08-01 〜 2087-10-20 の期間の `memory.id`, `memory.talk`, `battle.result` を出力してください。

```:log テーブルの概要
+-------------+------------+-------------------+
| カラム名     | データ型     | 説明               |
+-------------+------------+-------------------+
| memory_id   | int        | ID                |
| battle_id   | int        | カテゴリのID        |
| created_at  | date       | 記録日             |
+-------------+------------+-------------------+
```

```:battle テーブルの概要
+-------------+------------+-------------------+
| カラム名     | データ型     | 説明               |
+-------------+------------+-------------------+
| id          | int        | ID                |
| importance  | int        | 重要度             |
| result      | text       | 結果               |
| created_at  | date       | 記録日             |
+-------------+------------+-------------------+
```

（`memory` テーブルの概要は『二人が通った教室（Cランク）』で書いたので省略）

</details> 
<details><summary><strong>解答</strong></summary>

```sql:解答
SELECT memory.id, memory.talk, battle.result
FROM log 
JOIN memory ON log.memory_id = memory.id
JOIN battle ON log.battle_id = battle.id 
WHERE log.created_at >= "2085-08-01" AND log.created_at <= "2087-10-20"
```
- `log` テーブルの `created_at` が 2085-08-01 〜 2087-10-20 の期間であるという条件は`WHERE` で表現します。
- `JOIN` は複数回使用できます。今回取得するカラムは `memory` テーブルと `battle` テーブルにあるので、この 2 つのテーブルを `log` テーブルに結合します。

`log` テーブルには `結合条件` で使用する `memory` と `battle` のテーブルの `id` がどちらもあるので `JOIN memory`、`JOIN memory` どちらを先に記述しても問題ありません。

```sql:別解
SELECT memory.id, memory.talk, battle.result
FROM memory
JOIN log ON log.memory_id = memory.id
JOIN battle ON log.battle_id = battle.id 
WHERE log.created_at >= "2085-08-01" AND log.created_at <= "2087-10-20"
```
`FROM log` ではなく `FROM memory` にした場合、**`JOIN log` を `JOIN battle` より先に記述**しなければ、`memory` テーブルに `battle_id` カラムがないのでエラーが発生します。
</details> 

## 巨大コーポの最上階（Aランク）
**必要な知識**
- `JOIN` の順番

<details><summary><strong>問題</strong></summary>

`log`, `memory`, `battle`, `category`, `person` のテーブルがあります。

`person` のうち重要度が 5 の人物の `person.deleted_at` と同じ `battle` の `battle.created_at` から `memory` と `battle` と `person` の `memory.id`, `memory.talk`, `person.name`, `battle.created_at` を出力してください。
</details> 

<details><summary><strong>解答</strong></summary>

**依存関係に基づいて、結合する順番を決めることが重要です。**

問題文から `category` テーブルは使用しないことがわかるので、 このテーブル以外の依存関係を考えます。

`person` テーブルには外部キーがない一方、他のテーブルには `person_id` のカラムが存在します。今回は `person` テーブルを基準にテーブルを結合します。


```sql:解答
SELECT memory.id, memory.talk, person.name, battle.created_at
FROM person
JOIN battle ON battle.person_id = person.id
JOIN log ON log.person_id = person.id
JOIN memory ON log.memory_id = memory.id
WHERE person.importance = 5 AND person.deleted_at = battle.created_at;
```

- `JOIN memory ON log.memory_id = memory.id` は `log` テーブルを先に結合しないと `log.memory_id` を見つけることができないため、エラーが発生します。
- `person` のうち重要度が 5 の人物の `person.deleted_at` と同じ `battle` という条件は `WHERE` で表現。

```sql:別解
SELECT memory.id, memory.talk, person.name, battle.created_at
FROM log
JOIN person ON log.person_id = person.id
JOIN battle ON battle.person_id = person.id
JOIN memory ON log.memory_id = memory.id
WHERE person.importance = 5 AND person.deleted_at = battle.created_at;
```
依存関係に基づいて `JOIN` の順番に気をつければ、他のテーブルを基準にテーブル結合（内部結合）を行なっても同じ結果になります。

</details> 


# おわりに
本記事では「電脳少女プログラミング2088 ─壊レタ君を再構築─」を題材に、Rust と SQL の基礎を解説しました。

Rust では 標準入出力や、よく使用される型（文字列型や数値型、Vec 型、イテレータ）の基本的な扱い方を中心に解説しました。より詳しく学びたい方は、有志によって翻訳されている [Rustの日本語ドキュメント](https://doc.rust-jp.rs/)を参考にしてください。

SQL では `SELECT` のみを扱いましたが、`INSERT`、`UPDATE`、`DELETE` などのデータ操作も学ぶことで、より実践的なデータベース操作が可能になります。