💨

Typstでレポートを「実装」する

に公開

この記事は一関高専Advent Calender 2025 13日目 && Typst Advent Calendar 2025 15日目の記事です(遅くなってすみません...!)。

変なタイトルですが最後まで読めば何が言いたかったのか分かってもらえると思います。

What's Typst?

Typstは2023年にリリースされたRust製の組版システムです。

https://typst.app/

組版といえば \LaTeXが最強なイメージがありますが、Typstは次の点で優れています。

  • 導入が簡単(LaTeXのインストールは数時間...)
  • コンパイルが爆速(LaTeXのコンパイルは数分以上かかることも...)
  • マークアップ言語を採用(見やすい)
  • 数式が(LaTeXに比べて)簡潔に書ける

今ならなんとVSCodeの拡張機能Tinymist Typstをインストールするだけですぐに始められます(もちろんCursor/Zed向け拡張もCLIもあるしNeoVim用プラグインもある)。
Typstの詳しい紹介はこちらの記事こちらの記事、使い方は公式ドキュメントExample集に委ねることにして、この記事ではTypstのスクリプトモードを使用して計算の自動化及びデータ処理を行う方法を紹介します。

Typstのスクリプトモード

Typstには3つのモードがあります。

  • マークアップモード
  • 数式モード
  • スクリプトモード

Typstはマークアップモードが見やすくて数式モードがLaTeXより書きやすくていいぞ!といった感じで紹介されがちなのですが、Typstの真のすごさはスクリプトモードにあるということを伝えたくてこの記事を書きました。

スクリプトモードのすごさが分かる例を公式より引用します(いまだにtypstはシンタックスハイライトされません...zennは早くshikiを導入してほしい)。

https://github.com/tkxp200/typst-code/blob/02386aa7ff7510e32da0635d01c547da742fe3f5/test.typ#L15-L31

なんと文書中に突然変数や関数を宣言したり、条件分岐や繰り返し処理を書くことができます。
この例では次のような出力を得られます。

スクリプトモードを利用すると文書作成をTypstで完結できるほか、途中計算を表示できることや、表データなどの操作が楽に行えるといった恩恵を受けることができます。

スクリプトモードがあまりにも強力過ぎてテトリスをプレイできるようにした猛者まで現れました(画像は引用)。

https://typst.app/universe/package/soviet-matrix

最近ではトランプを表示できるパッケージもあるよう(画像は引用)なので、別のゲームが遊べる日が来るかも。

https://typst.app/universe/package/deckz

スクリプトモードの利点が理解できたところで、早速例題をスクリプトモードで解いてみたいと思います。

計算の自動化

手始めに次のような確率の問題を解いていきます。

問題

https://github.com/tkxp200/typst-code/blob/9f82cbb0c21a2ddd3bb1fcd647351a36b2a9f6ad/test.typ#L61-L69

数式中に配列を表示

まずはデータを配列で保持するようにします。

#let numbers = (24.1, 25.2, 26.5, 25.3, 24.2, 24.8, 25.6)

このまま$numbers$としたいところですが、残念ながらうまくいきません。一つずつ取り出して表示させる必要があります。
次のような関数を定義して利用します。

https://github.com/tkxp200/typst-code/blob/f539a47c354adf64a2ff1bbd2ec5e10a83a16cdf/test.typ#L39-L48

後は数式モード中でJoin関数を呼び出すだけです。

https://github.com/tkxp200/typst-code/blob/f539a47c354adf64a2ff1bbd2ec5e10a83a16cdf/test.typ#L63-L66

これにより全く同じ結果を得ることができました。このまま平均を計算していきます。

平均の計算

typstでは直接平均を計算する関数はありませんが、合計を計算するarray.sum()があるので、これを利用します。

#numbers_avg = numbers.sum() / numbers.len()

https://github.com/tkxp200/typst-code/blob/444221ebaacb1ef93dfada5a4ee066cbe9af0b32/test.typ#L68-L76

このままだと小数点以下が大きいので、四捨五入を行います。calc.round()を利用します。

https://github.com/tkxp200/typst-code/blob/86c065b3aa2e11ab1d89988a48171f822feb9b31/test.typ#L68-L76

このようにしてTypstで計算することができました。

分散の計算

分散(ここでは標本分散の意)を計算する際は繰り返し処理を利用するのがよさそうです。
念のため分散の式を書いておきます。

s^2 = \frac{1}{n} \sum_i (x_i - \overline{x})^2

例によって分散を計算する関数を作って利用します。

https://github.com/tkxp200/typst-code/blob/915742829ca6aac143f32286e85c5419cef53357/test.typ#L78-L85

後は四捨五入して表示するだけです。標準偏差を計算できるように変数に代入しています。

https://github.com/tkxp200/typst-code/blob/915742829ca6aac143f32286e85c5419cef53357/test.typ#L87-L94

標準偏差の計算

分散の正の平方根を取ることで計算できます。平方根の計算にはcalc.sqrt()を使用します。

https://github.com/tkxp200/typst-code/blob/061716cb51ac440aeba8cdf1f66b6b0d4bd7b02e/test.typ#L96-L99

ということで一度も手計算を行わずに問題を解くことができました。

そこまで標準の関数は充実しているわけでは無いですが、基本的な機能は有しているので様々な場面で活用できそうです。

計算自動化の高度な例(長いです)

行列計算

図形の描画パッケージであるcetzの内蔵関数を利用することで行列計算を行うことができます。
ここでは3B1Bの動画から次の問題を解いてみます(計算のみに絞り、重要な考察ポイントを飛ばしています)。

問題

A = \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}

とする。

  • A^2, A^3, A^4を計算しなさい。
  • 行列Aの固有ベクトルv_1, v_2は以下である。対角化を用いてA^nを計算しなさい。
v_1 = \begin{bmatrix} 2 \\ 1+\sqrt{5} \end{bmatrix} \quad v_2 = \begin{bmatrix} 2 \\ 1-\sqrt{5} \end{bmatrix}

A^2,A^3の計算

cetzをインポートします。

https://github.com/tkxp200/typst-code/blob/668670e520d568970959d82db9dd6318dd28655a/calc_automation/test.typ#L3

行列Aを定義します。

https://github.com/tkxp200/typst-code/blob/668670e520d568970959d82db9dd6318dd28655a/calc_automation/test.typ#L106

行列積を計算する際はmatrix.mul-mat()を使用します。

https://github.com/tkxp200/typst-code/blob/668670e520d568970959d82db9dd6318dd28655a/calc_automation/test.typ#L107-L109

行列の表示にはnumtyパッケージを利用します。インポートして、

https://github.com/tkxp200/typst-code/blob/668670e520d568970959d82db9dd6318dd28655a/calc_automation/test.typ#L4

nt.p()で表示できます。

https://github.com/tkxp200/typst-code/blob/dae3ccc194e00aba13b0c37496376d8d7fe4672b/calc_automation/test.typ#L110-L115

見ての通りこの行列には次のような特徴があります。

A^n = \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}^{n} = \begin{bmatrix} F_{n-1} & F_n \\ F_n & F_{n+1} \end{bmatrix}

F_nはフィボナッチ数列の第n項です。

対角化

対角化の詳細は飛ばします。式で書くと、行列Aに対して

A = PDP^{-1}

のように表すことができます(制限はある、詳細は前述の動画などを参照)。
PAの固有ベクトルを横に並べた行列、DPに対応する固有値を対角線上に並べた対角行列です。

まずは固有値を計算します。まずv_1に対応する固有値を計算します。

\begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix} \begin{bmatrix} 2 \\ 1+\sqrt{5} \end{bmatrix} = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}

とすると固有値は線形性より\lambda_1 = \frac{x_1}{2} = \frac{x_2}{1+\sqrt{5}}になります。
今回は簡単なので頭の中で計算すると、x_1 = 1+\sqrt{5}です。よって\lambda_1は、

https://github.com/tkxp200/typst-code/blob/7ecd14da74dc0a937cef7dfb69ead785feb546d7/calc_automation/test.typ#L117

となります。同様にv_2に対応する固有値は

\begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix} \begin{bmatrix} 2 \\ 1-\sqrt{5} \end{bmatrix} = \begin{bmatrix} y_1 \\ y_2 \end{bmatrix}

から線形性より\lambda_2 = \frac{y_1}{2} = \frac{y_2}{1-\sqrt{5}}になります。
頭の中で計算すると、y_1 = 1-\sqrt{5}です。よって\lambda_2は、

https://github.com/tkxp200/typst-code/blob/7ecd14da74dc0a937cef7dfb69ead785feb546d7/calc_automation/test.typ#L118

となります。
表示してみると次のようになります。

ここで\lambda_1は黄金比と呼ばれているやつです。
ということでP,Dが求まりました。

P = \begin{bmatrix} 2 & 2 \\ 1+\sqrt{5} & 1-\sqrt{5} \end{bmatrix} \quad D = \begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix}

次にPの逆行列P^{-1}を計算します。matrix.inverse()を使用します。

https://github.com/tkxp200/typst-code/blob/cdd62b4cff14dacff7bed51a8dd8c562bfdc572c/calc_automation/test.typ#L127-L128

次のような行列になるようです。

最後にA^nを計算する関数を作成します。

\begin{align*} A^n &= (PDP^{-1})^n \\ &= PDP^{-1}PD\cdots P^{-1}PDP^{-1} \\ &= PD^nP^{-1} \end{align*}

です。ここでDは対角行列なので自分で計算する関数を作ることにします。

https://github.com/tkxp200/typst-code/blob/48128c3842394a1856ba589ba34ab30ea77f2fe4/calc_automation/test.typ#L129-L132

あとは関数を実装するだけです。

https://github.com/tkxp200/typst-code/blob/6a9997853aec485b93272dd18d0b8923eca3f504/calc_automation/test.typ#L134-L137

試しにA^{20}を計算してみましょう。

https://github.com/tkxp200/typst-code/blob/6a9997853aec485b93272dd18d0b8923eca3f504/calc_automation/test.typ#L142-L144

F_{19} = 4181, F_{20} = 6765, F_{21} = 10946なので誤差はあれど正しく計算できていることが分かります。

続きはデータ処理の下でいろいろ書こうと思います。

ちなみに触れませんでしたが固有値を計算するパッケージ多項式の根を求めるパッケージもあるのでそこそこのことはできると思います。

ここからはスクリプトを使用したデータ処理の例を紹介します。

データ処理

今回はChatGPTに適当に作ってもらったcsvデータを利用し、表とグラフを作成してみます。

日付,商品カテゴリ,商品名,単価,販売数
2025/01/01,飲料,コーヒー,150,12
2025/01/01,飲料,お茶,120,8
2025/01/01,食品,パン,180,15
2025/01/02,飲料,コーヒー,150,10
2025/01/02,食品,パン,180,9
2025/01/02,食品,おにぎり,130,20
2025/01/03,飲料,お茶,120,14
2025/01/03,食品,パン,180,11
2025/01/03,食品,おにぎり,130,18

Typstではcsv()関数を用いることで配列として読み出すことができます。

https://github.com/tkxp200/typst-code/blob/4d6148c18cd9d5462ba36b012d8dc33b43979b8a/calc_automation/test.typ#L107-L109

出力

次のような例に取り組んでみます。

  • 各行の左端の列に番号を挿入する
  • 各行の売り上げを計算する。
  • 累計売上を合算する

先頭行の切り分け

データの先頭行は各データが何を示しているかのヘッダーで、このままだと数値操作の邪魔になります。
そのため別変数に切り分けることにします。remove()関数を用います。

https://github.com/tkxp200/typst-code/blob/cc0f80f812d464aa8cdc5a7d3b336afea882dc7c/calc_automation/test.typ#L107-L112

このようにして先頭行を変数headerに、データを変数dataに格納できました。

表の作成

結果が見やすいので先に表を作成します。
headerは一次元配列なのでそのまま..headerで渡すことができます。
一方dataは二次元なので1個1個展開して渡す必要があります。展開を行うにはflatten()を利用します。
もし表の使い方に困ったらとりあえず公式のTableガイドを見ることをお勧めします。

https://github.com/tkxp200/typst-code/blob/ebd9d4a28774303cf1f5e851a8b21a93f15640a7/calc_automation/test.typ#L112-L118

TypstのTableではtable.header()を用いてヘッダーを明示的に示す方法があります。
これによって少しだけ読みやすくなります。
あと今のままだと表が窮屈に見えるので、インセットを設けることにします。

https://github.com/tkxp200/typst-code/blob/194e606c0f16707bef1de78d094bca27d82922aa/calc_automation/test.typ#L112-L118

少しだけ見やすくなりました。

番号の追加

表の左端の列に番号を1番付で追加していきたいと思います。

まずはheaderの左端に番号の列を追加しましょう。

https://github.com/tkxp200/typst-code/blob/3466b8afb5e9b353ba4ba42bb526ac44a799519e/calc_automation/test.typ#L110

データの方はenumerate()関数を利用するとインデックスの項目を追加してくれます。デフォルトだと0から始まるので、startに1を渡します。また、letなどの句で始まらないコードブロックは#()または#{}で囲む必要があります。

https://github.com/tkxp200/typst-code/blob/3466b8afb5e9b353ba4ba42bb526ac44a799519e/calc_automation/test.typ#L112-L114

現在は次のようなデータになっているので番号とデータを一つにまとめましょう。map()関数を利用することですべてのアイテムに関数を適用することができます。

https://github.com/tkxp200/typst-code/blob/a8a098c972588e00c492090c491475aa4273d31f/calc_automation/test.typ#L113-L115

これによりデータが整形されます。

また、このままだとデータに整数型が含まれるためエラーとなってしまいます。

map()にキャスト関数を入れて修正します。

https://github.com/tkxp200/typst-code/blob/65ff0eb28d07b4539ab25d13b17c1489dabc08eb/calc_automation/test.typ#L116-L123

番号の列を追加することができました。

売上の計算

まずは売上列をヘッダーに追加します。最後に追加する場合はpush()関数を利用できます。

https://github.com/tkxp200/typst-code/blob/a8a098c972588e00c492090c491475aa4273d31f/calc_automation/test.typ#L111

売上は単価×販売数です、とりあえずforループで実装することにします。

https://github.com/tkxp200/typst-code/blob/6ab4d004dbe70e38717b5a609dc3b4940383577d/calc_automation/test.typ#L113-L121

売上の列を追加できました。

累計売上の計算

その番号時点での累計売上の列を追加します。例によってヘッダーに累計売上列を追加します。

https://github.com/tkxp200/typst-code/blob/5defd40e045ea6c8413256249fad18a5ef6be8ad/calc_automation/test.typ#L112

新しい変数total_priceを宣言し、売上計算部分にtotal_priceを加算するロジックを追加します。

https://github.com/tkxp200/typst-code/blob/5defd40e045ea6c8413256249fad18a5ef6be8ad/calc_automation/test.typ#L114-L125

完成です。元のCSVデータから番号、売上、累計売上を計算して表にまとめることができました。

グラフの作成

先ほどのデータからグラフを作成してみたいと思います。
データをグラフで可視化する際はlilaqパッケージを用います。

ファイルの上部にimport文を書いてインポートしておきます。

https://github.com/tkxp200/typst-code/blob/10c3c78a97e468e503e900b02812fa590e146ea8/calc_automation/test.typ#L3

日付ごとの売上総額のプロット

日付とその日の売上総額を棒グラフにしたいと思います。
そのためにはデータから日付:その日の売上総額という形の辞書を得る必要があります。

まずデータから日付と売上のペアを取得します。インデックス1が日付、-2が売上でした。

https://github.com/tkxp200/typst-code/blob/10c3c78a97e468e503e900b02812fa590e146ea8/calc_automation/test.typ#L138

このようにデータを取得できます。

typstには直接重複から合計を出す関数はありませんが、関数を用いて配列を走査するfold()関数が用意されています。

fold()関数は次のように呼び出します。

array.fold(init, function())

これは次のコードと等価です(多分)。

array.fold(init, function()) = {
    let acc = init
    for item in array{
        acc = function(acc, item)
    }
    return acc
}

最初にaccinitで初期化されます。その後配列の要素ごとにaccと配列の要素が関数に渡され、その戻り値がaccに代入されます。そして配列の要素すべてを走査した最後のaccが返される仕組みです。
例えば次のようにすれば配列の各要素の二乗和を計算することができます。

data.fold(0, (acc, x) => acc + x*x)

これをうまく用いることで日ごとの売上総額を計算できます。

https://github.com/tkxp200/typst-code/blob/fe1c25c32d0b5859a7a999d771875dd403f53957/calc_automation/test.typ#L138-L145

少し複雑なので解説します。

最初の

(:),

accの初期値に空の辞書を代入することを指しています。
第二引数の関数

(acc, x) => {
    let day = x.at(0)
    acc.insert(day, acc.at(day, default:0) + x.at(1))
    return acc
}

はキーを日付、値を売上として辞書を更新しています。もし辞書にキーが登録されていればその値に加算し、なければdefaultである0に加算して登録されます。

day_total_salesを見ると、ちゃんと日付ごとの売上総額を計算できていることが確認できます。

あとはlilaqパッケージを用いて図にプロットします。

https://github.com/tkxp200/typst-code/blob/fe1c25c32d0b5859a7a999d771875dd403f53957/calc_automation/test.typ#L148-L168

lilaqパッケージの詳細については公式ドキュメントを参照してください。

データ操作の高度な例(計算自動化の高度な例の続き)

先は対角化を用いて行列のべき乗を計算しフィボナッチ数列と等しくなることを確かめました。
ここからはフィボナッチ数列と固有値\lambda_1 = 1.61803...との関連性についてデータ操作を用いて確かめたいと思います。

数列のテーブル化・グラフ化

先ほど作成したCalculatePowerOfMatrixA()関数の右上の成分を取り出してテーブルにしていきます。まず右上の成分を取り出して四捨五入して返す関数を作成します。

https://github.com/tkxp200/typst-code/blob/6d4cd92f3d05cf6007f5f827711398cdd06d806f/calc_automation/test.typ#L139-L142

表にまとめます。試しに1~10まで表示してみます。

https://github.com/tkxp200/typst-code/blob/603e302191010b4a1e1c3c961572ddcd3cc4cf25/calc_automation/test.typ#L144-L153

表だとあまりよく分からないので、グラフで表示してみることにします。
折れ線グラフを利用して1刻みでプロットします。

https://github.com/tkxp200/typst-code/blob/603e302191010b4a1e1c3c961572ddcd3cc4cf25/calc_automation/test.typ#L155-L161

どうやらフィボナッチ数列は不規則ではなく指数関数的に増大しているようです。
ではその底はどのくらいなのか確かめていきます。
F_{n+1}に対してF_{n}を割ることで底を計算します。まずは表に追加してみます。

https://github.com/tkxp200/typst-code/blob/8ce5ca6ccc8030a10ad0d5f8df45836a89aa6984/calc_automation/test.typ#L146-L155

(おそらく皆さん予想通り)固有値の一つである1.618033...に近づいていることが確認できます。
念のためグラフでも確認してみます。

https://github.com/tkxp200/typst-code/blob/8ce5ca6ccc8030a10ad0d5f8df45836a89aa6984/calc_automation/test.typ#L157-L177

グラフで見ても比は固有値\lambda_1に収束していることが確認できます。

ということで組版システムでフィボナッチ数列の増大率と行列Aの固有値が黄金比に等しくなることを確認した最初の例となったのではないでしょうか(笑)。
コードブロックをクリックするとGitHubに飛べるのでnを色々変化させて結果をみるなどいろいろ遊んでみてもらえると嬉しいです。

ちなみに元のtypstコードを別PCでコンパイルしましたが1秒もかかりませんでした。
行列計算入ってるのに速すぎだろ...

終わりに

ExcelとWordを使おう

Typstは何でもできるしもうこれでいいじゃん!となるんですが将来会社に入ったときに役に立たないのでちゃんとExcelとかWordを練習すべきなのだと思います。このままTypstを使い続けてもただの逆張りオタクが爆誕するだけです。

一関高専 AdventCalender 2025 明日の記事はSakaiさんによる高専DCON2025体験記-筋電義手-,
Typst Advent Calender 2025 明日の記事はhikoharu14142さんのTypstのOffice拡張機能でした。

それでは最後までお読みいただきありがとうございました。
またどこかでお会いしましょう。

Discussion