細かすぎて伝わらないかもしれないJuliaのTips

2022/12/18 JuliaTokai #13
antimon2(後藤 俊介)

Note:
Julia の基本Tips?


お品書き

  • お前誰よ?
  • 簡単なJuliaの紹介
  • Tips集 (?)

お前誰よ?


自己紹介

  • 名前:後藤 俊介
  • 所属:有限会社 来栖川電算
  • コミュニティ:
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    JuliaTokai
    ,
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    NGK2023S,
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    機械学習名古屋,
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    jl.dev, Python東海, …
  • 言語:Julia, Python, Scala(勉強中), …
  • Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    @antimon2 /
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    antimon2
  • Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    antimon2 /
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    @antimon2 / @antimon2

Note:
今日は(も?)大っぴらに Julia の話ができるっ


有限会社来栖川電算
https://www.kurusugawa.jp

Note:
普段は Python で仕事してますっ


NGK2023S
https://ngk2022s.connpass.com/event/265837/

Note:
MGK2023S のスタッフやってますっ


簡単な
Julia の紹介

Note:
Julia の紹介っ


Julia

Note:
こんなロゴですっ


Julia とは?(1)

  • The Julia Language
  • 最新 v1.8.3(2022/11/14)
    • LTS:v1.6.7(2022/07/19)
    • 次期版:v1.9.0-α1(2022/11/15)
  • 科学技術計算に強い!
  • 動作が速い!(LLVM JIT コンパイル)

Note:
ググるときはなるべく julialang で!


Julia とは?(2)

  • Rのように中身がぐちゃぐちゃでなく、
  • Rubyのように遅くなく、
  • Lispのように原始的またはエレファントでなく、
  • Prologのように変態的なところはなく、
  • Javaのように硬すぎることはなく、
  • Haskellのように抽象的すぎない

ほどよい言語である

引用元:http://www.slideshare.net/Nikoriks/julia-28059489/8


Julia とは?(3)

  • C のように高速だけど、
    Ruby のようなダイナミズムを併せ持っている
  • Lisp のようにプログラムと同等に扱えるマクロがありながら、
    MATLAB のような直感的な数式表現もできる
  • Python のように総合的なプログラミングができて、
    R のように統計処理も得意で、
    Perl のように文字列処理もできて、
    MATLAB のように線形代数もできて、
    shell のように複数のプログラムを組み合わせることもできる
  • 超初心者にも習得は容易でありながら、
    超上級者の満足にも応えられる
  • インタラクティブにも動作して、コンパイルもできる

Why We Created Julia から抜粋・私訳)

Note:
いろんな言語の「いいとこどり」言語!ってことでっ


要するに

  • 動的言語なのに速い!
  • 文法も覚えやすい!
  • 数値計算に強い!

Note:
機械学習とかにも持って来いっ!


主な機能

Note:


文法・関数

Note:
以降、ほぼ過去スライドからのコピペ。すっ飛ばして先へ進んで戴いてもOKっ


基本的な演算

julia> 1 + 2 - 3 * 4  # 四則演算(除算以外)
-9

julia> 7 / 5  # `整数 / 整数` の結果は浮動小数
1.4

julia> 7 ÷ 5  # `整数 ÷ 整数` の結果は整数
1

julia> 2 ^ 10 # 冪乗は `^`
1024

julia> 123 & 234 | 345  # 論理積 / 論理和
376

julia> 123 ⊻ 234  # 排他的論理和(==`xor(123, 234)`)
145

Note:
整数同士の除算は実数になりますっ
整数除算演算子 ÷ が別に存在します(Python の // 相当)っ
また冪乗も(** ではなく)^ ですっ
\xor+Tab または \veebar+Tab で変換できますっ
ちなみに先ほどの ÷\div+Tabで(基本的に \({\rm \TeX}\) の書式)っ


配列

julia> a = [1, 2, 3, 4, 5]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> a[1]   # Julia は 1-origin
1

julia> println(a[2:3])  # 範囲指定は両端含む
[2, 3]

Note:
1-origin であることに注意すればあとは普通の配列っ
あと a:b は範囲(Range)の記法。両端を含む(Ruby の a..b と同じ)っ


配列の内包表記 (1)

julia> a = [n^2 for n=1:5]
5-element Vector{Int64}:
  1
  4
  9
 16
 25

julia> A = [x+10y for y=1:3, x=1:3]
3×3 Matrix{Int64}:
 11  12  13
 21  22  23
 31  32  33

Note:
内包表記の記法は Python に類似っ
かつ、for にカンマ区切りで複数のイテレータを渡すことで2次元以上の配列も作成可能っ


配列の内包表記 (2)

julia> [(a,b,c) for c=1:15,b=1:15,a=1:15 if a^2+a*b+b^2==c^2]
6-element Vector{Tuple{Int64, Int64, Int64}}:
 (3, 5, 7)
 (5, 3, 7)
 (6, 10, 14)
 (7, 8, 13)
 (8, 7, 13)
 (10, 6, 14)

Note:
Python と同様に if で条件を指定することも可能っ
あと Python と同様、[○ for ○=○](○ for ○=○) と書くと配列ではなくて Generator が返りますっ


多次元配列(v1.7以降)

julia> [1;2;;3;4;;;5;6;;7;8]  # 2×2×2 の3次元配列
2×2×2 Array{Int64, 3}:
[:, :, 1] =
 1  3
 2  4

[:, :, 2] =
 5  7
 6  8

julia> [1 2;3 4;;;5 6;7 8]  # 従来記法との併用も可能(次元の順番に注意)
2×2×2 Array{Int64, 3}:
[:, :, 1] =
 1  2
 3  4

[:, :, 2] =
 5  6
 7  8

julia> [1;;;;]  # 1×1×1×1 の4次元配列
1×1×1×1 Array{Int64, 4}:
[:, :, 1, 1] =
 1

Note:
; の数で次元方向の区切ができるっ
これだけでも v1.7 を使うべきと言える仕様拡張っ!


ベクトル

julia> x = [1., 2., 3.]; y = [3., 1., 2.];

julia> x + y  # `x .+ y` と書いても同じ(elementwise operation)
[4., 3., 5.]

julia> x .* y # これは `x * y` と書くとNG
[3., 2., 6.]

julia> using LinearAlgebra

julia> x ⋅ y  # 内積(dot積、`dot(x, y)` と書いても同じ)
11.0

julia> x × y  # 外積(cross積、`cross(x, y)` と書いても同じ)
[1., 7., -5.]

Note:
Julia では実は1次元配列がベクトルの扱いっ
\cdot+Tab×\times+Tab(これらを利用するには using LinearAlgebra 必要)っ
あとこれらや先ほどの ÷ などのように、ASCIIの範囲を超えたUnicode文字の演算子(そのほとんどが \(\TeX\) 由来)が Julia にはたくさんあります(他には例えば比較演算子の や、集合の要素 や包含関係 などなど)


行列

julia> A = [1 2; 3 4]   # この記法は MATLAB/Octave 由来
2×2 Matrix{Int64}:
 1  2
 3  4

julia> A'  # `○'` は転置行列(正確には随伴行列)の記法(これも MATLAB/Octave 由来)
2×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  3
 2  4
 
 julia> A'ᵀ  # ← ≥ v"1.6", v"1.5" までは `transpose(A)`  # 正確には転置行列はこっち
2×2 transpose(::Matrix{Int64}) with eltype Int64:
 1  3
 2  4

Note:
Julia では2次元配列が行列の扱いっ
v1.6 から転置を表す表記として ○'ᵀ と書けるようになりましたっ
'\^T + TAB で入力できますっ


行列の演算

julia> A = [1 2; 3 4]; B = [3 0; 0 6];

julia> A + B  # A .+ B でも同様
2×2 Matrix{Int64}:
 4   2
 3  10

julia> A * B   # matrix multiply
2×2 Matrix{Int64}:
 3  12
 9  24

julia> A .* B   # elementwise multiply
2×2 Matrix{Int64}:
 3   0
 0  24

Note:
行列は * で通常の行列積になりますっこれ便利っ


ブロードキャスト

julia> sin(0.1)
0.09983341664682815

julia> sin.([0.1, 0.2, 0.3, 0.4])
4-element Vector{Float64}:
 0.0998334
 0.198669
 0.29552
 0.389418

julia> [0.1, 0.2, 0.3, 0.4] .^ 2
# => [0.01, 0.04, 0.09, 0.16]

Note:
関数名と ( の間に . を置くと、普通の関数を配列に拡張してくれる(ブロードキャスト)っ
.^ のように演算子の前に . を書いても同様(先ほど出た .+ .* もブロードキャスト)っ


関数定義

julia> f(x) = x^2 + 2x - 1
f (generic function with 1 method)

julia> f(1)
2

julia> f.(1:5)
# => [2, 7, 14, 23, 34]

Note:
数学のように直感的な記述で関数を定義可能っ
2x2*x の省略形、曖昧さがなければリテラルと他の識別子が続く場合などに勝手に乗算と解釈してくれるっ
またユーザ定義関数も . をつけて自動的にブロードキャスト対応っ


有理数・複素数

julia> 1//2 == 0.5
true

julia> 1//2 - 1//3
1//6

julia> 1im ^ 2 == -1
true

julia> (1.0 + 0.5im) * (2.0 - 3.0im)
3.5 - 2.0im

Note:
有理数・複素数を標準サポート。
// は有理数除算(結果は有理数)
im は虚数単位。
どちらも四則演算も普通に書けますっ


Tips集 (?)

Note:
本題!


シチュエーション1: findXXX() 系関数?

  • 例えば「配列(1次元配列)で同じ要素が3つ続く箇所を見つけてそのインデックスの範囲を返す」はどう実現すれば良い?
  • Julia 標準の findXXX() 系の関数って、こういうとき使えるの?

Note:


解決法1:for 文で回せばOK

Note:


# 同じ要素がN個続く箇所を見つけてそのインデックスの範囲を返す
function findNrepeats(a::AbstractVector, N)
    _lastindex = lastindex(a)
    for i in eachindex(a)  # keys(a) でもOK
        r = i:i+N-1
        last(r) > _lastindex && break
        allequal(a[r]) && return r
    end
    nothing
end

Note:


julia> a = [0, 7, 7, 5, 0, 0, 0, 1, 1, 2];  # a[5:7] == [0, 0, 0]

julia> findNrepeats(a, 3)
5:7

julia> findNrepeats([3, 1, 4, 1, 5, 9, 2, 6, 5, 3], 2) === nothing
true

Note:


解決法2:無理矢理 findXXX() を利用する形に落とし込む

Note:


# 同じ要素がN個続く箇所を見つけてそのインデックスの範囲を返す ver.2
function findNrepeats_v2(a::AbstractVector, N)
    rs = [i:i+N-1 for i in eachindex(a) if i+N-1 ≤ lastindex(a)]
    index = findfirst(rs) do r
        allequal(a[r])
    end
    isnothing(index) ? nothing : rs[index]
end

Note:


julia> a = [0, 7, 7, 5, 0, 0, 0, 1, 1, 2];  # a[5:7] == [0, 0, 0]

julia> findNrepeats_v2(a, 3)
5:7

julia> findNrepeats_v2([3, 1, 4, 1, 5, 9, 2, 6, 5, 3], 2) === nothing
true

Note:


ベンチマーク

Note:


julia> using BenchmarkTools, Random

julia> N=3;

julia> Random.seed!(1234);
       @benchmark findNrepeats(a, $N) setup=(a=rand(0:9, 100))
# 出力例は画像参照

julia> Random.seed!(1234);
       @benchmark findNrepeats_v2(a, $N) setup=(a=rand(0:9, 100))
# 出力例は画像参照

Note:


ベンチマーク結果(findNrepeats)

Note:


Point1

  • Julia の findXXX() は「条件に合致するもののインデックス(の範囲)を返す関数(存在しなければ nothing)」という共通仕様
    • 例外:findmax() / findmin()
  • for i in eachindex(a) または for i in keys(a)
    • for i in 1:length(a) は危険!

Note:
※ Julia の findXXX() 系の関数は「条件に合致するもののインデックス(の範囲)を返す関数(存在しなければ nothing)」という共通仕様がある(例外:findmax()/findmin())ので、findNrepeats() もそれに合わせた仕様にしてあります。
※ Julia のインデックスは(標準の1次元配列なら)1-originだけど、全てのコレクションのインデックスがそうとは限らない(設定次第)ので for i in 1:length(a) のように書くのは危険!


Point2

  • findXXX() 系の関数は正直使いづらいし、必ずしも高パフォーマンスとは限らない。
  • てか for ループ速い!
  • あと allequal() て関数知ってた?

Note:
※ あと「コレクションの要素が全て同じかどうか」を判定するズバリ allequal() て関数が標準であります。便利♪知ってました?
なお「コレクションの要素が全て異なるかどうか」を判定する allunique() てのもあります。


参考:文字列なら 正規表現+findfirst() でOK!

Note:


# 前準備:「同じ文字のN回繰り返し」という正規表現を生成しキャッシュする仕組み
@generated function getNrepeatsRegex(::Val{N}) where {N}
    Regex("(.)" * "\\1" ^ (N-1))  # N=3 なら `r"(.)\1\1"` となる
end

# 同じ文字がN個続く箇所を見つけてそのインデックスの範囲を返す
function findNrepeats(s::AbstractString, N)
    rex = getNrepeatsRegex(Val(N))
    findfirst(rex, s)
end

Note:


julia> s = "ABC123あああ😁漢字";  # s[7:13] == "あああ"

julia> findNrepeats(s, 3)
7:13

Note:


参考の参考:文字列なら正規表現+findXXX() でOK(キャッシュしないバージョン)

Note:


# 同じ要素がN個続く箇所を見つけてそのインデックスの範囲を返す ver.2
function findNrepeats_v2(s::AbstractString, N)
    rex = Regex("(.)" * "\\1" ^ (N-1))  # N=3 なら `r"(.)\1\1"`
    findfirst(rex, s)
end

Note:


julia> s = "ABC123あああ😁漢字";  # s[7:13] == "あああ"

julia> findNrepeats_v2(s, 3)
7:13

Note:


ベンチマーク

Note:


julia> using BenchmarkTools, Random

julia> N=3;

julia> Random.seed!(1234);
       @benchmark findNrepeats(s, $N) setup=(
           s=randstring("123ABCあいう😁漢字", 100))
# 出力例は画像参照

julia> Random.seed!(1234);
       @benchmark findNrepeats_v2(s, $N) setup=(
           s=randstring("123ABCあいう😁漢字", 100))
# 出力例は画像参照

Note:


ベンチマーク結果2(findNrepeats w String)

Note:


Point3

  • findXXX() は文字列検索の場合パターン(正規表現、文字の範囲等)も利用OK!
  • ただし毎回正規表現を生成するのは意外とコスト高い(=遅い)
  • @generated(生成関数)こういうとき便利♪

Note:
findXXX は文字列検索の場合第1引数は関数だけではなくパターン(正規表現、文字の範囲等)もOK!
※ ただし毎回正規表現を生成するのは意外とコスト高い(=遅い)ので何らかの方法でキャッシュする必要あり(正規表現リテラルが使用できるなら使用するなど)
※ てかこのサンプルを作ることで @generated(生成関数)の使い途を1つ見つけてしまった♪これ基本!


補足:findXXX()(第1引数に関数を受け取る使い方)

  • ↑はなんで「値で検索」「結果はインデックス」なのか?
  • ⇒「インデックス」または「キー」で参照できるコレクションなら同じI/Fで使えるから!

Note:
→ 配列や文字列だけじゃなくて、辞書(Dict)や名前付きタプル(NamedTuple)にも同じI/Fで利用できるから(それらの場合キー(の列)が返る)。


julia> D = Dict(["Carol", "Alice", "Ellen", "Bob", "Dave"].=>1:5);

julia> findall(isodd, D)  # 値が奇数であるようなエントリーのキーを全て列挙
3-element Vector{String}:
 "Carol"
 "Dave"
 "Ellen"

julia> [D[key] for key in findall(isodd, D)]
3-element Vector{Int64}:
 1
 5
 3

julia> # ※ ↑は結果的に `filter(isodd, collect(values(D)))` と同じ

Note:


シチュエーション2: Y/A findXXX() 系関数

  • findXXX() 系関数の理念や使い途は分かった。分かったけれどやっぱ使いにくい!
    1. インデックスやキーで参照できるコレクションしか扱えない
    2. 戻り値がインデックスやキーなのが分かりにくい
  • Julia に「一般のイテレータで使える」「条件に合致する最初の要素を取得(なければ nothing)」っていう関数はないの?

Note:


解決法1:Iterators.dropwhile()Base.first() を組み合わせればOK

Note:


# 第1引数で条件判定して、第2引数のコレクションで最初に合致する要素を返す
# (なければ `nothing`)
function meetfirst_v1(pred::Function, itr)
    # NG: `return first(Iterators.dropwhile(!pred, itr))`
    itr2 = Iterators.dropwhile(!pred, itr)
    try
        first(itr2)
    catch e
        if isa(e, BoundsError) || isa(e, ArgumentError)
            return nothing
        end
        rethrow(e)
    end
end

Note:


julia> a = [314, 159, 265, 358, 979, 323, 846, 264];

julia> meetfirst_v1(n -> n % 11 == 0, a)
979

julia> meetfirst_v1(n -> n % 7 == 0, a) === nothing
true

Note:


julia> # コラッツ数列を列挙するイテレータ(`Channel`)を返す関数
       function collatz(n::Int)
           Channel{Int}() do chnl
               put!(chnl, n)
               while n > 1
                   n = iseven(n) ? n ÷ 2 : 3n + 1
                   put!(chnl, n)
               end
           end
       end
collatz (generic function with 1 method)

julia> meetfirst_v1(≥(200), collatz(27))
214

julia> meetfirst_v1(≥(50), collatz(3)) === nothing
true

Note:


解決法2:パフォーマンス気にしてみる

Note:


# 第1引数で条件判定して、第2引数のコレクションで最初に合致する要素を返す
# (なければ `nothing`) ver.2
function meetfirst(pred::Function, itr)
    itr2 = Iterators.dropwhile(!pred, itr)
    next = iterate(itr2)
    isnothing(next) ? nothing : first(next)  # `first(next)` はタプルの第1要素を取得している
end

Note:


julia> a = [314, 159, 265, 358, 979, 323, 846, 264];

julia> meetfirst(n -> n % 11 == 0, a)
979

julia> meetfirst(n -> n % 7 == 0, a) === nothing
true

Note:


julia> # コラッツ数列を列挙するイテレータ(`Channel`)を返す関数
       function collatz end;  # 長いので略

julia> meetfirst(≥(200), collatz(27))
214

julia> meetfirst(≥(50), collatz(3)) === nothing
true

Note:


ベンチマーク

Note:


julia> using BenchmarkTools, Random

julia> Random.seed!(1234);
       @benchmark meetfirst_v1(≥(200), c) setup=(c=collatz(rand(3:100)))
# 出力例は画像参照

julia> Random.seed!(1234);
       @benchmark meetfirst(≥(200), c) setup=(c=collatz(rand(3:100)))
# 出力例は画像参照

Note:


ベンチマーク結果(meetfirst)

Note:


Point1

  • 基本的な発想:first(Iterators.dropwhile(!pred, itr))
    • !《関数》x -> !(《関数》(x)) 相当
    • first(《空のイテレータ》) は例外が発生してしまうので適切に対処

Note:
※ 基本的な発想は first(Iterators.dropwhile(!pred, itr))
!pred というのは、pred() 関数の結果(Bool値 という前提)を否定、つまり x->!(pred(x)) 相当
first(《空のイテレータ》) は例外が発生してしまうので適切な対処が必要


Point2

  • iterate(itr)
    • 最初の要素があるとき: (《要素》, 《状態オブジェクト》)
    • ないとき: nothing
  • Julia の例外処理意外と重いっ!

Note:
※ Julia のイテレーションの仕組み(iterate() 関数の使い方・戻り値)を知っていれば、first() 関数相当のことを簡単に実現できてしかも例外処理不要!(Julia の例外処理は意外と重い!)
※ (簡単におさらい) iterate(itr) は、最初の要素があるときは (《要素》, 《状態オブジェクト》)、なければ nothing


シチュエーション3: 文字列の折り返し

  • 長い文字列はずらーっと横に長くなる…
  • 途中に改行すると改行文字が入っちゃうし…

Note:

  • Julia はフリーインデントだから、長い配列を直書きするときは適宜改行して書けるから良いよね。
  • でも長い文字列はずらーっと横に長くなっちゃうよね…
  • だって Julia の文字列は中に改行を入れられる=途中で改行すると改行文字になっちゃうじゃん…

解決:行末に \ を入れることで折り返しできる(ようになった)よ!(≥v1.7)

Note:


julia> jgm0 = "寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末食う寝る処に住む処やぶらこうじのぶらこうじパイポ・パイポ・パイポのシューリンガン・シューリンガンのグーリンダイ・グーリンダイのポンポコピーのポンポコナの長久命の長助";

julia> jgm1 = "寿限無寿限無五劫の擦り切れ\
海砂利水魚の水行末雲来末風来末\
食う寝る処に住む処やぶらこうじのぶらこうじ\
パイポ・パイポ・パイポのシューリンガン・\
シューリンガンのグーリンダイ・グーリンダイのポンポコピーのポンポコナの\
長久命の長助";

Note:


julia> jgm2 = """
    寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末\
    食う寝る処に住む処やぶらこうじのぶらこうじ\
    パイポ・パイポ・パイポのシューリンガン・シューリンガンのグーリンダイ・\
    グーリンダイのポンポコピーのポンポコナの\
    長久命の長助""";
# ↑ `"""~"""` ならインデントも無視されるのでさらに見やすく!

Note:


julia> jgm0 == jgm1 == jgm2  # もちろん文字列として同一視できる
true

julia> jgm0 === jgm1 === jgm2  # リテラルとしても同一になる(今回の場合)!
true

Note:
※リテラルとして同一になる(今回の場合)ので === で比較しても true になる!


Point

  • 文字列の折り返し便利になった!

Note:
Julia v1.6.x 以前を捨てて良いコードならば。


参考:v1.6.x も対象のコードなら…

julia> jgm0 = "寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末食う寝る処に住む処やぶらこうじのぶらこうじパイポ・パイポ・パイポのシューリンガン・シューリンガンのグーリンダイ・グーリンダイのポンポコピーのポンポコナの長久命の長助";

julia> jgm_oldstyle =
           "寿限無寿限無五劫の擦り切れ" * 
           "海砂利水魚の水行末雲来末風来末" *
           "食う寝る処に住む処やぶらこうじのぶらこうじ" *
           "パイポ・パイポ・パイポのシューリンガン・" *
           "シューリンガンのグーリンダイ・グーリンダイのポンポコピーのポンポコナの" *
           "長久命の長助";

julia jgm0 == jgm_oldstyle
true

Note:
※ なお最適化の関係でこれくらいの長さの文字列ならたぶん jgm0 === jgm_oldstyle になります。。。


シチュエーション4: OrderedDict とか SortedSet とか

  • Julia の DictSet って順不同ですよね…
  • OrderdDict とか SortedSet 欲しい…

Note:

  • Julia の DictSet って追加順やキーの序列と列挙順が順不同ですよね…
  • パフォーマンスが理由でそうなっているのだろうとは思うのだけれど、やっぱ所謂 OrderdDict とか SortedSet 欲しい…ん…

解決1:外部パッケージ DataStructures にあるからそれ使うのが素直な解決法

Note:


julia> # 標準の Dict: 順不同
       dic = Dict(["Carol", "Alice", "Ellen", "Bob", "Dave"].=>1:5)
Dict{String, Int64} with 5 entries:
  "Carol" => 1
  "Alice" => 2
  "Dave"  => 5
  "Ellen" => 3
  "Bob"   => 4

Note:


(@v1.x) pkg> add DataStructures
# 出力例略

julia> using DataStructures

julia> # OrderedDict: 追加順
       odic = OrderedDict(["Carol", "Alice", "Ellen", "Bob", "Dave"].=>1:5)
OrderedDict{String, Int64} with 5 entries:
  "Carol" => 1
  "Alice" => 2
  "Ellen" => 3
  "Bob"   => 4
  "Dave"  => 5

Note:


julia> # 標準の Set: 順不同
       set = Set(["Carol", "Alice", "Ellen", "Bob", "Dave"])
Set{String} with 5 elements:
  "Carol"
  "Alice"
  "Dave"
  "Ellen"
  "Bob"

Note:


(@v1.x) pkg> add DataStructures
# 出力例略

julia> using DataStructures

julia> # SortedSet: 値の昇順
       sset = SortedSet(["Carol", "Alice", "Ellen", "Bob", "Dave"])
SortedSet{String, Base.Order.ForwardOrdering} with 5 elements:
  "Alice"
  "Bob"
  "Carol"
  "Dave"
  "Ellen"

Note:


解決2:Vector でキーの列を別管理する

Note:
列挙時にだけ追加順/ソート順が欲しい場合


julia> dkeys = ["Carol", "Alice", "Ellen", "Bob", "Dave"];

julia> dic = Dict(dkeys.=>1:5)
Dict{String, Int64} with 5 entries:
  "Carol" => 1
  "Alice" => 2
  "Dave"  => 5
  "Ellen" => 3
  "Bob"   => 4

Note:


julia> # 追加順に列挙
       for key in dkeys
           println("$(key) => $(dic[key])")
       end
Carol => 1
Alice => 2
Ellen => 3
Bob => 4
Dave => 5

Note:


julia> # キーの昇順に列挙
       for key in sort(dkeys)
           println("$(key) => $(dic[key])")
       end
Alice => 2
Bob => 4
Carol => 1
Dave => 5
Ellen => 3

Note:


Point1

  • DataStructures.jl 便利♪
    • OrderedDict/SortedSet の他に SortedDict/OrderedSet ももちろんある
    • 他にも有名どころのデータ構造多数用意

Note:


Point2

  • キーを Vector で別管理するのもお手軽で十分
    • 列挙時にだけ追加順/ソート順が欲しい場合ならば
    • 長さが膨大でなければ

Note:
※ その分メモリを消費するが、列挙時にだけ追加順/ソート順が欲しいならそれで十分
※ ↑の「解決2」でもそんなに困らない(長さが膨大でなければ)


シチュエーション5: 無名関数の多重定義

  • fn = x -> x*xfn(x, y) はメソッドエラー(まぁ当然)
  • fn(x) = x*xfn(x, y) = ~ と多重定義できる
  • 前者の方法で定義した関数(=無名関数)は多重定義できないの?

Note:

  • fn = x -> x*x みたいにして関数を定義すると、fn(x, y) のような呼び出しは失敗しますよね。多重定義されていないから。
  • fn(x) = x*x という定義にすれば良い、そうすれば後で fn(x, y) = ~ と多重定義できる、それは分かります。
  • では、前者の方法で定義した関数(=無名関数)は多重定義できないの?

解決:できます!

Note:


julia> fn = x -> x * x
#45 (generic function with 1 method)

julia> # 関数 `fn`(正確には変数 `fn` に格納されている無名関数)の多重定義
       (::typeof(fn))(x, y) = x * y

julia> methods(fn)
# 2 methods for anonymous function "#45":
[1] (::var"#45#46")(x) in Main at REPL[121]:1
[2] (::var"#45#46")(x, y) in Main at REPL[122]:2

julia> @show fn(5) fn(5, 8)
fn(5) = 25
fn(5, 8) = 40
40

Note:


Point

  • ほぼ使い途はありません!(重要)

Note:
※ 重要:できるけれどほぼ使い途はありません!


シチュエーション6: 文字列のインデックス

  • インデックスが 1-origin なのはもう慣れるしかないのでOK
  • 文字列のインデックスが連続じゃないのがどうしても慣れない…
  • 例えば「s = "123ABCあいう😁漢字" に対して「sの10文字目」と指定して「'😁'」を取得したい…。

Note:

  • Juliaの(配列などの)インデックスが 1-origin なのはもう慣れるしかないのでOKです。
  • 文字列(非ASCII文字を含む)のインデックスが連続じゃないのがどうしても慣れないです…。
  • 例えば「s = "123ABCあいう😁漢字" に対して「sの10文字目」と指定して「'😁'」を取得したい…。

解決1:nextind() という関数が用意されています!

Note:


help?> nextind
search: nextind IndexCartesian MissingException current_exceptions InterruptException InvalidStateException

  nextind(str::AbstractString, i::Integer, n::Integer=1) -> Int

# :《中略》

•  Case n > 1
       Behaves like applying n times nextind for n==1. The only difference is that if n is so large that applying
       nextind would reach ncodeunits(str)+1 then each remaining iteration increases the returned value by 1.
       This means that in this case nextind can return a value greater than ncodeunits(str)+1.

# :《以下略》

Note:


julia> s = "123ABCあいう😁漢字";

julia> nextind(s, 0, 10)  # `0` より後で `10` 番目の文字のインデックス
16

julia> s[nextind(s, 0, 10)]  # `s` の10文字目
'😁': Unicode U+1F601 (category So: Symbol, other)

Note:


参考1:他の xxxxind() 系の関数

julia> # `thisind()`: 指定したあたりに存在する文字の正しいインデックス番号を返す

julia> s = "123ABCあいう😁漢字";

julia> thisind(s, 9)
7

julia> s[thisind(s, 9)]
'あ': Unicode U+3042 (category Lo: Letter, other)

julia> # `prevind()`:「後ろから何文字目」という指定が可能

julia> s[prevind(s, end+1, 3)]  # 後ろから3文字目の文字
'😁': Unicode U+1F601 (category So: Symbol, other)

Note:


参考2:eachindex() 関数

julia> s = "123ABCあいう😁漢字";

julia> # 文字列の全てのインデックスを列挙するなら↓

julia> eachindex(s)
Base.EachStringIndex{String}("123ABCあいう😁漢字")

julia> collect(eachindex(s))
12-element Vector{Int64}:
  1
  2
  3
  4
  5
  6
  7
 10
 13
 16
 20
 23

Note:


参考3:split() の場合

julia> s = "123ABCあいう😁漢字";

julia> split(s, "")[10]
"😁"
  • 結果は文字ではなく文字列
  • 一時的に配列が生成される

Note:
※似たようなことはできるが以下が異なる:

  • 結果は文字(Char)ではなく文字列(SubString{String})になる
  • 一時的に文字列(部分文字列)の配列が生成されるのでパフォーマンスは決して良くない(使い方次第)

まとめ


  • Julia 楽しい!

Note:


リンク


実験Note

  • Binder

参考リンク


ご清聴ありがとうございます。

Note:
ご清聴ありがとうございますっ!

Select a repo