初めに
「FizzBuzzクイズ」クイズ-Ruby編 を見て、これ Julia でやるにはどうすれば良いだろう? と考えてやってみてできたので晒します。
できたもの
Julia v0.6.0 で開発、v0.6.0/v0.5.2 で動作確認済。
Aho にも対応済。
"""文字列連結(多重再定義)"""
function Base.:*(n::Int, s2::AbstractString)::AbstractString
str = String(UInt8[reinterpret(UInt8, [n]);Vector{UInt8}(s2)])
SubString(str, sizeof(Int)+1, endof(str))
end
function Base.:*(s1::SubString{String}, s2::AbstractString)::AbstractString
str = String(UInt8[Vector{UInt8}(s1.string);Vector{UInt8}(s2)])
SubString(str, s1.offset+1, endof(str))
end
"""文字列に埋め込んだ整数値を抽出(多重定義)"""
function choose_n(str::SubString{String})::Int
reinterpret(Int, Vector{UInt8}(str.string)[1:str.offset])[1]
end
choose_n(n::Int)::Int = n
"""fizz()"""
function fizz(val::Union{Int,SubString{String}})::Union{Int,AbstractString}
choose_n(val) % 3 == 0 ? val * "Fizz" : val
end
"""buzz()"""
function buzz(val::Union{Int,SubString{String}})::Union{Int,AbstractString}
choose_n(val) % 5 == 0 ? val * "Buzz" : val
end
# @show fizz(1) # => 1
@show 1 |> fizz # => 1
@show 3 |> fizz # => "Fizz"
@show 1 |> buzz # => 1
@show 5 |> buzz # => "Buzz"
# @show buzz(fizz(1)) # => 1
@show 1 |> fizz |> buzz # => 1
@show 3 |> fizz |> buzz # => "Fizz"
@show 5 |> fizz |> buzz # => "Buzz"
@show 15 |> fizz |> buzz # => "FizzBuzz"
@show 15 |> buzz |> fizz # => "BuzzFizz"
"""pezz()"""
function pezz(val::Union{Int,SubString{String}})::Union{Int,AbstractString}
choose_n(val) % 7 == 0 ? val * "Pezz" : val
end
# @show pezz(buzz(fizz(7))) # => "Pezz"
@show 7 |> fizz |> buzz |> pezz # => "Pezz"
@show 21 |> fizz |> buzz |> pezz # => "FizzPezz"
@show 35 |> fizz |> buzz |> pezz # => "BuzzPezz"
@show 105 |> fizz |> buzz |> pezz # => "FizzBuzzPezz"
@show 105 |> fizz |> pezz |> buzz # => "FizzPezzBuzz"
@show 105 |> pezz |> buzz |> fizz # => "PezzBuzzFizz"
# 以下も念のため
@show 1 |> fizz |> buzz |> pezz # => 1
@show 3 |> fizz |> buzz |> pezz # => "Fizz"
@show 5 |> fizz |> buzz |> pezz # => "Buzz"
@show 15 |> fizz |> buzz |> pezz # => "FizzBuzz"
@show 15 |> buzz |> fizz |> pezz # => "BuzzFizz"
@show 104 |> fizz |> buzz |> pezz # => 104
"""hozz()"""
function hozz(val::Union{Int,SubString{String}})::Union{Int,AbstractString}
3 ∈ digits(choose_n(val)) ? val * "Aho" : val
end
@show 13 |> fizz |> buzz |> hozz # => "Aho"
@show 3 |> fizz |> buzz |> hozz # => "FizzAho"
@show 35 |> fizz |> buzz |> hozz # => "BuzzAho"
@show 30 |> fizz |> buzz |> hozz # => "FizzBuzzAho"
@show 30 |> hozz |> buzz |> fizz # => "AhoBuzzFizz"
解説1:基本方針
Julia では、Ruby のオープンクラス(モンキーパッチ)みたいな、既存の型にフィールドを追加するようなことはできません(はず)。なので「オブジェクト(文字列)にインスタンス変数を持たせて整数値を保持」という手は使えません。
関数定義をモジュール化して、グローバル変数ではなくモジュール内ローカル変数に保持する手は使えますが、自分の中ではギリギリアウトなので却下。
ところでJuliaの文字列はString
型なのですが、supertypeとしてAbstractString
が存在して、状況によってこれを継承した別の型を文字列として扱うことによって効率化を図っています。
その代表的な例がSubString{<:AbstractString}
型で、内部にAbstractString
型の値とオフセット値(とバイト長)を保持して、既存の文字列の一部だけを新しい文字列として扱うための型。新しい文字列オブジェクトを生成(する際にバイト列のコピーを発生)しないようにするために用意された型で、部分文字列を返す関数(例:match()
)でよく利用されています。見た目上・基本動作は普通の文字列です。
ということで、これを利用することにしました。
文字列を表すバイト列の先頭に、整数値を表すバイト列(64bitなら8バイト分)を埋め込み、SubString
で9バイト目から始まる文字列として扱うようにしてもらう。
fizz()
buzz()
の第1引数が SubString
型だったら、その先頭(8バイト)に埋め込まれた整数値を抽出して処理をする。
あとは文字列連結するときなどに、せっかく埋め込んだ整数値の情報が消されないように注意すればOK。
解説2:Julia コードの概説
今回のコードの中で(解説1の内容も踏まえて)特に Julia 固有な部分を少しだけ解説。
- 1〜9行目。Julia での文字列連結演算子は
*
。ただし«整数値» * «文字列»
は定義されていないし、«AbstractString» * «AbstractString»
しか定義されていないので、そのことを逆に利用して«Int» * «AbstractString»
と«SubString{String}» * «AbstractString»
を多重定義することで今回の目的を達成できるよう調整。
Julia はこんな感じで演算子のオーバーロードができます。 - 11〜15行目。
SubString{String}
が保持するバイト列に埋め込んだ整数値を抽出。こちらも関数を多重定義して、整数値が引数として渡ってきたらそのままその値を返しています。
15行目のchoose_n(n::Int)::Int = n
は関数のワンライナー定義の表記です。 - 18, 23, 40, 61 行目。引数やその戻り値の型アノテーションとして
Union{A,B}
と指定することで、「A
またはB
の型を受け取る(返す)」という指定ができます。
動的型付けであり型アノテーション付けなくても動くんですけれど、型推論に全部お任せするのではなくなるべく型情報を付けるようにしています。適切に指定すればパフォーマンス向上するので。 - 62行目、
e ∈ S
は数学の「eはSの要素である」という記号ですが、Julia ではそれがそのまま演算子てとして定義されています。あとdigits()
は整数値を(第2引数が指定されなければ10進数の)各桁の数値を配列(Vector{Int}
型)で返す関数。 - 28行目他多数:
x |> f1 |> f2
という記述は、関数適用チェーン。文法ではなく|>
が値と関数を受け取る演算子(関数)として定義されているだけ。この場合f2(f1(x))
と同じ意味になります。