Week 8: グループワーク2(1回目)

今日やること

はじめに

  • 最初に少し講義をします。

  • その後、チームに分かれて作業を行います。

内包表記

便利ですが慣れないとわかりにくい記法として、内包表記があります。これは、数学の集合記法に似ています。例えば、\(x\)が\(1\)から\(5\)までの整数の集合を考えます。 この集合の各要素を二乗した集合である\(\mathcal{S}\)を考えたいとします。これは数式としてはセットビルダーノテーションを用いて次のように書けます。

\[ \mathcal{S} = \{x^2 \mid x \in \{1, \dots, 5\}\} \]

これと同様の処理が、juliaでは次のように書けます。

S = [x^2 for x in 1:5]

この結果は次のようになります。

5-element Vector{Int64}:
  1
  4
  9
 16
 25

ここでは得られるものは集合ではなくベクトルですが、数式の場合と同様の処理を実現できています。

また、この表記は多次元にも拡張できます。以下のように、ijから行列を構成できます。

[2i + j for i in 1:4, j in 1:3]
4×3 Matrix{Int64}:
 3   4   5
 5   6   7
 7   8   9
 9  10  11

時間計測

juliaで作業の時間を計測するには、関数の先頭に@timeをつければよいです。例えば大きな行列を2つ作り、その行列積にかかる時間を計測してみましょう。

A = rand(1000, 1000)
B = rand(1000, 1000)
@time C = A * B
0.093328 seconds (2 allocations: 7.629 MiB)
(以下、行列の中身が表示される)

ちなみに、juliaは「最初の1回の実行は遅い」という特性があります。上記を2回実行すると、2回目のほうがずっと早いかもしれません。

また、ベンチマークツールを用いることもできます。まず、BenchmarkToolsというパッケージをインストールしましょう。 以下を実行してください。これは一度だけでOKです。

import Pkg
Pkg.add("BenchmarkTools")

これは、パッケージを追加するためのパッケージであるPkgを読み込み、それを経由してBenchmarkToolsをインストールしています。 このパッケージは初期状態では準備されていないので、上記を用いてインストールする必要があります。

ひとたびインストールすると、これを使える様になります。以下のようにしてBenchmarkToolsパッケージを インポートしましょう。

using BenchmarkTools

これも、プログラムの中で1度行うだけでいいです。

次に、実際に計測してみましょう。

@benchmark C = A * B

ここでは自動的に処理を何度も実行し、以下のようにビジュアルに結果を出力してくれます。

BenchmarkTools.Trial: 45 samples with 1 evaluation.
 Range (min … max):   73.526 ms … 188.175 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     114.416 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   112.045 ms ±  22.360 ms  ┊ GC (mean ± σ):  0.12% ± 0.32%

                          ▂█ ▂▂
  ▅▅▅██▁▁▅▅█▁█▁▁▁▁▁▅█▅█████▁▅▅▁██▁▁█▅▁▅▁▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅ ▁
  73.5 ms          Histogram: frequency by time          188 ms <

 Memory estimate: 7.63 MiB, allocs estimate: 2.

関数に「!」マーク

関数名の最後に!をつけると、その関数は「破壊的」であることを示します。これは、関数が引数を変更することを意味します。 例えば次を考えましょう。

arr = [10; 20; 30]
push!(arr, 40)
4-element Vector{Int64}:
 10
 20
 30
 40

ここでは、push!という関数は、ベクトルの一番最後に要素を追加してくれます。この関数は、引数であるベクトルarrの 中身を変更します。なので、関数名に!がついています。同様に、pop!という関数は、ベクトルの一番最後の要素を取り除いてくれます。

pop!(arr)
arr   # 表示
3-element Vector{Int64}:
 10
 20
 30

この!をつけるという操作はjuliaにおける慣習であり、単なる命名規則になっています。皆さんも、自分で関数を作る際に、それが引数を変更する場合は!をつけるようにしましょう。

BigInt / BigFloat

さて、Juliaを始め通常プログラミング言語では、整数や少数を表すために使うビット数には上限があります。例えば以下を実行してみましょう。

a = 2^63-1
println("a's type: ", typeof(a))
println("a     = ", a)
println("a + 1 = ", a + 1)

ここでは結果は次のようになります

a's type: Int64
a     = 9223372036854775807
a + 1 = -9223372036854775808

これはすなわち、特に指定しなければ整数にはInt64の型が割り振られる、すなわち64bitの情報で整数を表現するということになります。 64bitで正負の数字を表現する関係上、2^63の数字までは正しく処理できるのですが、2^63 + 1になると正しく表現できなくなります(そのビット列は負の数に割り当てられているため、負の数になります)。 ちなみにここでなぜこのような変な巨大な数になるか知りたい方は私の別の講義の2の補数というところをご参照ください。

Juliaには、大きな数であっても扱える特別な型であるBigIntが準備されています。これを用いると、次のように、64bitの限界を超える整数演算も実行することが出来ます。

b = BigInt(2^63 - 1)
println("b's type: ", typeof(b))
println("b     = ", b)
println("b + 1 = ", b + 1)

この結果は次のようになり、正しく表現できています。

b's type: BigInt
b     = 9223372036854775807
b + 1 = 9223372036854775808

計算の途中で変数の値の限界を超えてしまうという場合は、これらを試してみてもいいでしょう。この少数版であるBigFloatも準備されています。

ちなみに、最初からすべての変数をBigIntBigFloatにしておけばいいのでは?と思うかもしれません。しかし、変数はとてもプリミティブなものであり、固定長のビットで表現されているからこそ、演算を高速に行えることが出来ます。全てをBigIntなどにしてしまうと、演算がとても遅くなってしまい、またメモリ消費量も増加してしまいます。

やってみよう:

  • 上記を写経しましょう

  • 複雑な内包表記を書いてみましょう。

  • これまでに作ったプログラムについて、時間を計測してみましょう。

  • これまでに作ってプログラムについて、内容を変更するものがあったなら、!をつけてみましょう。

  • 少数演算において正しく表現ができない例を調べ、それに対しBigFloatを適用してみましょう。

チーム作業

それではチームに分かれてもらいます。

線形代数に関するトピックを調べ、それらをJuliaで実装してみてください。

線形代数の講義の内容で納得のいっていない点とか、いまいち腑に落ちない点とかを議論してください。

それらをJuliaで実装してみてください。

例えば、

  • 線形独立という概念がよくわからない。実際にJuliaでランダムベクトルを作って検証してみる。

  • 逆行列が計算できる例とできない例を作ってみる。

などです。今週と来週に色々実装してもらい、その次の週に発表を行ってもらいます。

トピックについて、例えば下記の書籍の目次などが参考になるかもしれません。

今週と来週に色々実装してもらい、その次の週に発表を行ってもらいます。

CC BY-SA 4.0 Yusuke Matsui. Last modified: June 04, 2026. Website built with Franklin.jl and the Julia programming language.