トラック
/
Julia
Julia
/
演習
/
ログレベル
ログレベル

ログレベル

学習演習

はじめに

JuliaのStringは、文字を順番に並べたものです。マニュアルにも書かれているとおりです。「もちろん、本当に厄介なのは『文字とは何か』と問い始めたときです」。

この複雑さの多くはCharsのコンセプト演習でより詳しく扱いますが、手短に言うと次のとおりです:

  • Juliaの文字列はすべてUnicodeです。
  • ASCIIでもある文字列(英語の大文字・小文字のAからZ、数字、そしていくつかの句読点)は、扱うのが_簡単_です。
  • それ以外の世界のほとんどの文字体系も扱うことは_可能_ですが、より注意が必要です。

文字列リテラル

一般に、文字列は二重引用符" "で囲みます。 一重引用符' 'はCharsにだけ使います。

三重の二重引用符""" """を使うこともできます。これを使うと、文字列の中の"をエスケープせずに使えます。 さらに重要なのは、複数行の文字列から余分なインデントが取り除かれることです。

julia> """
       Multiline
       
       """
"Multiline\nString\n"

文字のエスケープ

C以降のほとんどの言語と同じく、Juliaはエスケープにバックスラッシュ\を使います:

  1. 先ほどの例の改行\nのような、表示されない文字を入力するためです。
  2. \$(後述)のように、そうしなければ特別な意味を持ってしまう文字を守るためです。

インデックス

原則として、文字列はベクトルとまったく同じようにインデックスできます:

julia> s[1], s[5]
('J', 'a')

「Julia」のようなASCII文字列なら問題なく動きますが、それでは世界のほとんどの言語を無視することになります。

約15世紀前に古英語で書かれた『ベオウルフ』の文字を1つ考えてみましょう。

「Hrōðgār」は明らかに7文字ですが、そのすべてがASCIIというわけではありません。

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> isascii(king)
false

# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
 'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'ō': Unicode U+014D (category Ll: Letter, lowercase)
 'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'ā': Unicode U+0101 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)

このように文字列をインデックスしていくと、最初はうまくいきますが、どこかで破綻します:

julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)

julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'

問題は、この種のインデックスが_バイト_を数えていることです。非ASCIIの文字列では、バイトと文字は1対1の関係にはありません。 Unicode/UTF-8の文字を表現するには最大4バイト必要になることがあり、この点はCharsのコンセプト演習でさらに掘り下げます。

文字列の組み立て

連結

多くの言語では文字列の連結演算子として+を使いますが、Juliaは違います。 せめてエラーメッセージは役に立ちます。

julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with * 

julia> "s1 " * "s2"
"s1 s2"

string()関数は、任意の数の文字列を連結します。

julia> string("lots ", "of ", "bits")
"lots of bits"

join()関数も同じことができ、さらに文字列のベクトルを受け取って、指定した区切り文字(デフォルトは"")で連結します。

julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"

文字列はイミュータブルで、連結にはコピーが伴うことに注意してください。 そのため、ループの中で連結を繰り返すのは非効率で、断片をベクトルに集めておいて最後にまとめてjoinするほうがよいです。

繰り返し

簡単です:

julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"

補間

多くの言語には、計算した値を文字列に埋め込む方法がありますが、最適な構文については驚くほど意見が分かれています。

Juliaでは、埋め込む値の前に$を付けます。

julia> myvar = 42
42

julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"

julia> r = 5.3
5.3

julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"

省略すると曖昧になる場合は括弧(())が必要ですが、空白が続く単一の識別子なら省略できます。

文字列に$を含めるには、\$とエスケープします。

現時点では、補間で表示する小数点以下の桁数などの書式設定ができないという制限があることに注意してください。

@sprintf()

文字列の組み立てをより細かく制御するために、JuliaはCからsprintf関数を取り入れています(後発の言語の多くも同様で、Wikipediaには約30言語が挙げられています)。

Juliaでは、これはPrintfモジュール内のmacroとして実装されているため、@が頭に付きます。

julia> using Printf

julia> r = 5.3
5.3

julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"

文字列の関数

話を簡単にするため、このセクションでは新しい文字列を返し、入力を変更しない関数に絞ります。 多くには、!を付けた名前の、入力をその場で変更する同等の関数があります。

長さ

文字列の長さはどれくらいでしょうか? 数え方によって変わることがあります。

単純なのはASCII文字列で、どの文字も1バイトを占めます。

julia> string_asc = "Julia"
"Julia"

julia> length(string_asc)
5

julia> sizeof(string_asc)
5

length()関数は_文字_の数を返し、sizeof()関数は_バイト_の数を返します。

この違いは、他の文字集合では重要になります:

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> sizeof(king)
10

分割

プログラミングでよくある操作は、長い文字列を、1文字以上の区切り文字列をもとに分割することです。

最も一般的にはsplit()関数を使います。 区切り文字はデフォルトで(任意の)空白ですが、別のものも指定できます。

julia> split("my little string")
3-element Vector{SubString{String}}:
 "my"
 "little"
 "string"

julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
 "My"
 "snake"
 "case"
 "string"

これらの例は文字列をより小さな_文字列_に分割しています。 文字列を_文字_に分けるのは別の操作です。

検索と置換

文字列に指定した部分文字列が含まれているかどうかを調べるにはどうすればよいでしょうか? 奇妙なことに、これを調べる関数は2つあります。occursin()とcontains()は引数の順序だけが違います。

julia> needle = "Julia"
"Julia"

julia> haystack = "Python, Julia and R"
"Python, Julia and R"

julia> occursin(needle, haystack)
true

julia> contains(haystack, needle)
true

位置をより詳しく調べるには:

julia> startswith(haystack, "Python")
true

julia> endswith(haystack, "Python")
false

部分文字列の開始:終了のインデックスを得るには、いくつかの関数があります:

  • findfirst()
  • findlast()
  • findprev()
  • findnext()
julia> findfirst(needle, haystack)
9:13

部分文字列を置換するには、x => y構文で変更を並べます。

julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"

Julia 1.7以降、replace関数は任意の数の変更を受け取ることができ、置換の間にどの文字も一度しか変更されないことが保証されています。

x => y構文はpairと呼ばれ、Juliaで重要な型です。これについてはDicts and Pairsのコンセプト演習で詳しく扱います。

空白

文字列の先頭や末尾の空白を取り除きたいことがよくあります。

そのための関数はlstrip()(左)、rstrip()(右)、strip()(両方)です。

julia> strip("\n\t  useful_bit\n\n")
"useful_bit"

大文字・小文字の変換

これらの関数は名前を見ればほぼわかります:

julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "

julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "

julia> lowercase(s)
"abcd abcd abcd abcd abcd "

julia> titlecase(s)  # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "

説明

この演習では、ログ行を処理します。

各ログ行は、次のような形式の文字列です:"[<LEVEL>]: <MESSAGE>"。

ログレベルは3種類あります:

  • INFO
  • WARNING
  • ERROR

タスクは3つあり、どれもログ行を受け取って何らかの処理をするものです。

1. ログ行からメッセージを取り出す

message関数を実装して、ログ行のメッセージを返すようにしましょう:

julia> message("[ERROR]: Invalid operation")
"Invalid operation"

先頭と末尾の空白は取り除いてください:

julia> message("[WARNING]:  Disk almost full\r\n")
"Disk almost full"

2. ログ行からログレベルを取り出す

log_level関数を実装して、ログ行のログレベルを返すようにしましょう。戻り値は小文字にします:

julia> log_level("[ERROR]: Invalid operation")
"error"

3. ログ行を整形する

reformat関数を実装してログ行を整形しましょう。メッセージを先に書き、そのあとにログレベルを括弧(())に入れます:

julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"

***注:***この演習に出てくる文字列はすべて英語で、ASCII文字セットに限られています。Unicode文字を扱う機会は、あとの概念で出てきます。

GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Julia Exercism

ログレベルを始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト128個の演習、そして本物の人間によるメンタリングとともに、Juliaを学んでマスターできます。すべて無料です。