トラック
/
Go
Go
/
演習
/
ログ、ログ、ログ!
ログ、ログ、ログ!

ログ、ログ、ログ!

学習演習

はじめに

Goのrune型は、int32のエイリアスです。 この基底となるint32型があるため、rune型は符号付き32ビット整数の値を保持します。 ただし、int32型とは異なり、rune型に格納される整数値は、1つのUnicode文字を表します。

UnicodeとUnicodeコードポイント

UnicodeはASCIIの上位集合で、すべての文字に一意の番号を割り当てることで文字を表します。 この一意の番号は、Unicodeコードポイントと呼ばれます。 Unicodeは、さまざまなアルファベット、数字、記号、さらには絵文字まで、世界中のあらゆる文字をUnicodeコードポイントとして表すことを目指しています。

Goでは、rune型は1つのUnicodeコードポイントを表します。

次の表は、Unicode文字の例と、そのUnicodeコードポイントおよび10進数値を示しています:

Unicode文字 Unicodeコードポイント 10進数値
0 U+0030 48
A U+0041 65
a U+0061 97
¿ U+00BF 191
π U+03C0 960
🧠 U+1F9E0 129504

UTF-8

UTF-8は可変幅の文字エンコーディングで、すべてのUnicodeコードポイントを1、2、3、または4バイトでエンコードするために使われます。 Unicodeコードポイントは最大で4バイトでエンコードされるため、rune型は最大4バイトのデータを保持できる必要があります。 int32型は最大4バイトのデータを保持できるので、rune型がint32のエイリアスになっているのはこのためです。

GoのソースコードファイルはUTF-8でエンコードされています。

runeを使う

rune型の変数は、文字をシングルクォートで囲んで宣言します:

myRune := '¿'

runeは単なるint32のエイリアスなので、runeの型を出力するとint32になります:

myRune := '¿'
fmt.Printf("myRune type: %T\n", myRune)
// Output: myRune type: int32

同様に、runeの値を出力すると、その整数(10進数)値が表示されます:

myRune := '¿'
fmt.Printf("myRune value: %v\n", myRune)
// Output: myRune value: 191

runeが表すUnicode文字を出力するには、%c書式動詞を使います:

myRune := '¿'
fmt.Printf("myRune Unicode character: %c\n", myRune)
// Output: myRune Unicode character: ¿

runeが表すUnicodeコードポイントを出力するには、%U書式動詞を使います:

myRune := '¿'
fmt.Printf("myRune Unicode code point: %U\n", myRune)
// Output: myRune Unicode code point: U+00BF

runeと文字列

Goの文字列はUTF-8でエンコードされているため、Unicode文字を含んでいます。 文字列内の文字は、表すUnicode文字に応じて1、2、3、または4バイトで格納・エンコードされます。

Goでは、シーケンスを表すためにスライスが使われ、これらのスライスはrangeを使って反復処理できます。 文字列を反復処理すると、Goはその文字列を一連のruneに変換します。それぞれのruneは4バイトです(rune型はint32のエイリアスであることを思い出してください!)。

文字列は単なるバイトのスライスですが、rangeキーワードは文字列のバイトではなく、そのruneを反復処理します。

この例では、index変数は現在のruneのバイトシーケンスの開始インデックスを表し、char変数は現在のruneを表します:

myString := "❗hello"
for index, char := range myString {
  fmt.Printf("Index: %d\tCharacter: %c\t\tCode Point: %U\n", index, char, char)
}
// Output:
// Index: 0	Character: ❗		Code Point: U+2757
// Index: 3	Character: h		Code Point: U+0068
// Index: 4	Character: e		Code Point: U+0065
// Index: 5	Character: l		Code Point: U+006C
// Index: 6	Character: l		Code Point: U+006C
// Index: 7	Character: o		Code Point: U+006F

runeは1、2、3、または4バイトで格納されるため、文字列の長さは文字列内の文字数と常に等しいとは限りません。 文字列の長さをバイト単位で取得するには組み込みのlen関数を、文字列内のruneの数を取得するにはutf8.RuneCountInString関数を使います:

import "unicode/utf8"

myString := "❗hello"
stringLength := len(myString)
numberOfRunes := utf8.RuneCountInString(myString)

fmt.Printf("myString - Length: %d - Runes: %d\n", stringLength, numberOfRunes)
// Output: myString - Length: 8 - Runes: 6

説明

組織のログ管理を支援するログライブラリの作成が課題です。 このライブラリを使うと、あるログを出力したアプリケーションを特定したり、壊れたログを修復したり、あるログ行が特定の文字数制限に収まっているかを判定したりできるようになります。

1. あるログを出力したアプリケーションを特定する

ログは複数のアプリケーションから送られてきて、それぞれが独自のログ形式を使っています。 ログ集約システムに保存する前に、そのログを出力したアプリケーションを特定する必要があります。

ログ行を1つ受け取り、そのログ行を出力したアプリケーションを返すApplication関数を実装しましょう。

あるログ行を出力したアプリケーションを特定するには、次の表にある特定の文字をログ行から探します。

アプリケーション 文字 Unicodeコードポイント
recommendation ❗ U+2757
search 🔍 U+1F50D
weather ☀ U+2600

ログ行に上の表の文字が1つも含まれていない場合は、呼び出し元にdefaultを返します。 ログ行に上の表の文字が複数含まれている場合は、ログ行を左から右へ見ていって最初に見つかった文字に対応するアプリケーションを返します。

Application("❗ recommended search product 🔍")
// => recommendation

2. 壊れたログを修復する

ログ基盤には、まれに発生するもののなかなか直らないバグがあり、ログの中の特定の文字が壊れてしまうことがあります。 壊れた文字とその元の値を特定するのに時間を費やしたあなたは、壊れたログの修復を支援できるようにログライブラリを更新することにしました。

ログ行、壊れた文字、元の値を受け取り、壊れた文字をすべて元の値に置き換えたログ行を返すReplace関数を実装しましょう。

log := "please replace '👎' with '👍'"

Replace(log, '👎', '👍')
// => please replace '👍' with '👍'"

3. ログを表示できるかどうかを判定する

ログの表示を担当するシステムには、1つのログ行に表示できる文字数の上限があります。 そこで、あるログ行が特定の文字数制限に収まっているかどうかを判定する補助関数をこのライブラリに追加してほしい、という要望が寄せられています。

ログ行と文字数制限を受け取り、そのログ行が文字数制限に収まっているかどうかを返すWithinLimit関数を実装しましょう。

WithinLimit("hello❗", 6)
// => true
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Go Exercism

ログ、ログ、ログ!を始める準備はできましたか?

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