学校の課題になりそうな文字列計算機をワンライナーで作ってみた

# TL;DR;

`"1+2*3-4"`という文字列を先頭から順に計算(`(1+2)*3-4`)して`5`を出力するコードを1行で書いてみた。

```C#
Regex.Matches("1+2*3-4", @"(^|[+\-*/]) *(\d+) *").Cast<Match>().Select(m => new { Op = m.Groups[1].Value, Val = m.Groups[2].Value }).Aggregate((r, c) => new { Op = c.Op, val = new DataTable().Compute(r.Val + c.Op + c.Val, "").ToString() }).Val;
```

# ネタ

C#の課題で下記の問題があったと仮定する。

> `1+2*3-4`のような文字列を受け取ると先頭から順に自然数の加減乗除を行い、計算結果を表示するプログラムを作りなさい。
> なお演算子の優先順位は考慮せず括弧は使用しないものとする。

演算子の優先順位などを考慮して文字列を動的に計算するならば[System.Data.DataTable.Compute](https://learn.microsoft.com/ja-jp/dotnet/api/system.data.datatable.compute?view=net-7.0)で実現できる。

```C#
var s = "1+2*3-4";
var result = new System.Data.DataTable().Compute(s, "");
Console.WriteLine(result);  // 3
```

しかし先頭から順に計算すると`(1+2)*3-4`の順に計算して`5`を導出しなければならない。
出題者はfor文やif文を使ってループ、条件分岐、変数の基本を学ばせたいのであろう。
初学者にとってはなかなか難しい問題なので、プログラムが苦手な人は頭がこんがらかってしまうかもしれない。

そんな人に意地悪な先輩が「ふん、そんなに苦手なら**`if`も`int`も`for`も使わなければいい**だろう」と言うかもしれない。
その挑発にあなたは「**できらぁっ！**」と返してしまったとしよう。

安心してほしい。
それだけなら`case`文と`long`型と`foreach`文を使えば朝飯前だ。
だがきっと意地悪な先輩はニヤリと笑ってこう続けるのだ。

「ならばやって見せろ。`if`も`int`も`for`も`case`も`三項演算子`も`long`も`float`も`double`も`decimal`も`short`も`ushort`も`uint`も`ulong`も`foreach`も`while`も`do-while`も`goto`も`再帰処理`も`NuGetパッケージ`も使わずにな！」
と、**後付けの方が長い**セリフを。

安心してほしい。
**できらぁ！**

## サンプルコード

```C#
using System;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
 
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var s = "1+2*3-4";
            // ワンライナー
            var result = Regex.Matches(s, @"(^|[+\-*/]) *(\d+) *").Cast<Match>().Select(m => new { Op = m.Groups[1].Value, Val = m.Groups[2].Value }).Aggregate((r, c) => new { Op = c.Op, val = new DataTable().Compute(r.Val + c.Op + c.Val, "").ToString() }).Val;
            Console.WriteLine(result);
        }
    }
}
```

このコードでは`for`も`if`も`int`も使っていない。
DataTableに加えて正規表現とAggregateと匿名型があればワンライナーで実現できる。

さあ、このコードを提出して先輩や他の生徒に差をつけよう！
などという甘言に乗ると普通に**単位を落とす**ので注意すること。

## 解説のようなもの

ざっくり。

```
Regex.Matches(s, @"(^|[+\-*/]) *(\d+) *")
```
(`行頭`または[`四則演算子`])の後に連続する`数字`にマッチする正規表現である。(数字の前後には`0個以上のスペース`を含むことができる)

例えば`12+ 3-567 `であれば、`12`と`+ 3`と`-567 `がそれぞれマッチ対象となる。
`Regex.Matches`の戻り値[`MatchCollection`](https://learn.microsoft.com/ja-jp/dotnet/api/system.text.regularexpressions.matchcollection?view=net-7.0)には、上記の`12`と`+ 3`と`-567 `がコレクションとして格納される。

```
.Cast<Match>().Select(m => new { Op = m.Groups[1].Value, Val = m.Groups[2].Value })
```

`.Cast<Match>()`は、`MatchCollection`をLinqで使えるようにするおまじない。

`new { Op = "hoge", Val = "fuga" }`は[匿名型](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/types/anonymous-types)の宣言である。
`Op`と`Val`変数を持つクラス名のないオブジェクトを作ることができる。

正規表現の`()`の中身はマッチごとにグループとして取り出せる。
前述の例ならば、`12`と`+ 3`と`-567 `はそれぞれ`["", "12"]`と`["+", "3"]`と`["-", "567"]`がグループ配列になると考えてよい。
(インデックス`0`はマッチした文字列全体が入るので、インデックスは`1`から始まる)

```C#
.Aggregate((r, c) =>
    new {
        Op = c.Op,
        val = new DataTable().Compute(r.Val + c.Op + c.Val, "").ToString() // 計算結果の戻り値(Object型)を文字列型に変換
        }).Val; // 計算結果の値(Val)を取得する
```

LinqのAggregateは配列を先頭から順に取り出して自由に集計できる式だ。
例えば`new int[] { 1, 2, 3 }.Aggregate((r, c) => c%2==0 ? r+c : r-c)`を考えてみよう。
Aggregateは最初に`r=1`, `c=2`を代入して計算を行い、その結果の`3`を次のrに代入する。そして次の値の`3`をcに代入してまた計算を行い、すべての計算結果の`0`を返す。

その計算を既出のSystem.Data.DataTable.Computeに肩代わりしてもらうことで、先頭から順に計算した結果を取得できるのだ。

正直こんなざっくり解説を聞いても**さっぱり分からない**方が多数派だと思うが、ネタなのでこの辺で。
あとはググったりコーディングして自分の目で確かめてくれ！

# 言い訳

ちなみに私自身が、このコードを作ったはいいけど**使いどころがなくてネタとして供養**したかったのがこの記事のきっかけである。
中級者以上の方にご笑納いただいたり、正規表現やLinqや匿名クラスが分からない方の学習材料になれば望外の喜びである。