20200921のGoに関する記事は10件です。

【Golang】Go言語の基礎 結局ポインタって何なの??

Go言語やC言語を学ぶ上で避けては通れないのがポインタです。
プログラミング初学者の入門言語であるRubyやPHPにはポインタという概念がありません。
Rubyから入った僕にはいろんな記事を読んでも???でした!そんな方にもわかるようにここでは解説いたします!

ポインタとは?

プログラムというのは、メモリの中で読み込まれ、CPUで処理されます。また、僕たちが普段プログラムで使っている変数というのはメモリの中に配置されます。CPUによってメモリの中に配置されている変数の値を処理しているのです。
ポインタというのは、変数がメモリのどこに配置されているか?という情報(変数の住所)を格納する変数のことです。

早速コードを書いてみる

package main

import "fmt"

func main() {
    var n int = 10
    fmt.Println(n) // => 10

    //アンパサンドを付けることで変数を入れたメモリの住所(正式にはアドレスといいます。)が表示できる。
    fmt.Println(&n) // => 0xc00002c008

    //* を付けることでintgerのポインタ型の変数p(変数nのアドレスを入れる箱)を用意できる。
    var p *int = &n 

    fmt.Println(p) // => 0xc00002c008

    //変数pの値を表示したいときは*pとすると、値を表示できる。
    fmt.Println(*p) // => 10

}

var 変数名 データ型 とすることで、変数のアドレスを格納する変数を宣言することができます。
ここに変数のアドレスを入れるためには &変数名とするとその変数のアドレスを格納できます。
格納したアドレスの値を表示したい場合、
変数名とすることによって値を表示することができます。

もう少し書いてみる。

package main

import "fmt"

func one(x int) {
    x = 1
}

func main() {
    var n int = 10
    one(n)
    fmt.Println(n) // => 10

}

↑に書いたコードはone関数の引数に値しか渡していないので、最初にmain関数で宣言したxに影響はありません。

実際にxの値を更新するにはxのアドレスを引数に渡す必要があります。このようなことをしたいときにポインタが使われます↓

package main

import "fmt"

func one(x *int) {
    *x = 1
}

func main() {
    var n int = 10
    one(&n)
    fmt.Println(n) // => 1

}

one関数の引数に&nとしてnのアドレスを渡しています。その際引数はポインタ型でなければアドレスを受け取ることができないので x *int としてポインタ型の引数を定義しています。
これはやっていることとしては最初にやった var p *int = &n とやっていることは同じだとわかります。

最後まで読んでいただきありがとうございます!

何かご指摘などございましたらコメント等いただけると嬉しいです!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JWE(JSON Web Encryption)は2020年現在も危険なのか?

概要

いくつかの記事でJWEは避けるべき、脆弱性がある。という指摘があるが、これらが議論されていたのは2017年頃だ。2020年においてまだ該当記事の脆弱性が実際に存在するか調査した結果を記録する。私はJWEのエキスパートではないし、RFC-7516を全て読み込んでもいないため間違っている部分があるかもしれない。もしこの調査結果に対してフィードバックがあれば編集リクエストを送ってほしい。

結論

現在は脆弱性は修正され、安全なようだ。脆弱性に対応していないライブラリを使っている場合はもちろん危険なので使用しているライブラリが対応されているか調査する必要がある。

調査内容

go-joseの修正

oss-sec: CVE request: multiple issues in go-jose package ここでCVE番号のリクエストと、パッチへのリンクが提供されている。

Critical Vulnerability in JSON Web Encryption? · Issue #141 · square/go-jose これはgo-joseライブラリの質問。

https://github.com/square/go-jose/issues/141#issuecomment-286887765 このコメントでその脆弱性は1.1.0で修正されているよ。と書かれている。

脆弱性の概要

攻撃の名称は、Classic Invalid Curve Attack.というらしい。

この脆弱性にさらされると、攻撃者はJWE with Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES)を使用して、送信者が受信者の秘密鍵を取り出すことができるようになってしまうよ、ということらしい。

Critical Vulnerability in JSON Web Encryption ここが詳しい。

Auth0のAntonioとIETFチームのやりとり

[jose] Use of ECDH-ES in JWEこのメーリングリストでやり取りしている。

IETFの人もCurve Attackについては言及されていないとしながらも、本文は修正することができないため、Errata(正誤表)に記載してほしいとなり、Antonioが正誤表に投稿する旨で決着していました。ただ、RFC Errata Report » RFC Editorこの正誤表検索で該当のエントリは見つからないため、投稿されていないか、レビューされていない可能性がありそうだ。

JWEの利用実績

  • KubernetesのDashboardとブラウザ間の通信で実際に利用されている。Kubernetes Dashboardの該当部分はgo-joseが使われている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで画像の形式を変換するCLIツールを作った

はじめに

Goで画像の形式変換をするCLIツールを作りました。

  • jpg (jpeg)
  • png
  • gif

の間で相互に変換可能です。

ソースコードはこちら↓

機能

オプション 説明 デフォルト
-f 変換前の画像の拡張子 .jpeg
-t 変換後の画像の拡張子 .png
-r 変換前の画像を削除する false

使い方

オプションに何も指定しなかった場合は、.jpeg → .png への変換になります。

$ ./imgconv sample.jpeg

.png → .jpeg への変換はこんな感じ。

./imgconv -f .png -t .jpg sample.png

変換前の画像を削除したい場合はこんな感じ。

./imgconv -r sample.jpeg

コード

ディレクトリ構成は以下の通りです。

├── cmd
│   └── imgconv
│       └── main.go
├── imgconv.go
└── go.mod

まずは、imgconv.goから見ていきます。
説明はコメントに書きます。

imgconv.go
package imgconv

import (
    "image"
    "image/gif"
    "image/jpeg"
    "image/png"
    "os"
    "path/filepath"
)

// 変換前のファイルを削除する
func removeSrc(src string) error {
    err := os.Remove(src)
    if err != nil {
        return err
    }
    return nil
}

// 画像の拡張子を変換する
func Convert(src, dst string, rmsrc bool) error {
    // 変換前のファイルを読み取り専用で開く
    sf, err := os.Open(src)
    if err != nil {
        return err
    }
    defer sf.Close()

    // image.Imageへとデコード
    img, _, err := image.Decode(sf)
    if err != nil {
        return err
    }

    // 読み書き用ファイルを作成
    df, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer df.Close()

    // .以降を取り出して条件分岐 その後image.Imageからエンコード
    switch filepath.Ext(dst) {
    case ".png":
        err = png.Encode(df, img)
    case ".jpg", ".jpeg":
       // 第三引数は画質で1~100まで。(今回は定数を使用:75)
        err = jpeg.Encode(df, img, &jpeg.Options{Quality: jpeg.DefaultQuality})
    case ".gif":
        err = gif.Encode(df, img, nil)
    }

    if err != nil {
        return err
    }

    // 変換前のファイル削除
    if rmsrc {
        if err = removeSrc(src); err != nil {
            return err
        }
     }
    return nil
}

次は、cmd/imgconv/main.go

cmd/imgconv/main.go
package main

import (
    "flag"
    "fmt"
    "os"
    "strings"

    "github.com/Le0tk0k/imgconv"
)

// 第一引数に名前、第二引数にデフォルト値、第三引数に使い方を渡す。戻り値はフラグの値が格納されるそれぞれの型の変数のアドレス。
var from = flag.String("f", ".jpeg", "Extension before conversion")
var to = flag.String("t", ".png", "Extension after conversion")
var rm = flag.Bool("r", false, "Remove file before conversion")

func main() {
    flag.Parse() // コマンドライン引数がパースされ、ポインタの指す先に値が設定される。
    src := flag.Arg(0)
    // 第一引数の文字列のコピーに対し、第四引数個、第二引数の部分を第三引数に置換したものを返す。
    dst := strings.Replace(flag.Arg(0), *from, *to, 1)

    err := imgconv.Convert(src, dst, *rm)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%s\n", err.Error())
    }
}

flag.Argsはos.Argsと違って0番目の要素にコマンドを含まないので、flag.Arg(0)で変換前のファイル名を取得できます。

以上です!

さいごに

簡単に作れて、標準パッケージの勉強にもなるのでコマンドラインツール作成おすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゴゴコ゛ゴゴゴコ゛をゴゴゴゴゴゴゴにするGo言語のライブラリを作ってみた

はじめに

Unicode 正規化って日本語の処理に向いていないというか、ちょっと使いどころが難しいと思いませんか?

ということで、Golang のノーマライゼーション・ライブラリを作ってみました。使えるレベルに仕上がったと思うので GaGa という名前で公開します。

Unicode 正規化に関しては、既に Golang の準標準ライブラリに、

  • golang.org/x/text/unicode/norm
  • golang.org/x/text/width

というパッケージがありますが、今回作ったものと機能を比較すると以下のような違いがあります。

機能など gaga norm width 補足
全角-半角
変換
詳細は後述しますが、norm と width は期待外れな結果を返します。
ひらがな-
カタカナ
変換
× × 「ひらがな-カタカナ変換器」はノーマライザというよりはトランスレータの類かもしれませんね。
大文字-
小文字
変換
× × これは Golang の標準ライブラリ strings パッケージでサポートされていますね。これもトランスレータの類でしょうか。
濁点・半濁点
の分解合成
これも norm と width は期待外れな振舞いをします。詳細は後述します。
文字種別ごとの変換 × × gaga は アルファベット、ラテン数字、ラテン記号、ひらがな、カタカナ、かな記号ごとに変換方法を指定できます。
パフォーマンス 処理時間を比較するとこんな感じです。
gaga : norm : width ≒ 2 : 6 : 1
サポートする文字 gaga のサポート範囲はラテン文字とひらがなカタカナを中心とする462文字です。

ちなみに、Golang の準標準ライブラリの振舞いが期待外れなのは、開発チームや実装に問題があるということではありません。世界中のありとあらゆる文字を取り扱わなければならない Unicode の標準化において、多種多様な文化間のバランスを取りながら最大公約数的に策定せざるを得なかった正規化フォームの仕様が、「かな文字」をネイティブとして扱う我々にとっては完全なものではないということなのだと思います。

目次

Unicode の 7 種類の「ゴ」について

Unicode でカタカナの「ゴ」と書くとき、多くの人はコードポイント U+30B4(名称: KATAKANA LETTER GO)を使うと思います。

ところが歴史的な事情により UNICODE には 3 種類の「濁点マーク」が登録されていて、

3 種類の濁点マーク

グリフ Unicode JIS 名前
U+309B 212B
(JIS X 0208)
KATAKANA-HIRAGANA VOICED SOUND MARK
U+FF9E DE
(JIS X 0201)
HALFWIDTH KATAKANA VOICED SOUND MARK
◌゙ U+3099 (undefined) COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK

これに濁点なしの全角の「コ」と半角の「コ」を組み合わせると、「ゴ」という 1 つの文字を 7 通りの方法で表現できてしまいます。

7 通りの「ゴ」

No. グリフ 構成 コードポイント 説明
1 [ ] [ U+30B4 ] [ 濁点付き 全角文字 ]
2 コ゛ [ ] +
[ ]
[ U+30B3 ] +
[ U+309B ]
[ 濁点なし 全角文字 ] +
[ 全角 濁点 ]
3 ゴ [ ] +
[ ]
[ U+30B3 ] +
[ U+FF9E ]
[ 濁点なし 全角文字 ] +
[ 半角 濁点 ]
4 ゴ [ ] +
[ ]
[ U+30B3 ] +
[ U+3099 ]
[ 濁点なし 全角文字 ] +
[ 幅なし 濁点 ]
5 コ゛ [ ] +
[ ]
[ U+FF7A ] +
[ U+309B ]
[ 濁点なし 半角文字 ] +
[ 全角 濁点 ]
6 ゴ [ ] +
[ ]
[ U+FF7A ] +
[ U+FF9E ]
[ 濁点なし 半角文字 ] +
[ 半角 濁点 ]
7 ゴ [ ] +
[ ]
[ U+FF7A ] +
[ U+3099 ]
[ 濁点なし 半角文字 ] +
[ 幅なし 濁点 ]

上表のグリフ欄の見え方は環境によって変わりますが、筆者の環境(Windows 10 + Chrome 85)では、上記 1 と 4、また 5 〜 7 は見た目上の違いがわかりません。

また、これは特にラテン文字に顕著ですが、プロポーショナルフォントが当たり前になった現代では、全角と半角の違いが昔より分かりにくくなりました。

そして、この「似ているのに異なる」ことが、

  • 検索してても見つからない
  • 統計を取る際のカテゴライズに苦労する

などといった、地味だけど面倒な問題を引き起こします。

この問題の対処方法はいくつかありますが、

No. アプローチ ソリューションの具体例 補足
1 入力制限 Validation 検査 登録フォームなど情報の入口で入力できる文字を制限し、正規形のみを通過させるとてもポピュラーなアプローチ。
2 選択 プルダウンメニューなど 入力制限の一種。選択肢が少ないときは便利。
3 同値とみなす DBMS の Collation 情報の出口での対処方法。データベースの文字セットに照合順序の属性を持たせ、比較の際に例えば半角と全角、大文字と小文字を同値とみなすアプローチ。
4 形を揃える Unicode Normalization 正規化して形を整えてから比較するアプローチ。情報の入口でも適用できるが、Webサイトの登録フォームなどではあまり見かけない。

本稿のテーマは上記 4 に関するものです。

誰かがついうっかり「ゴー言語」や「コ゛ー言語」と入力してしまっても、由緒正しい「ゴー言語」ではないからといって、「その文字は入力できません!」などと拒絶するのではなく、そっと優しく正規の形に導いてあげませんか? そのためのライブラリを作りました!という話です。

Combining characters について

上述のとおり、Unicode には、JIS 由来の濁点マーク [゛](U+309B)、[゙](U+FF9E) の他に、端末でレンダリングする際に基底文字に合成してレイアウトされる「幅ゼロの濁点マーク」[◌゙](U+3099) が登録されていて、その名称には "COMBINING" というプレフィクスが付けられています。
(半濁点にも同様に [ ゚ ](U+309A) があります)

普段の生活でこの Combining character (Non-space mark) を使う機会は少ないと思いますが、Unicode 正規化フォームの「正準等価性による分解と合成」においてもキーになる文字なので少しだけ触れておきます。

Combining character のメリット

かつて JIS X 0208 には という文字はありましたが、 はありませんでした。
JIS X 0213 では が登録され、他にも、(濁点付きの [ワ])、カ゚(半濁点付きの [カ])なども登録されました。

ところが、JIS X 0213 に はあるのに、わ゙ はありません。
また、Unicode への カ゚ の登録は見送られました。見送られた理由は Combining character の U+309A で合成できるから、ということのようです。

整理すると以下のようになります。

特殊な濁点付き文字の例

グリフ
(Unicode)
代用表記
(JIS X 0208)
JIS X 0213
のサポート
Unicode
のサポート
補足
う゛
ワ゛
わ゙ わ゛ × × グリフ欄:
[わ]+[U+3099]で表示
カ゚ カ゜ × グリフ欄:
[カ]+[U+309A]で表示

また、以下のような文字も JIS に登録されていません。
(JIS に登録されていないので、当然 Unicode にも登録されていません)

用途 グリフ
Unicode
代用表記
JIS X 0208
補足
ジャイアントロボの決め台詞 マ゙ マ゛ グリフ欄:
[マ]+[U+3099]で表示
一部の官能表現など ア゙ ア゛ グリフ欄:
[ア]+[U+3099]で表示

近年、アーティスト名の表現方法が多彩になってきていますが、今後、濁音を持たない文字に濁点や半濁点を組み合わせた名前も出てくるかもしれません。

Combining character を使えば、新たにコードポイントを追加しなくても、このような要求を満たすことができます。

Combining character のデメリット

現時点ではまだ普及しているとはいえないことが挙げられるでしょう。
環境によって見え方が変わってしまうのです。

同じプラットフォームで同じアプリケーションを使っていても、フォントにより見え方がまったく違います。
以下は筆者の環境で、ひとつの Excel シート内に、Combining Character を含む同一の文字列をフォントだけ変えて表示した例です。

image.png

編集時の挙動もアプリケーションによって違います。
アプリケーションによって、濁点だけを削除することができたり、先行の文字にくっついて削除できなかったりと、違いがあります。
興味がある人は以下の文字列をエディタなどにコピペして試してみてください。

え゚?゚ G゙A゙G゙A゙が゙?゙

このように、Combining character はまだまだ普及しているとは言えません。現段階ではキャラクターベースのコミュニケーション用途には向きません。どうしても使いたいという場合は、環境要件を固定するか、特定の環境でラスタライズしたものを共有するなどの対応が必要になるでしょう。

Unicode 正規化フォームの問題点

問題点について述べる前に Unicode 正規化フォーム (Unicode Normalization Form) について簡単に整理しておきます。

まず用語ですが、本稿では以下の用語を使って話を進めます。

用語 別名
(かな文字の場合)
略称
Voicing modifier 発声修飾子
(濁点または半濁点)
[] [] VOM
Pre-composed character 合成済み文字
(濁点付き文字)
[] Precomp
Base character 基底文字
(濁点なし文字)
[] Base
Combining character 結合文字
(幅なしのVOM)
※全角と半角のVOMは含まない
[ ◌゙ ] Combining

次に Unicode の「正準等価性」と「互換等価性」について整理します。

正準等価性

まずは、正準等価性です。
正準等価性というのは、かな文字で言えば濁点付き文字(または半濁点付き文字)の等価性のことで、分解と合成により別の等価な文字シーケンスに置き換えることができます。

正準等価性による分解・合成

正規化の種類 かな文字の挙動
分解 Precomp を Base と Combining に分解する。 [] => [] + [ ◌゙ ]
合成 Base と Combining を Precomp に合成する。 [] + [ ◌゙ ] => []

本稿のテーマからは逸れますが、ラテン文字のダイアクリティカルマークなども、正準等価性による分解と合成をすることで、これとよく似た変換が行われます。

互換等価性

次に、互換等価性です。
互換等価性は、かな文字で言えば全角と半角の等価性のことです。
分解により、かな文字は全角へ、ラテン文字は半角へ変換されます。

また、かな文字以外では「互換等価性による分解」で「文字幅」が変化するとは限らないので注意が必要です。
例えば、[㍉] という全角の非漢字文字は、 [ミ][リ] という全角カタカナ 2 文字に分解されます。

互換等価性による分解

正規化の種類 かな文字の挙動
分解 半角文字を全角文字にする。
(ラテン文字の場合は逆)
[] => []
[
] => [ A ]

なお、「互換等価性による合成」という言葉は Uicode 正規化の仕様には出てこないようですが、かな文字の全角から半角への変換はこれにあたると考えることもできるでしょう。

Golang の準標準ライブラリ

冒頭で触れたとおり、Golang の準標準ライブラリには Unicode Normalization Form 関連のパッケージが 2 つありますが、どちらもかな文字の扱いに関して問題があり、期待した結果を返してくれません。

golang.org/x/text/width の問題点

ひとつは golang.org/x/text/width パッケージで、これは互換等価性の分解と合成に近い働きをするものです。
(先に述べた通り互換等価性による合成という言葉は Unicode の仕様には出てきません)

要するに全角 (Full/Wide) と半角 (Half/Narrow) の変換をしてくれるものなのですが、このパッケージで 7 種類の「ゴ」を変換してみると可愛げのない挙動になります。

確認用のプログラムは以下のとおりです。

width_examples.go
package main

import (
        "fmt"
        "golang.org/x/text/width"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ゴコ゛コ\u3099ゴゴコ゛コ\u3099"
        narrow := width.Narrow.String(s)
        widen := width.Widen.String(s)
        fold := width.Fold.String(s)

        fmt.Printf("SRC :\t%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("NAR :\t%q,\n\t%s\n", narrow, hexs(narrow))
        fmt.Printf("WIDE:\t%q,\n\t%s\n", widen, hexs(widen))
        fmt.Printf("FOLD:\t%q,\n\t%s\n", fold, hexs(fold))
}

実行結果は以下のとおりです。

結果
$ go run width_examples.go 
SRC :   "ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
NAR :   "ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A FF9E 30B4 FF7A FF9E FF7A 309B FF7A FF9E]
WIDE:   "ゴコ゛ゴゴゴコ゛ゴ",
        [30B3 3099 30B3 309B 30B3 3099 30B4 30B3 3099 30B3 309B 30B3 3099]
FOLD:   "ゴコ゛ゴゴゴコ゛ゴ",
        [30B3 3099 30B3 309B 30B3 3099 30B4 30B3 3099 30B3 309B 30B3 3099]

結果を整理すると以下のようになります。
(具合の悪い箇所を赤くしています)

Source ゴ
FF7A
FF9E
コ゛
FF7A
309B
ゴ
FF7A
3099

30B4
 
ゴ
30B3
FF9E
コ゛
30B3
309B

30B3
3099
問題点
Narrow ゴ
FF7A
FF9E

FF7A
309B
ゴ
FF7A
FF9E

30B4
 
ゴ
FF7A
FF9E

FF7A
309B
ゴ
FF7A
FF9E
Wide VOM(309B) が Narrow にならない。
Wide Precomp(30B4) が Narrow にならない。
Widen
30B3
3099
コ゛
30B3
309B

30B3
3099

30B4
 

30B3
3099
コ゛
30B3
309B

30B3
3099
3 つとも Precomp(30B4) になってほしい。
せめて Base + Combining (30B3+3099) に揃えてほしい。
Fold
30B3
3099
コ゛
30B3
309B

30B3
3099

30B4
 

30B3
3099
コ゛
30B3
309B

30B3
3099
Fold は Latin を Narrow に、Kana を Wide にする Transformer。問題点は Widen と同様。

golang.org/x/text/unicode/norm の問題点

もうひとつは golang.org/x/text/unicode/norm で、4 種類の Unicode Normalization Form をサポートしています。

4 種類の Unicode 正規化形式

形式 説明 かな文字の場合
NFD 正準等価性によって分解される。 濁点や半濁点が基底文字から分離する。Precomp => Base + Combining
NFC 正準等価性によって分解された後に、正準等価性によって合成される。 濁点や半濁点が基底文字から分離した後に再度合成される。
Precomp => Base + Combining,
Base + Combining => Precomp
NFKD 互換等価性によって分解される。 半角カタカナが全角カタカナになる。
Narrow => Wide
NFKC 互換等価性によって分解された後に、正準等価性によって合成される。 半角カタカナが全角カタカナに変換された後に、基底文字と合成文字が合成される。
Narrow => Wide,
Base + Combining => Precomp

7 種類の「ゴ」を変換してみます。

norm_examples.go
package main

import (
        "fmt"
        "golang.org/x/text/unicode/norm"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ゴコ゛コ\u3099ゴゴコ゛コ\u3099"
        nfd := norm.NFD.String(s)  // Canonical decomposition
        nfkd := norm.NFKD.String(s) // Compatibility decomposition
        nfc := norm.NFC.String(s)  // Canonical decomposition -> Canonical composition
        nfkc := norm.NFKC.String(s) // Compatibility decomposition -> Ccanonical composition

        fmt.Printf("SRC :\t%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("NFD :\t%q,\n\t%s\n", nfd, hexs(nfd))
        fmt.Printf("NFKD:\t%q,\n\t%s\n", nfkd, hexs(nfkd))
        fmt.Printf("NFC :\t%q,\n\t%s\n", nfc, hexs(nfc))
        fmt.Printf("NFKC:\t%q,\n\t%s\n", nfkc, hexs(nfkc))

}

実行結果は以下のとおりです。

結果
$ go run norm_examples.go 
SRC :   "ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
NFD :   "ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B3 3099 30B3 FF9E 30B3 309B 30B3 3099]
NFKD:   "ゴコ ゙ゴゴゴコ ゙ゴ",
        [30B3 3099 30B3 0020 3099 30B3 3099 30B3 3099 30B3 3099 30B3 0020 3099 30B3 3099]
NFC :   "ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B4]
NFKC:   "ゴコ ゙ゴゴゴコ ゙ゴ",
        [30B4 30B3 0020 3099 30B4 30B4 30B4 30B3 0020 3099 30B4]

結果を整理すると以下のようになります。

Source ゴ
FF7A
FF9E
コ゛
FF7A
309B
ゴ
FF7A
3099

30B4
 
ゴ
30B3
FF9E
コ゛
30B3
309B

30B3
3099
問題点
NFD
FF7A
FF9E

FF7A
309B
ゴ
FF7A
3099

30B3
3099

30B3
FF9E

30B3
309B

30B3
3099
Combining ではない VOM が無視される。
NFC
FF7A
FF9E

FF7A
309B
ゴ
FF7A
3099

30B4
 

30B3
FF9E

30B3
309B

30B4
 
NFD と同様。
NFKD
30B3
3099

30B3
0020
3099

30B3
3099

30B3
3099

30B3
3099

30B3
0020
3099

30B3
3099
全角の VOM が Whitespace + Combining になる。
NFKC
30B4
 

30B3
0020
3099

30B4
 

30B4
 

30B4
 

30B3
0020
3099

30B4
 
全角の VOM が Wthitespace + Combining になるため、再合成されない。

仕様通りに振舞っているのだとは思います。
でも、このパッケージをありがたいと感じている日本人は少ないのではないでしょうか。

どんなライブラリを作ったのか?

日本語文字列ユーティリティです。
ノーマライザは、日本語のロケールで良く使われる 426 文字(ASCII 互換のラテン文字と JIS 互換のひらがなカタカナなど)をサポートしており、文字列の縦書き関数などを同梱しています。

ダウンロード

ソースコードは GitHub に置いてあります。
image.png
go-gaga (Japanese language utility)
README

Golang の環境があれば、以下のコマンドでライブラリが使えるようになります。

console
$ go get github.com/y-bash/go-gaga

使い方

まず、正規化ルールをフラグで指定して Normalizer を生成します。
(エラー処理については後述します)

n, err := gaga.Norm(gaga.LatinToNarrow | gaga.KanaToWide)

あとは String() メソッドを呼ぶだけです。

s := n.String("変換したい文字列")

途中でフラグを変更することもできます。
(エラー処理については後述します)

err = n.SetFlag(gaga.LatinToWide)
s = n.String("変換したい文字列")

もう少し具体的なコードの例を示します。
以下のように文字種別ごとに半角全角の変換、濁点・半濁点の結合、分解、カタカナひらがなの変換、大文字小文字の変換が行えます。

gaga_examples.go
package main

import (
    "fmt"
    "github.com/y-bash/go-gaga"
)

func main() {
    s := "ゴコ゛コ\u3099ゴゴコ゛コ\u3099"
    n, _ := gaga.Norm(gaga.ComposeVom)   // 濁点と半濁点を適切に結合する
    fmt.Println(n.String(s))             // => ゴゴゴゴゴゴゴ

    n, _ = gaga.Norm(gaga.KanaToNarrow)  // ひらがなとカタカナを半角にする
    fmt.Println(n.String(s))             // => ゴゴゴゴゴゴゴ

    n, _ = gaga.Norm(gaga.KanaToWide)    // カタカナを全角にする
    fmt.Println(n.String(s))             // => ゴゴゴゴゴゴゴ

    n, _ = gaga.Norm(gaga.KanaToHiragana) // カタカナをひらがなにする
    fmt.Println(n.String(s))              // => ごごごごごごご

    s = "abcアイウ"
    n, _ = gaga.Norm(
        gaga.LatinToNarrow | // ラテン文字を半角に、
        gaga.AlphaToUpper |  // アルファベットを大文字に、
        gaga.KanaToHiragana) // カタカナをひらがなにする
    fmt.Println(n.String(s)) // => ABCあいう
}

gaga で width や norm と同様のプログラムを書いてみます。

gaga_examples.go
package main

import (
        "fmt"
        "github.com/y-bash/go-gaga"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ゴコ゛コ\u3099ゴゴコ゛コ\u3099"
        n, _ := gaga.Norm(gaga.ComposeVom)
        co := n.String(s)

        n.SetFlag(gaga.DecomposeVom)
        de:= n.String(s)

        n.SetFlag(gaga.KanaToNarrow)
        na:= n.String(s)

        n.SetFlag(gaga.KanaToWide)
        wi:= n.String(s)

        n.SetFlag(gaga.KanaToHiragana)
        hi := n.String(s)

        n.SetFlag(gaga.KatakanaToHiragana | gaga.DecomposeVom)
        hd := n.String(s)

        fmt.Printf("SRC            :%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("ComposeVom     :%q,\n\t%s\n", co, hexs(co))
        fmt.Printf("DecomposeVom   :%q,\n\t%s\n", de, hexs(de))
        fmt.Printf("KanaToNarrow   :%q,\n\t%s\n", na, hexs(na))
        fmt.Printf("KanaToWide     :%q,\n\t%s\n", wi, hexs(wi))
        fmt.Printf("KanaToHiragana :%q,\n\t%s\n", hi, hexs(hi))
        fmt.Printf("Hiragana&DecVom:%q,\n\t%s\n", hd, hexs(hd))
}

結果は以下のとおりです。

結果
$ go run gaga_examples.go 
SRC            :"ゴコ゛ゴゴゴコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
ComposeVom     :"ゴゴゴゴゴゴゴ",
        [FF7A FF9E FF7A FF9E FF7A FF9E 30B4 30B4 30B4 30B4]
DecomposeVom   :"ゴゴゴゴゴゴゴ",
        [FF7A 3099 FF7A 3099 FF7A 3099 30B3 3099 30B3 3099 30B3 3099 30B3 3099]
KanaToNarrow   :"ゴゴゴゴゴゴゴ",
        [FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E]
KanaToWide     :"ゴゴゴゴゴゴゴ",
        [30B4 30B4 30B4 30B4 30B4 30B4 30B4]
KanaToHiragana :"ごごごごごごご",
        [3054 3054 3054 3054 3054 3054 3054]
Hiragana&DecVom:"ごごごごごごご",
        [3053 3099 3053 3099 3053 3099 3053 3099 3053 3099 3053 3099 3053 3099]

結果を整理すると以下のようになります。

Source ゴ
FF7A
FF9E
コ゛
FF7A
309B
ゴ
FF7A
3099

30B4
 
ゴ
30B3
FF9E
コ゛
30B3
309B

30B3
3099
ComposeVom ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E

30B4
 

30B4
 

30B4
 

30B4
 
DecomposeVom ゴ
FF7A
3099
ゴ
FF7A
3099
ゴ
FF7A
3099

30B3
3099

30B3
3099

30B3
3099

30B3
3099
KanaToNarrow ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
ゴ
FF7A
FF9E
KanaToWide
30B4
 

30B4
 

30B4
 

30B4
 

30B4
 

30B4
 

30B4
 
KanaToHiragana
3054
 

3054
 

3054
 

3054
 

3054
 

3054
 

3054
 
KatakanaToHiragana |
DecomposeVom

3053
3099

3053
3099

3053
3099

3053
3099

3053
3099

3053
3099

3053
3099

エラーハンドリングについて

上記のコードでは見やすさのために省略していましたが、実際に使う際はエラーを適切にハンドリングする必要があります。gaga.Norm() も、gaga.Normalizer.SetFlag() も、存在しない値や排他的なフラグの組み合わせ(例えば KanaToNarrow | KanaToWide など)が指定されると error を返します。

以下にエラーハンドリングの例を記しておきます。

gaga_error.go
package main

import (
        "fmt"
        "github.com/y-bash/go-gaga"
        "log"
)

func main() {
        n, err := gaga.Norm(gaga.LatinToWide | gaga.AlphaToUpper)
        if err != nil {
          log.Fatal(err)
        }
        fmt.Println(n.String("aaa"))
        fmt.Println(n.String("bbb"))
        fmt.Println(n.String("ccc"))

        err = n.SetFlag(gaga.LatinToNarrow | gaga.SymbolToWide)
        if err != nil {
          log.Fatal(err)
        }
        fmt.Println(n.String("aaa"))
        fmt.Println(n.String("bbb"))
        fmt.Println(n.String("ccc"))
}
結果
$ go run gaga_error.go 
AAA
BBB
CCC
2020/09/20 08:57:21 invalid normalization flag: (AlphaToNarrow | DigitToNarrow | SymbolToNarrow | SymbolToWide), invalid combination: (SymbolToNarrow | SymbolToWide)
exit status 1

コマンドラインツール

go build や go install でライブラリと同じ機能を持つコマンドラインツールをビルドできます。
Linux と Windows で動作確認しています。
norm コマンド README

フラグの種類

変換ルールを指示するための 20 個のフラグ定数(単独フラグ)が用意されています。
ノーマライザを生成する際にこれを指定して使うのですが、その際、フラグをビット OR 演算子で組み合わせて指定することができます。

また、複数のフラグを組み合わせた定数(コンビネーションフラグ)が 8 個用意されているので、通常はこちらを使う方が便利でしょう。

コンビネーションフラグ (8個)

フラグ 意味 同等の組み合わせ
LatinToNarrow アルファベット、ラテン数字、ラテンシンボルを半角にする。
【例】
"A1?" => "A1?"
AlphaToNarrow | DigitToNarrow | SymbolToNarrow
LatinToWide アルファベット、ラテン数字、ラテンシンボルを全角にする。
【例】
"A1?" => "A1?"
AlphaToWide | DigitToWide | SymbolToWide
KanaToNarrow ひらがな、カタカナ、仮名シンボルを半角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を半角にする。
【例】
"あイ、が゛" => "アイ、ガ゙"
HiraganaToNarrow | KatakanaToNarrow | KanaSymbolToNarrow | IsolatedVomToNarrow | ComposeVom
KanaToWide カタカナ、仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。
【例】
"ア、ガ゙" => "ア、ガ゛"
KatakanaToWide | KanaSymbolToWide | IsolatedVomToWide | ComposeVom
KanaToWideKatakana ひらがなを全角カタカナにする。半角カタカナを全角カタカナにする。仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。
【例】
"あイ、ガ゙" => "アイ、ガ゛"
KatakanaToWide | HiraganaToKatakana | KanaSymbolToWide | IsolatedVomToWide | ComposeVom
KanaToNarrowKatakana ひらがなを半角カタカナにする。全角カタカナを半角カタカナにする。仮名シンボルを半角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を半角にする。
【例】
"あイ、が゛" => "アイ、ガ゙"
KatakanaToNarrow | HiraganaToNarrow | KanaSymbolToNarrow | IsolatedVomToNarrow | ComposeVom
KanaToHiragana 全角カタカナと半角カタカナをひらがなにする。仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。
【例】
"アイ、ガ゛" => "あい、が゛"
KatakanaToHiragana | KanaSymbolToWide | IsolatedVomToWide | ComposeVom
Fold 全角アルファベット、数字、シンボルを半角にする。半角カタカナを全角カタカナにする。半角仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。
【例】
"A1?ア、ガ゙" => "A1?ア、ガ゛"
LatinToNarrow | KanaToWide

単独フラグ (20個)

フラグ 意味
AlphaToNarrow 全角アルファベットを半角にする。
【例】
[A]=>[A]
AlphaToWide 半角アルファベットを全角にする。
【例】
[A]=>[A]
AlphaToUpper 半角アルファベットと全角アルファベットを大文字にする。
【例】
[a]=>[A], [a]=>[A]
AlphaToLower 半角アルファベットと全角アルファベットを小文字にする。
【例】
[A] => [a], [A] => [a]
DigitToNarrow 全角ラテン数字を半角にする。
【例】
[1]=>[1]
DigitToWide 半角ラテン数字を全角にする。
【例】
[1]=>[1]
SymbolToNarrow 全角ラテン・シンボルを半角にする。
【例】
[?]=>[?]
SymbolToWide 半角ラテン・シンボルを全角にする。
【例】
[?]=>[?]
HiraganaToNarrow ひらがなを半角カタカナにする。
【例】
[あ]=>[ア]
HiraganaToKatakana ひらがなを全角カタカナにする。
【例】
[あ]=>[ア]
KatakanaToNarrow 全角カタカナを半角にする。
【例】
[ア]=>[ア]
KatakanaToWide 半角カタカナを全角にする。
【例】
[ア]=>[ア]
KatakanaToHiragana 半角カタカナと全角カタカナをひらがなにする。
【例】
[ア] => [あ]
[ア] => [あ]
KanaSymbolToNarrow 全角仮名シンボルを半角にする。
【例】
[、]=>[、]
KanaSymbolToWide 半角仮名シンボルを全角にする。
【例】
[、]=>[、]
ComposeVom 濁点と半濁点を従来の方法で結合する。
【例】
[か][゛]=>[が]
[か][U+3099] => [が]
[か][゙]=>[が]
[カ][゛]=>[カ][゙]
[カ][゙]=>[カ][゙]
[カ][U+3099]=>[カ][゙]
[は][゜]=>[ぱ]
[ヰ][゛]=>[ヸ]

Pre-composed character を持たないひらがなカタカナに続く濁点と半濁点は、先行する文字と同じ幅に変換される。
[あ][゙]=>[あ][゛]
[あ][U+3099]=>[あ][゛]
[ア][゛]=>[ア][゙]
[ア][U+3099]=>[ア][゙]
[ゐ][゛]=>[ゐ][゛]

Pre-composed character に続く濁点と半濁点はこのフラグでは変換されない。
【例】[が][゛]=>[が][゛]
[が][U+3099]=>[が][U+3099]
[が][゙]=>[が][゙]

ひらがなカタカナ以外の文字に続く濁点と半濁点はこのフラグでは変換されない。
【例】
[日][゛]=>[日][゛]
[日][U+3099]=>[日][U+3099]
[日][゙]=>[日][゙]
DecomposeVom 濁点と半濁点を Unicode Normalization Form の正準等価による分解に似た方法で分解する。
【例】
[が]=>[か][U+3099]
[か][゛]=>[か][U+3099]
[か][゙]=>[か][U+3099]
[カ][゛]=>[カ][U+3099]
[カ][゙]=>[カ][U+3099]
[は][゜]=>[は][U+309A]
[あ][゛]=>[あ][U+3099]


Pre-composed character に続く濁点と半濁点はこのフラグでは変換されない。

ひらがなカタカナ以外の文字に続く濁点と半濁点はこのフラグでは変換されない。
IsolatedVomToNarrow Base character に接続されない独立した濁点と半濁点を半角にする。
【例】
[゛]=>[゙]
[U+3099]=>[゙]
[゜]=>[゚]
[U+309A]=>[゚]
IsolatedVomToWide Base character に接続されない独立した濁点と半濁点を全角にする。
【例】
[゙]=>[゛]
[U+3099]=>[゛]
[゚]=>[゜]
[U+309A]=>[゜]
IsolatedVomToNonspace Base character に接続されない独立した濁点と半濁点を Non-space (Combining character) にする。
【例】
[゛]=>[U+3099]
[゙]=>[U+3099]
[゜]=>[U+309A]
[゚]=>[U+309A]

どのように実装したのか?

The Unicode Consortium が提供する UCD in XML (Unicode Character Database in XML) を元に Golang 用のテーブルを生成しています。

ジェネレータのコード
ジェネレータが生成したテーブル (unichar_tables.go)

コード生成の大まかな流れは以下のとおりです。

コード生成シーケンス

No. プロセス 補足
1. zip 形式ファイルのダウンロード
2. zip 解凍、XML ファイル (UCD) の取り出し
3. XML ファイルのパース
4. パースした XML を CSV として出力 確認用 Utility
(ソースコード生成とは別のコマンド)
5. UCD に足りない情報の付与 追加した情報
- ひらがな-カタカナのリレーション、
- 全角小書き文字-半角並字のリレーションなど
6. 文字間の双方向リンクの作成 UCD のひらがなカタカナ情報には片方向のリンクしかない。
- Narrow->Wide(ア->ア など)
- Precomp->Base,Combining (ガ->カ,◌゙ など)
※ラテン文字の Uppercase と Lowercase については双方向のリンク情報がある
7. 拡張 UCD (5, 6 を施したもの)を CSV 出力 確認用 Utility
(ソースコード生成とは別のコマンド)
多言語ライブラリへの移植用としての活用も想定
(Rust, TypeScript/JavaScript, etc...)
8. 拡張 UCD を Golang のテーブルとして出力 with go fmt

なお、コード生成については以下の情報を参考にさせていただきました。

yaegashi さんの記事は、ジェネレータを含むプロジェクトのリポジトリ構成をどうすべきかという点について参考にさせていただきました。

mattn さんの go-runewidth からも多くの事を学ばせていただきました。中でも生成したテーブル構造のチェックサムをテストするアイデアを真似させていただき、これがとても気に入ってます。これを行うことで、万一、元ネタ(ダウンロードデータ)の内容が変更されたり、ジェネレータをリファクタリングした際にうっかり生成コードを壊してしまうようなことがあってもすぐ気づくことができます。

また、GaGa に同梱した 縦書きコマンド vert の中で go-runewidth を使わせていただいています。

お二人ともありがとうございました。

パフォーマンスは良いのか?

パフォーマンスは悪くないと思います。
というか、VOM の結合と分解をしているところ以外は、アルゴリズム的にほぼ何もしていません(笑)

Unicode のブロックごとに 4 つのテーブル (配列) があり(連続するブロックは 1 つにまとめています)、変換する文字がこれのどれにあたるのかを線形検索しているところの平均計算量が O(2)
テーブルが決まってからは、変換する文字のコードポイント(rune の値)からオフセット値を計算、それを配列のインデックスにして要素を取り出しているところの計算量が O(1) です。

ベンチマークのコードと結果は以下のとおりです。

benchmark
const normSTR = "Aa# Aa#あア。ア。”゙漢字ガギグゲゴパピプペポ"

func BenchmarkString(b *testing.B) {
        b.ResetTimer()
        n, _ := Norm(LatinToNarrow | KanaToWide)
        for i := 0; i < b.N; i++ {
                n.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkNormNFKD(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                norm.NFKD.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkNormNFKC(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                norm.NFKC.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthNarrow(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Narrow.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthWiden(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Widen.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthFold(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Fold.String(normSTR)
        }
        b.StopTimer()
}
benchmark_result
$ make bench
go test -v -run "Benchmark" -bench . -benchmem -o ./output/bench.bin -cpuprofile=./output/cpu.prof -memprofile=./output/mem.prof
goos: linux
goarch: amd64
pkg: github.com/y-bash/go-gaga
BenchmarkString
BenchmarkString-2                 713604              1683 ns/op             224 B/op          2 allocs/op
BenchmarkNormNFKD
BenchmarkNormNFKD-2               189483              6348 ns/op             784 B/op          3 allocs/op
BenchmarkNormNFKC
BenchmarkNormNFKC-2               165166              7101 ns/op             752 B/op          3 allocs/op
BenchmarkWidthNarrow
BenchmarkWidthNarrow-2           1395560               892 ns/op             368 B/op          3 allocs/op
BenchmarkWidthWiden
BenchmarkWidthWiden-2            1392997               854 ns/op             384 B/op          3 allocs/op
BenchmarkWidthFold
BenchmarkWidthFold-2             1452519               822 ns/op             368 B/op          3 allocs/op

上記結果を gaga を 100 として比較すると以下のようになります。

パッケージ 処理時間 メモリ使用量 アロケーション回数
gaga 100 100 100
norm.NFKD 377 350 150
norm.NFKC 422 336 150
width.Narrow 53 164 150
width.Widen 51 171 150
width.Fold 49 164 150

norm パッケージの 3 〜 4 倍の速度が出ているので悪くないと思います。
width は速いですね。VOMの結合と分解をしていないからですかね …

GaGa がサポートする 462文字

ライブラリがサポートする文字の一覧表を挙げておきます。

一覧表を展開する
Block Name Age Codepoint Glyph
ASCII SPACE 1.1 U+0020
ASCII EXCLAMATION MARK 1.1 U+0021 !
ASCII QUOTATION MARK 1.1 U+0022 "
ASCII NUMBER SIGN 1.1 U+0023 #
ASCII DOLLAR SIGN 1.1 U+0024 $
ASCII PERCENT SIGN 1.1 U+0025 %
ASCII AMPERSAND 1.1 U+0026 &
ASCII APOSTROPHE 1.1 U+0027 '
ASCII LEFT PARENTHESIS 1.1 U+0028 (
ASCII RIGHT PARENTHESIS 1.1 U+0029 )
ASCII ASTERISK 1.1 U+002A *
ASCII PLUS SIGN 1.1 U+002B +
ASCII COMMA 1.1 U+002C ,
ASCII HYPHEN-MINUS 1.1 U+002D -
ASCII FULL STOP 1.1 U+002E .
ASCII SOLIDUS 1.1 U+002F /
ASCII DIGIT ZERO 1.1 U+0030 0
ASCII DIGIT ONE 1.1 U+0031 1
ASCII DIGIT TWO 1.1 U+0032 2
ASCII DIGIT THREE 1.1 U+0033 3
ASCII DIGIT FOUR 1.1 U+0034 4
ASCII DIGIT FIVE 1.1 U+0035 5
ASCII DIGIT SIX 1.1 U+0036 6
ASCII DIGIT SEVEN 1.1 U+0037 7
ASCII DIGIT EIGHT 1.1 U+0038 8
ASCII DIGIT NINE 1.1 U+0039 9
ASCII COLON 1.1 U+003A :
ASCII SEMICOLON 1.1 U+003B ;
ASCII LESS-THAN SIGN 1.1 U+003C <
ASCII EQUALS SIGN 1.1 U+003D =
ASCII GREATER-THAN SIGN 1.1 U+003E >
ASCII QUESTION MARK 1.1 U+003F ?
ASCII COMMERCIAL AT 1.1 U+0040 @
ASCII LATIN CAPITAL LETTER A 1.1 U+0041 A
ASCII LATIN CAPITAL LETTER B 1.1 U+0042 B
ASCII LATIN CAPITAL LETTER C 1.1 U+0043 C
ASCII LATIN CAPITAL LETTER D 1.1 U+0044 D
ASCII LATIN CAPITAL LETTER E 1.1 U+0045 E
ASCII LATIN CAPITAL LETTER F 1.1 U+0046 F
ASCII LATIN CAPITAL LETTER G 1.1 U+0047 G
ASCII LATIN CAPITAL LETTER H 1.1 U+0048 H
ASCII LATIN CAPITAL LETTER I 1.1 U+0049 I
ASCII LATIN CAPITAL LETTER J 1.1 U+004A J
ASCII LATIN CAPITAL LETTER K 1.1 U+004B K
ASCII LATIN CAPITAL LETTER L 1.1 U+004C L
ASCII LATIN CAPITAL LETTER M 1.1 U+004D M
ASCII LATIN CAPITAL LETTER N 1.1 U+004E N
ASCII LATIN CAPITAL LETTER O 1.1 U+004F O
ASCII LATIN CAPITAL LETTER P 1.1 U+0050 P
ASCII LATIN CAPITAL LETTER Q 1.1 U+0051 Q
ASCII LATIN CAPITAL LETTER R 1.1 U+0052 R
ASCII LATIN CAPITAL LETTER S 1.1 U+0053 S
ASCII LATIN CAPITAL LETTER T 1.1 U+0054 T
ASCII LATIN CAPITAL LETTER U 1.1 U+0055 U
ASCII LATIN CAPITAL LETTER V 1.1 U+0056 V
ASCII LATIN CAPITAL LETTER W 1.1 U+0057 W
ASCII LATIN CAPITAL LETTER X 1.1 U+0058 X
ASCII LATIN CAPITAL LETTER Y 1.1 U+0059 Y
ASCII LATIN CAPITAL LETTER Z 1.1 U+005A Z
ASCII LEFT SQUARE BRACKET 1.1 U+005B [
ASCII REVERSE SOLIDUS 1.1 U+005C \
ASCII RIGHT SQUARE BRACKET 1.1 U+005D ]
ASCII CIRCUMFLEX ACCENT 1.1 U+005E ^
ASCII LOW LINE 1.1 U+005F _
ASCII GRAVE ACCENT 1.1 U+0060 `
ASCII LATIN SMALL LETTER A 1.1 U+0061 a
ASCII LATIN SMALL LETTER B 1.1 U+0062 b
ASCII LATIN SMALL LETTER C 1.1 U+0063 c
ASCII LATIN SMALL LETTER D 1.1 U+0064 d
ASCII LATIN SMALL LETTER E 1.1 U+0065 e
ASCII LATIN SMALL LETTER F 1.1 U+0066 f
ASCII LATIN SMALL LETTER G 1.1 U+0067 g
ASCII LATIN SMALL LETTER H 1.1 U+0068 h
ASCII LATIN SMALL LETTER I 1.1 U+0069 i
ASCII LATIN SMALL LETTER J 1.1 U+006A j
ASCII LATIN SMALL LETTER K 1.1 U+006B k
ASCII LATIN SMALL LETTER L 1.1 U+006C l
ASCII LATIN SMALL LETTER M 1.1 U+006D m
ASCII LATIN SMALL LETTER N 1.1 U+006E n
ASCII LATIN SMALL LETTER O 1.1 U+006F o
ASCII LATIN SMALL LETTER P 1.1 U+0070 p
ASCII LATIN SMALL LETTER Q 1.1 U+0071 q
ASCII LATIN SMALL LETTER R 1.1 U+0072 r
ASCII LATIN SMALL LETTER S 1.1 U+0073 s
ASCII LATIN SMALL LETTER T 1.1 U+0074 t
ASCII LATIN SMALL LETTER U 1.1 U+0075 u
ASCII LATIN SMALL LETTER V 1.1 U+0076 v
ASCII LATIN SMALL LETTER W 1.1 U+0077 w
ASCII LATIN SMALL LETTER X 1.1 U+0078 x
ASCII LATIN SMALL LETTER Y 1.1 U+0079 y
ASCII LATIN SMALL LETTER Z 1.1 U+007A z
ASCII LEFT CURLY BRACKET 1.1 U+007B {
ASCII VERTICAL LINE 1.1 U+007C
ASCII RIGHT CURLY BRACKET 1.1 U+007D }
ASCII TILDE 1.1 U+007E ~
CJK_Symbols IDEOGRAPHIC SPACE 1.1 U+3000  
CJK_Symbols IDEOGRAPHIC COMMA 1.1 U+3001
CJK_Symbols IDEOGRAPHIC FULL STOP 1.1 U+3002
CJK_Symbols LEFT CORNER BRACKET 1.1 U+300C
CJK_Symbols RIGHT CORNER BRACKET 1.1 U+300D
Hiragana HIRAGANA LETTER SMALL A 1.1 U+3041
Hiragana HIRAGANA LETTER A 1.1 U+3042
Hiragana HIRAGANA LETTER SMALL I 1.1 U+3043
Hiragana HIRAGANA LETTER I 1.1 U+3044
Hiragana HIRAGANA LETTER SMALL U 1.1 U+3045
Hiragana HIRAGANA LETTER U 1.1 U+3046
Hiragana HIRAGANA LETTER SMALL E 1.1 U+3047
Hiragana HIRAGANA LETTER E 1.1 U+3048
Hiragana HIRAGANA LETTER SMALL O 1.1 U+3049
Hiragana HIRAGANA LETTER O 1.1 U+304A
Hiragana HIRAGANA LETTER KA 1.1 U+304B
Hiragana HIRAGANA LETTER GA 1.1 U+304C
Hiragana HIRAGANA LETTER KI 1.1 U+304D
Hiragana HIRAGANA LETTER GI 1.1 U+304E
Hiragana HIRAGANA LETTER KU 1.1 U+304F
Hiragana HIRAGANA LETTER GU 1.1 U+3050
Hiragana HIRAGANA LETTER KE 1.1 U+3051
Hiragana HIRAGANA LETTER GE 1.1 U+3052
Hiragana HIRAGANA LETTER KO 1.1 U+3053
Hiragana HIRAGANA LETTER GO 1.1 U+3054
Hiragana HIRAGANA LETTER SA 1.1 U+3055
Hiragana HIRAGANA LETTER ZA 1.1 U+3056
Hiragana HIRAGANA LETTER SI 1.1 U+3057
Hiragana HIRAGANA LETTER ZI 1.1 U+3058
Hiragana HIRAGANA LETTER SU 1.1 U+3059
Hiragana HIRAGANA LETTER ZU 1.1 U+305A
Hiragana HIRAGANA LETTER SE 1.1 U+305B
Hiragana HIRAGANA LETTER ZE 1.1 U+305C
Hiragana HIRAGANA LETTER SO 1.1 U+305D
Hiragana HIRAGANA LETTER ZO 1.1 U+305E
Hiragana HIRAGANA LETTER TA 1.1 U+305F
Hiragana HIRAGANA LETTER DA 1.1 U+3060
Hiragana HIRAGANA LETTER TI 1.1 U+3061
Hiragana HIRAGANA LETTER DI 1.1 U+3062
Hiragana HIRAGANA LETTER SMALL TU 1.1 U+3063
Hiragana HIRAGANA LETTER TU 1.1 U+3064
Hiragana HIRAGANA LETTER DU 1.1 U+3065
Hiragana HIRAGANA LETTER TE 1.1 U+3066
Hiragana HIRAGANA LETTER DE 1.1 U+3067
Hiragana HIRAGANA LETTER TO 1.1 U+3068
Hiragana HIRAGANA LETTER DO 1.1 U+3069
Hiragana HIRAGANA LETTER NA 1.1 U+306A
Hiragana HIRAGANA LETTER NI 1.1 U+306B
Hiragana HIRAGANA LETTER NU 1.1 U+306C
Hiragana HIRAGANA LETTER NE 1.1 U+306D
Hiragana HIRAGANA LETTER NO 1.1 U+306E
Hiragana HIRAGANA LETTER HA 1.1 U+306F
Hiragana HIRAGANA LETTER BA 1.1 U+3070
Hiragana HIRAGANA LETTER PA 1.1 U+3071
Hiragana HIRAGANA LETTER HI 1.1 U+3072
Hiragana HIRAGANA LETTER BI 1.1 U+3073
Hiragana HIRAGANA LETTER PI 1.1 U+3074
Hiragana HIRAGANA LETTER HU 1.1 U+3075
Hiragana HIRAGANA LETTER BU 1.1 U+3076
Hiragana HIRAGANA LETTER PU 1.1 U+3077
Hiragana HIRAGANA LETTER HE 1.1 U+3078
Hiragana HIRAGANA LETTER BE 1.1 U+3079
Hiragana HIRAGANA LETTER PE 1.1 U+307A
Hiragana HIRAGANA LETTER HO 1.1 U+307B
Hiragana HIRAGANA LETTER BO 1.1 U+307C
Hiragana HIRAGANA LETTER PO 1.1 U+307D
Hiragana HIRAGANA LETTER MA 1.1 U+307E
Hiragana HIRAGANA LETTER MI 1.1 U+307F
Hiragana HIRAGANA LETTER MU 1.1 U+3080
Hiragana HIRAGANA LETTER ME 1.1 U+3081
Hiragana HIRAGANA LETTER MO 1.1 U+3082
Hiragana HIRAGANA LETTER SMALL YA 1.1 U+3083
Hiragana HIRAGANA LETTER YA 1.1 U+3084
Hiragana HIRAGANA LETTER SMALL YU 1.1 U+3085
Hiragana HIRAGANA LETTER YU 1.1 U+3086
Hiragana HIRAGANA LETTER SMALL YO 1.1 U+3087
Hiragana HIRAGANA LETTER YO 1.1 U+3088
Hiragana HIRAGANA LETTER RA 1.1 U+3089
Hiragana HIRAGANA LETTER RI 1.1 U+308A
Hiragana HIRAGANA LETTER RU 1.1 U+308B
Hiragana HIRAGANA LETTER RE 1.1 U+308C
Hiragana HIRAGANA LETTER RO 1.1 U+308D
Hiragana HIRAGANA LETTER SMALL WA 1.1 U+308E
Hiragana HIRAGANA LETTER WA 1.1 U+308F
Hiragana HIRAGANA LETTER WI 1.1 U+3090
Hiragana HIRAGANA LETTER WE 1.1 U+3091
Hiragana HIRAGANA LETTER WO 1.1 U+3092
Hiragana HIRAGANA LETTER N 1.1 U+3093
Hiragana HIRAGANA LETTER VU 1.1 U+3094
Hiragana HIRAGANA LETTER SMALL KA 3.2 U+3095
Hiragana HIRAGANA LETTER SMALL KE 3.2 U+3096
Hiragana COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK 1.1 U+3099
Hiragana COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK 1.1 U+309A
Hiragana KATAKANA-HIRAGANA VOICED SOUND MARK 1.1 U+309B
Hiragana KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK 1.1 U+309C
Hiragana HIRAGANA ITERATION MARK 1.1 U+309D
Hiragana HIRAGANA VOICED ITERATION MARK 1.1 U+309E
Hiragana HIRAGANA DIGRAPH YORI 3.2 U+309F
Katakana KATAKANA-HIRAGANA DOUBLE HYPHEN 3.2 U+30A0
Katakana KATAKANA LETTER SMALL A 1.1 U+30A1
Katakana KATAKANA LETTER A 1.1 U+30A2
Katakana KATAKANA LETTER SMALL I 1.1 U+30A3
Katakana KATAKANA LETTER I 1.1 U+30A4
Katakana KATAKANA LETTER SMALL U 1.1 U+30A5
Katakana KATAKANA LETTER U 1.1 U+30A6
Katakana KATAKANA LETTER SMALL E 1.1 U+30A7
Katakana KATAKANA LETTER E 1.1 U+30A8
Katakana KATAKANA LETTER SMALL O 1.1 U+30A9
Katakana KATAKANA LETTER O 1.1 U+30AA
Katakana KATAKANA LETTER KA 1.1 U+30AB
Katakana KATAKANA LETTER GA 1.1 U+30AC
Katakana KATAKANA LETTER KI 1.1 U+30AD
Katakana KATAKANA LETTER GI 1.1 U+30AE
Katakana KATAKANA LETTER KU 1.1 U+30AF
Katakana KATAKANA LETTER GU 1.1 U+30B0
Katakana KATAKANA LETTER KE 1.1 U+30B1
Katakana KATAKANA LETTER GE 1.1 U+30B2
Katakana KATAKANA LETTER KO 1.1 U+30B3
Katakana KATAKANA LETTER GO 1.1 U+30B4
Katakana KATAKANA LETTER SA 1.1 U+30B5
Katakana KATAKANA LETTER ZA 1.1 U+30B6
Katakana KATAKANA LETTER SI 1.1 U+30B7
Katakana KATAKANA LETTER ZI 1.1 U+30B8
Katakana KATAKANA LETTER SU 1.1 U+30B9
Katakana KATAKANA LETTER ZU 1.1 U+30BA
Katakana KATAKANA LETTER SE 1.1 U+30BB
Katakana KATAKANA LETTER ZE 1.1 U+30BC
Katakana KATAKANA LETTER SO 1.1 U+30BD
Katakana KATAKANA LETTER ZO 1.1 U+30BE
Katakana KATAKANA LETTER TA 1.1 U+30BF
Katakana KATAKANA LETTER DA 1.1 U+30C0
Katakana KATAKANA LETTER TI 1.1 U+30C1
Katakana KATAKANA LETTER DI 1.1 U+30C2
Katakana KATAKANA LETTER SMALL TU 1.1 U+30C3
Katakana KATAKANA LETTER TU 1.1 U+30C4
Katakana KATAKANA LETTER DU 1.1 U+30C5
Katakana KATAKANA LETTER TE 1.1 U+30C6
Katakana KATAKANA LETTER DE 1.1 U+30C7
Katakana KATAKANA LETTER TO 1.1 U+30C8
Katakana KATAKANA LETTER DO 1.1 U+30C9
Katakana KATAKANA LETTER NA 1.1 U+30CA
Katakana KATAKANA LETTER NI 1.1 U+30CB
Katakana KATAKANA LETTER NU 1.1 U+30CC
Katakana KATAKANA LETTER NE 1.1 U+30CD
Katakana KATAKANA LETTER NO 1.1 U+30CE
Katakana KATAKANA LETTER HA 1.1 U+30CF
Katakana KATAKANA LETTER BA 1.1 U+30D0
Katakana KATAKANA LETTER PA 1.1 U+30D1
Katakana KATAKANA LETTER HI 1.1 U+30D2
Katakana KATAKANA LETTER BI 1.1 U+30D3
Katakana KATAKANA LETTER PI 1.1 U+30D4
Katakana KATAKANA LETTER HU 1.1 U+30D5
Katakana KATAKANA LETTER BU 1.1 U+30D6
Katakana KATAKANA LETTER PU 1.1 U+30D7
Katakana KATAKANA LETTER HE 1.1 U+30D8
Katakana KATAKANA LETTER BE 1.1 U+30D9
Katakana KATAKANA LETTER PE 1.1 U+30DA
Katakana KATAKANA LETTER HO 1.1 U+30DB
Katakana KATAKANA LETTER BO 1.1 U+30DC
Katakana KATAKANA LETTER PO 1.1 U+30DD
Katakana KATAKANA LETTER MA 1.1 U+30DE
Katakana KATAKANA LETTER MI 1.1 U+30DF
Katakana KATAKANA LETTER MU 1.1 U+30E0
Katakana KATAKANA LETTER ME 1.1 U+30E1
Katakana KATAKANA LETTER MO 1.1 U+30E2
Katakana KATAKANA LETTER SMALL YA 1.1 U+30E3
Katakana KATAKANA LETTER YA 1.1 U+30E4
Katakana KATAKANA LETTER SMALL YU 1.1 U+30E5
Katakana KATAKANA LETTER YU 1.1 U+30E6
Katakana KATAKANA LETTER SMALL YO 1.1 U+30E7
Katakana KATAKANA LETTER YO 1.1 U+30E8
Katakana KATAKANA LETTER RA 1.1 U+30E9
Katakana KATAKANA LETTER RI 1.1 U+30EA
Katakana KATAKANA LETTER RU 1.1 U+30EB
Katakana KATAKANA LETTER RE 1.1 U+30EC
Katakana KATAKANA LETTER RO 1.1 U+30ED
Katakana KATAKANA LETTER SMALL WA 1.1 U+30EE
Katakana KATAKANA LETTER WA 1.1 U+30EF
Katakana KATAKANA LETTER WI 1.1 U+30F0
Katakana KATAKANA LETTER WE 1.1 U+30F1
Katakana KATAKANA LETTER WO 1.1 U+30F2
Katakana KATAKANA LETTER N 1.1 U+30F3
Katakana KATAKANA LETTER VU 1.1 U+30F4
Katakana KATAKANA LETTER SMALL KA 1.1 U+30F5
Katakana KATAKANA LETTER SMALL KE 1.1 U+30F6
Katakana KATAKANA LETTER VA 1.1 U+30F7
Katakana KATAKANA LETTER VI 1.1 U+30F8
Katakana KATAKANA LETTER VE 1.1 U+30F9
Katakana KATAKANA LETTER VO 1.1 U+30FA
Katakana KATAKANA MIDDLE DOT 1.1 U+30FB
Katakana KATAKANA-HIRAGANA PROLONGED SOUND MARK 1.1 U+30FC
Katakana KATAKANA ITERATION MARK 1.1 U+30FD
Katakana KATAKANA VOICED ITERATION MARK 1.1 U+30FE
Katakana KATAKANA DIGRAPH KOTO 3.2 U+30FF
Katakana_Ext KATAKANA LETTER SMALL KU 3.2 U+31F0
Katakana_Ext KATAKANA LETTER SMALL SI 3.2 U+31F1
Katakana_Ext KATAKANA LETTER SMALL SU 3.2 U+31F2
Katakana_Ext KATAKANA LETTER SMALL TO 3.2 U+31F3
Katakana_Ext KATAKANA LETTER SMALL NU 3.2 U+31F4
Katakana_Ext KATAKANA LETTER SMALL HA 3.2 U+31F5
Katakana_Ext KATAKANA LETTER SMALL HI 3.2 U+31F6
Katakana_Ext KATAKANA LETTER SMALL HU 3.2 U+31F7
Katakana_Ext KATAKANA LETTER SMALL HE 3.2 U+31F8
Katakana_Ext KATAKANA LETTER SMALL HO 3.2 U+31F9
Katakana_Ext KATAKANA LETTER SMALL MU 3.2 U+31FA
Katakana_Ext KATAKANA LETTER SMALL RA 3.2 U+31FB
Katakana_Ext KATAKANA LETTER SMALL RI 3.2 U+31FC
Katakana_Ext KATAKANA LETTER SMALL RU 3.2 U+31FD
Katakana_Ext KATAKANA LETTER SMALL RE 3.2 U+31FE
Katakana_Ext KATAKANA LETTER SMALL RO 3.2 U+31FF
Half_And_Full_Forms FULLWIDTH EXCLAMATION MARK 1.1 U+FF01
Half_And_Full_Forms FULLWIDTH QUOTATION MARK 1.1 U+FF02
Half_And_Full_Forms FULLWIDTH NUMBER SIGN 1.1 U+FF03
Half_And_Full_Forms FULLWIDTH DOLLAR SIGN 1.1 U+FF04
Half_And_Full_Forms FULLWIDTH PERCENT SIGN 1.1 U+FF05
Half_And_Full_Forms FULLWIDTH AMPERSAND 1.1 U+FF06
Half_And_Full_Forms FULLWIDTH APOSTROPHE 1.1 U+FF07
Half_And_Full_Forms FULLWIDTH LEFT PARENTHESIS 1.1 U+FF08
Half_And_Full_Forms FULLWIDTH RIGHT PARENTHESIS 1.1 U+FF09
Half_And_Full_Forms FULLWIDTH ASTERISK 1.1 U+FF0A
Half_And_Full_Forms FULLWIDTH PLUS SIGN 1.1 U+FF0B
Half_And_Full_Forms FULLWIDTH COMMA 1.1 U+FF0C
Half_And_Full_Forms FULLWIDTH HYPHEN-MINUS 1.1 U+FF0D
Half_And_Full_Forms FULLWIDTH FULL STOP 1.1 U+FF0E
Half_And_Full_Forms FULLWIDTH SOLIDUS 1.1 U+FF0F
Half_And_Full_Forms FULLWIDTH DIGIT ZERO 1.1 U+FF10
Half_And_Full_Forms FULLWIDTH DIGIT ONE 1.1 U+FF11
Half_And_Full_Forms FULLWIDTH DIGIT TWO 1.1 U+FF12
Half_And_Full_Forms FULLWIDTH DIGIT THREE 1.1 U+FF13
Half_And_Full_Forms FULLWIDTH DIGIT FOUR 1.1 U+FF14
Half_And_Full_Forms FULLWIDTH DIGIT FIVE 1.1 U+FF15
Half_And_Full_Forms FULLWIDTH DIGIT SIX 1.1 U+FF16
Half_And_Full_Forms FULLWIDTH DIGIT SEVEN 1.1 U+FF17
Half_And_Full_Forms FULLWIDTH DIGIT EIGHT 1.1 U+FF18
Half_And_Full_Forms FULLWIDTH DIGIT NINE 1.1 U+FF19
Half_And_Full_Forms FULLWIDTH COLON 1.1 U+FF1A
Half_And_Full_Forms FULLWIDTH SEMICOLON 1.1 U+FF1B
Half_And_Full_Forms FULLWIDTH LESS-THAN SIGN 1.1 U+FF1C
Half_And_Full_Forms FULLWIDTH EQUALS SIGN 1.1 U+FF1D
Half_And_Full_Forms FULLWIDTH GREATER-THAN SIGN 1.1 U+FF1E
Half_And_Full_Forms FULLWIDTH QUESTION MARK 1.1 U+FF1F
Half_And_Full_Forms FULLWIDTH COMMERCIAL AT 1.1 U+FF20
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER A 1.1 U+FF21
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER B 1.1 U+FF22
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER C 1.1 U+FF23
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER D 1.1 U+FF24
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER E 1.1 U+FF25
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER F 1.1 U+FF26
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER G 1.1 U+FF27
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER H 1.1 U+FF28
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER I 1.1 U+FF29
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER J 1.1 U+FF2A
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER K 1.1 U+FF2B
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER L 1.1 U+FF2C
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER M 1.1 U+FF2D
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER N 1.1 U+FF2E
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER O 1.1 U+FF2F
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER P 1.1 U+FF30
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER Q 1.1 U+FF31
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER R 1.1 U+FF32
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER S 1.1 U+FF33
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER T 1.1 U+FF34
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER U 1.1 U+FF35
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER V 1.1 U+FF36
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER W 1.1 U+FF37
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER X 1.1 U+FF38
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER Y 1.1 U+FF39
Half_And_Full_Forms FULLWIDTH LATIN CAPITAL LETTER Z 1.1 U+FF3A
Half_And_Full_Forms FULLWIDTH LEFT SQUARE BRACKET 1.1 U+FF3B
Half_And_Full_Forms FULLWIDTH REVERSE SOLIDUS 1.1 U+FF3C
Half_And_Full_Forms FULLWIDTH RIGHT SQUARE BRACKET 1.1 U+FF3D
Half_And_Full_Forms FULLWIDTH CIRCUMFLEX ACCENT 1.1 U+FF3E
Half_And_Full_Forms FULLWIDTH LOW LINE 1.1 U+FF3F _
Half_And_Full_Forms FULLWIDTH GRAVE ACCENT 1.1 U+FF40
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER A 1.1 U+FF41
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER B 1.1 U+FF42
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER C 1.1 U+FF43
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER D 1.1 U+FF44
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER E 1.1 U+FF45
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER F 1.1 U+FF46
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER G 1.1 U+FF47
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER H 1.1 U+FF48
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER I 1.1 U+FF49
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER J 1.1 U+FF4A
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER K 1.1 U+FF4B
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER L 1.1 U+FF4C
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER M 1.1 U+FF4D
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER N 1.1 U+FF4E
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER O 1.1 U+FF4F
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER P 1.1 U+FF50
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER Q 1.1 U+FF51
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER R 1.1 U+FF52
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER S 1.1 U+FF53
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER T 1.1 U+FF54
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER U 1.1 U+FF55
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER V 1.1 U+FF56
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER W 1.1 U+FF57
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER X 1.1 U+FF58
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER Y 1.1 U+FF59
Half_And_Full_Forms FULLWIDTH LATIN SMALL LETTER Z 1.1 U+FF5A
Half_And_Full_Forms FULLWIDTH LEFT CURLY BRACKET 1.1 U+FF5B
Half_And_Full_Forms FULLWIDTH VERTICAL LINE 1.1 U+FF5C
Half_And_Full_Forms FULLWIDTH RIGHT CURLY BRACKET 1.1 U+FF5D
Half_And_Full_Forms FULLWIDTH TILDE 1.1 U+FF5E
Half_And_Full_Forms HALFWIDTH IDEOGRAPHIC FULL STOP 1.1 U+FF61
Half_And_Full_Forms HALFWIDTH LEFT CORNER BRACKET 1.1 U+FF62
Half_And_Full_Forms HALFWIDTH RIGHT CORNER BRACKET 1.1 U+FF63
Half_And_Full_Forms HALFWIDTH IDEOGRAPHIC COMMA 1.1 U+FF64
Half_And_Full_Forms HALFWIDTH KATAKANA MIDDLE DOT 1.1 U+FF65
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER WO 1.1 U+FF66
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL A 1.1 U+FF67
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL I 1.1 U+FF68
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL U 1.1 U+FF69
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL E 1.1 U+FF6A
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL O 1.1 U+FF6B
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL YA 1.1 U+FF6C
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL YU 1.1 U+FF6D
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL YO 1.1 U+FF6E
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SMALL TU 1.1 U+FF6F
Half_And_Full_Forms HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK 1.1 U+FF70
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER A 1.1 U+FF71
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER I 1.1 U+FF72
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER U 1.1 U+FF73
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER E 1.1 U+FF74
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER O 1.1 U+FF75
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER KA 1.1 U+FF76
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER KI 1.1 U+FF77
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER KU 1.1 U+FF78
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER KE 1.1 U+FF79
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER KO 1.1 U+FF7A
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SA 1.1 U+FF7B
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SI 1.1 U+FF7C
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SU 1.1 U+FF7D
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SE 1.1 U+FF7E
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER SO 1.1 U+FF7F ソ
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER TA 1.1 U+FF80
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER TI 1.1 U+FF81
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER TU 1.1 U+FF82
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER TE 1.1 U+FF83
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER TO 1.1 U+FF84
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER NA 1.1 U+FF85
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER NI 1.1 U+FF86
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER NU 1.1 U+FF87
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER NE 1.1 U+FF88
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER NO 1.1 U+FF89
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER HA 1.1 U+FF8A
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER HI 1.1 U+FF8B
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER HU 1.1 U+FF8C
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER HE 1.1 U+FF8D
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER HO 1.1 U+FF8E
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER MA 1.1 U+FF8F
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER MI 1.1 U+FF90
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER MU 1.1 U+FF91
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER ME 1.1 U+FF92
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER MO 1.1 U+FF93
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER YA 1.1 U+FF94
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER YU 1.1 U+FF95
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER YO 1.1 U+FF96
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER RA 1.1 U+FF97
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER RI 1.1 U+FF98
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER RU 1.1 U+FF99
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER RE 1.1 U+FF9A
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER RO 1.1 U+FF9B
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER WA 1.1 U+FF9C
Half_And_Full_Forms HALFWIDTH KATAKANA LETTER N 1.1 U+FF9D
Half_And_Full_Forms HALFWIDTH KATAKANA VOICED SOUND MARK 1.1 U+FF9E
Half_And_Full_Forms HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK 1.1 U+FF9F

おわりに

Unicode Normalization Form って使いづらいなぁと思って、ネットで調べてみても、この仕様に否定的な記事はあまり見つかりませんでした。

みなさん、norm や width をそのまま使ってるんですかね?
それとも、特に難しくもないからみんな独自にライブラリを作ってるのかな?
あ、そもそもノーマライズとかしてないんでしょうか?

それが気になってます。誰か教えてください(笑)

また、Golang を触り始めてからまだ日が浅いので、ライブラリの実装に抜けがあるかもしれません。

  • Go らしくない部分
  • 非効率な箇所
  • バグ

などを見つけたら教えてもらえると嬉しいです。

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golandでdelve古くてデバッガ動かねえよって叱られた時の対処

うっすい内容ですがvscodeの情報は結構あったけどGolandのは発見出来なかったので一応

タイトル通り、GolandでDebug実行しようとすると

Version of Delve is too old for this version of Go ...

こんなんが出て叱られるときの対処です。
Golandはこの記事書いた時点で1番新しいです

単純にgo-delve自前でビルドしてプラグイン差し替えれば良いです。
mainはcmd/dlv/配下にあるので、git cloneかましたらディレクトリ移動してgo build
念のためもともとあったdlvバイナリはdlv.bkとかにしておき、
できあがったバイナリを[[your path]]/[[Goland本体ディレクトリ]]/plugins/go/lib/dlv/linux/下に放り込めばok

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

書籍「Go言語でつくるインタプリタ」の進行で躓いたところ

はじめに

書籍「Go言語でつくるインタプリタ」を進めていたところ、序盤(1.3節)から躓いたので解決方法をメモしておこうと思います。
なお、go言語の開発で本来とるべき構成などとは異なっている可能性がありますが、あくまでこの書籍をすすめるための対処となりますのでご了承ください。

1章3節でビルド結果が違う

1.3節をすすめていると、テストコードのビルドの際に紙面では「New関数が定義されていないのでエラーが発生する」、という段取りのはずが

想定
# monkey/lexer [monkey/lexer.test]
lexer\lexer_test.go:28:7: undefined: New
FAIL    monkey/lexer [build failed]
FAIL

テストコードの中で指定している「パッケージ"monkey/token"が見つからない」と出たところで異常に気づきました。

実際
lexer\lexer_test.go:6:2: cannot find package "monkey/token" in any of:
        c:\go\src\monkey\token (from $GOROOT)
        C:\Users\Hoge\go\src\monkey\token (from $GOPATH)
FAIL    _/C_/Users/Hoge/source/repos/monkey/lexer [setup failed]
FAIL

そもそもgoのパスを通していなかった。

自作したモジュールを使用してコーディングを進めていくので、そのモジュールまでのパスが通っていなければいけないはずですが、golangのインストールの際に設定される環境変数GOPATHをそのままにしていました。

パスに\srcが付け足される。

エラーメッセージの展開後パスから、プロジェクトのソースコードはsrcフォルダの下にあることが想定されていることがわかりました。(パスとモジュール指定の間に\srcが挿入されて展開されるため、見つからない、というエラーになる)
多分goのリファレンスからきちんと入っていればこのようなことは起こらなかったのでしょうが、当のインタプリタ書籍から入ったので全くわかりませんでした。

対処

結論として、以下のようにフォルダ構成と環境変数を設定することで書籍の内容進行に対応しました。

フォルダ構造

以下のような構造に修正しました。

\Users\UserName\source\repos\
monkey\
  + src\
    + monkey\      (<- 「go test位置」)
      + lexer\
      | + lexer_test.go
      | + (lexer.go)
      | 
      + token\
        + token.go

(筆者はVisual Studioのプロジェクトフォルダの生成場所の法則によせて、\Users\UserName\source\repos\フォルダの下に各プロジェクトのフォルダを作成しています。)

環境変数

環境変数のGOPATHに以下を追加しました。

%USERPROFILE%\source\repos\monkey  # USERPROFILEは \Users\Hogeに展開される

go test(テスト実行)コマンドを通すには

書籍どおりのテストコード実行を行うにはmonkey\プロジェクトフォルダのsrc\monkey\にフォルダ移動してからになります。
(上図のフォルダ構成の 「go test位置」)

想定通り
C:\Users\Hoge\source\repos\monkey\src\monkey>go test ./lexer
# monkey/lexer [monkey/lexer.test]
lexer\lexer_test.go:28:7: undefined: New
FAIL    monkey/lexer [build failed]
FAIL

まとめ

以下の様に対処して、「Go言語でつくるインタプリタ」1.3節をいなしました。

  • フォルダ構成の見直し
  • 環境変数の追加
  • go test実行時のフォルダ位置

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】コレクションを導入したら幸せになったので、わかりやすく説明してみる

はじめに

こんにちは。むらってぃーです。
チームで開発しているプロダクトのコードがそれなりに大きくなってきました。

メソッドによっては「割と冗長になってきたね」という話が出てきました。
その部分のほとんどが配列操作の部分だったので、コレクションを使ってリファクタリングを行ったのがこの記事のきっかけです。

エンティティのスライス操作のロジックをコレクションに閉じ込めると、可読性が上がったりテストしやすくなったりで幸せになれたので紹介します。

実装例を交えて紹介

今回は、bookというエンティティと、それを扱うリポジトリ、サービスをコンポーネントとして扱います。

今回扱うコンポーネント

エンティティ

bookというエンティティはID、タイトル、著者の3つの値を持ちます。

book.go
package entity

type Book struct {
    ID uint64
    Title string
    Author string
}

func NewBook(id uint64, title, author string) *Book {
    return &Book{
        ID:     id,
        Title:  title,
        Author: author,
    }
}

リポジトリ

bookエンティティをDBに入れたり取り出したりします。
今回はDB操作にxormというORMを使います。

book_repository.go
package gateway

import (
    "github.com/go-xorm/xorm"
    "go-collection-sample/internal/entity"

)

// bookをDBに入れたり取り出したりするリポジトリのインターフェース
type IBookRepository interface {
    FindByAuthor(author string) ([]*entity.Book, error)
}

type BookRepository struct {
    engine *xorm.Engine
}

func NewBookRepository(engine *xorm.Engine) *BookRepository {
    return &BookRepository{
        engine: engine,
    }
}

type bookData struct {
    Id uint64
    Title string `xorm:"varchar(100)"`
    Author string `xorm:"varchar(50)"`
}

// 著者名でbookを複数件取得
func (br *BookRepository) FindByAuthor(author string) ([]*entity.Book, error) {
    // DBからAuthorを取ってくる
    var results []bookData
    if err := br.engine.Where("author = ?", author).Find(&results); err != nil {
        return []*entity.Book{}, err
    }

    // エンティティのスライスに詰めて返す(重要)
    var books []*entity.Book
    for _, result := range results {
        book := entity.NewBook(result.Id, result.Title, result.Author)
        books = append(books, book)
    }

    return books, nil
}

サービス

ビジネスロジックにあたる部分です。
ここが今回の本題です。

book_service.go
package service

import (
    "go-collection-sample/internal/entity"
    "go-collection-sample/internal/gateway"
)

type BookService struct {
    bookRepository gateway.IBookRepository
}

func NewBookService(repository gateway.IBookRepository) *BookService {
    return &BookService{
        bookRepository: repository,
    }
}

DB操作にリポジトリを使います。

このままでも問題ないパターン

book_service.goに「著者名で本を検索する」というメソッドがあったら、その実装はどうなるでしょうか。

book_service.go
// 著者名から本を検索して返す
func (b *BookService) SearchBooksByAuthor(author string) ([]*entity.Book, error) {
    // DBからBookをまとめて取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []*entity.Book{}, err
    }

    return books, nil
}

DBからbookをまとめて取ってきて、エンティティを返すだけなので非常にシンプルなロジックになりますね。

問題の実装

では、「著者名で本を検索し、そのID配列を取得する」というメソッドがあったら、その実装はどうなるでしょうか。
そのまま書くと下記の形になるのではないでしょうか。

book_service.go
// 著者名からbookを検索し、それらのIDをスライスで返す
func (b *BookService) SearchBookIDsByAuthor(author string) ([]uint64, error) {
    // DBからBookをまとめて取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []uint64{}, err
    }

    // IDのリストに詰め直す(重要)
    var ids []uint64
    for _, book := range books {
        ids = append(ids, book.ID)
    }

    return ids, nil
}

for文でIDを取り出し、IDに詰め直す記述があることがわかります。

ではここで、「著者名で本を検索し、それらの本をお気に入りに登録しているユーザーを一括取得したい」といったメソッドを生やしたい場合はどうでしょうか。

この場合、上記と同じ「IDのリストに詰め直す」処理を再度書かなければなりません。

そこでコレクションの登場です。

コレクションを導入する

コレクションの定義は、

プログラミングの分野で、データやオブジェクトなどをまとめて格納するためのデータ構造やクラスなどの総称をコレクションということがある。

といった感じです。
参考ページ: IT用語辞典

今回は下記のコレクションを用意します。

books.go
package collection

import "go-collection-sample/internal/entity"

// bookエンティティを束ねておくコレクション
type Books struct {
    books []*entity.Book
}

func NewBooks() *Books {
    return &Books{
        books: []*entity.Book{},
    }
}

// コレクションにbookを1つ追加
func (b *Books) Add(book *entity.Book) {
    b.books = append(b.books, book)
}

// コレクションに束ねているbookのIDをスライスで取得
func (b *Books) IDs() []uint64 {
    var ids []uint64
    for _, book := range b.books {
        ids = append(ids, book.ID)
    }
    return ids
}

で、repositoryを下記のように書き換えます。

book_repository.go
package gateway

import (
    "github.com/go-xorm/xorm"
    "go-collection-sample/internal/collection"
    "go-collection-sample/internal/entity"

)

type IBookRepository interface {
    FindByAuthor(author string) (*collection.Books, error)
}

type BookRepository struct {
    engine *xorm.Engine
}

func NewBookRepository(engine *xorm.Engine) *BookRepository {
    return &BookRepository{
        engine: engine,
    }
}

type bookData struct {
    Id uint64
    Title string `xorm:"varchar(100)"`
    Author string `xorm:"varchar(50)"`
}

// 著者名で検索し、ヒットしたbookエンティティのコレクションを返す
func (br *BookRepository) FindByAuthor(author string) (*collection.Books, error) {
    // DBからAuthorを取ってくる
    var results []bookData
    if err := br.engine.Where("author = ?", author).Find(&results); err != nil {
        return collection.NewBooks(), err
    }

    // bookをコレクションに詰めて返す(変更後)
    books := collection.NewBooks()
    for _, result := range results {
        book := entity.NewBook(result.Id, result.Title, result.Author)
        books.Add(book)
    }

    return books, nil
}

すると、先ほどのメソッドは下記のように書き換えることができます。

book_service.go
func (b *BookService) SearchBookIDsByAuthor(author string) ([]uint64, error) {
    // DBからbookのコレクションを取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []uint64{}, err
    }

    // コレクションにIDスライス取得の処理を任せる
    return books.IDs(), nil
}

非常にシンプルになったのではないでしょうか。
記述量を減らせただけでなく、コレクションの導入によって「複数のエンティティを束ねる責務」を他のオブジェクトに移譲できています。
よって、SearchBookIDsByAuthor メソッドの中もサッと読んで何をしているのかを理解しやすくなっているのではないでしょうか。

サービス側でエンティティのスライスを取り回す場合と、コレクションを取り回す場合を再度比較してみましょうk。

エンティティのスライスを取り回す場合

book_service.go
// 著者名からbookを検索し、それらのIDをスライスで返す
func (b *BookService) SearchBookIDsByAuthor(author string) ([]uint64, error) {
    // DBからBookをまとめて取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []uint64{}, err
    }

    // IDのリストに詰め直す(重要)
    var ids []uint64
    for _, book := range books {
        ids = append(ids, book.ID)
    }

    return ids, nil
}

コレクションを取り回す場合

book_service.go
func (b *BookService) SearchBookIDsByAuthor(author string) ([]uint64, error) {
    // DBからbookのコレクションを取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []uint64{}, err
    }

    // コレクションにIDスライス取得の処理を任せる
    return books.IDs(), nil
}

他のケース

他にも、「サービス内でソートを行いたいとき」にも役立ちます。
RDBだとDBから取ってくる際にソートをかけることができますが、CSVファイルからデータを取得したり、NoSQLを使う場合だとアプリケーション側でソートすることがあります。

「著者名で検索し、タイトル名の昇順でbooksのエンティティ配列を返す」というメソッドを例にしてみます。

こちらもサービス側でエンティティのスライスを取り回す場合と、コレクションを取り回す場合を比較してみましょう。

エンティティのスライスを取り回す場合

book_service.go
// 著者名で検索し、タイトル名の昇順で返す
func (b *BookService) SearchBooksByAuthorOrderByTitleAsc(author string) ([]*entity.Book, error) {
    // DBからBookをまとめて取ってくる
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []*entity.Book, err
    }

    // Titleの昇順にソート
    sort.Slice(books, func(i, j int) bool {
        return books[i].Title < books[j].Title
    })

    // エンティティの配列を返す
    return books, nil
}

「Titleの昇順にソート」というコメントを書かなかった場合を考えてみましょう。
このコメントがなければ、一度ソートをしている部分で立ち止まって、何をしているか解読するための時間が必要になるのではないでしょうか。

コレクションを取り回す場合

コレクションに下記を追加。

books.go
// タイトルの昇順にソートする
func (b *Books) SortAscByTitle() {
    sort.Slice(b.books, func(i, j int) bool {
        return b.books[i].Title < b.books[j].Title
    })
}

// スライスに変換する
func (b *Books) Slice() []*entity.Book {
    var eBooks []*entity.Book
    for _, book := range b.books {
        eBooks = append(eBooks, book)
    }
    return eBooks
}

サービスを下記のように書き換えます。

book_service.go
// 著者名で検索し、タイトルの昇順で返す
func (b *BookService) SearchBooksByAuthorOrderByTitleAsc(author string) ([]*entity.Book, error) {
    books, err := b.bookRepository.FindByAuthor(author)
    if err != nil {
        return []*entity.Book, err
    }

    books.SortAscByTitle()

    return books.Slice(), nil
}

あえてメソッドの中にコメントを書かずにコードを書いてみました。
コメントがなくとも、「何をしているか」は一目瞭然なのではないでしょうか。

ちなみに各処理を解説すると、

  1. リポジトリからbooksのコレクションを取ってくる
  2. booksをタイトルの昇順にソート
  3. booksをスライスに変換して返す

となります。

その他のコレクションメソッド

下記のような、スライスを扱う操作を閉じ込めることができます。

books.go
// 空である
func (b *Books)IsEmpty() bool {
    if len(b.books) == 0 {
        return true
    }
    return false
}

// 順番をシャッフルする
func (b *Books) Shuffle() {
    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(b.books), func(i, j int) {
        b.books[i], b.books[j] = b.books[j], b.books[i]
    })
}

// 指定したインデックスの要素を削除する
func (b *Books) Delete(index int) {
    var result []*entity.Book
    for i, book := range b.books{
        if i == index {
            continue
        }
        result = append(result, book)
    }
    b.books = result
}

コレクションを使う利点

私としては下記の利点を感じました。

  • スライスを扱う汎用的な操作を共通のメソッドに切り出すことができる
  • スライスを扱う処理をメソッドに切り出すことで、メソッドを呼び出す部分に意味を持たせることができる
  • スライスのソートといった複雑な処理をテストしやすい

最後に

今回はコレクションを導入することで得られる恩恵と、コレクションの使用例について紹介しました。
コレクションを使う分構造体は増えますし、好みはあるかもしれません。
しかしそれによって受けられる恩恵もまた大きいのではないでしょうか。

もしスライス操作のロジックが散らばって可読性が低くなっている場合は、参考にしてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Emacs で Go の開発パッケージを入れられない Not found, Failed to verify signature の解決方法 (Ubuntu 18.04)

タイトルが長い。

emacsで package-install が上手くできなかった方向けです。
以下の参考のブログの、「必要な go パッケージの導入」まで行っている事が前提です。

参考

https://qiita.com/bussorenre/items/3e80e59d517db8aebf46

Emacs のバージョン確認

emacs --version

バージョンが26以上の方は次の作業をする必要はありません。

25以下の方はバージョンアップする必要があります。
(解る方は、鍵を取得して更新する方法もあります)

Emacs のバージョンアップ

Emacs の package-install ではバージョンが古いと署名鍵が一致しない場合があるようです。
この場合、パッケージを管理しているレポジトリに接続できません。

新しいバージョンのEmacsを使うことでこの問題を解決できます。

sudo add-apt-repository ppa:kelleyk/emacs
sudo apt update
sudo apt install emacs26

emacsの最新版インストールしましょう。
現在27まであるようなので、お好みで。

sudo apt remove --autoremove emacs25*

古いEmacsを削除します。
emacs25*に指定するバージョンは、一番最初に確認した値に合わせて変更してください。

Init.elをいじる

init.elを書き換えている方は、そのファイルを変更します。
init.el自体書いた覚えのない人は、以下に新たにファイルを作ります。

cd ~/.emacs.d/
vim init.el

先頭に以下の文を追加します。
既に書かれていた場合は、この作業は不要です。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

今回導入するパッケージは、melpaと呼ばれるリポジトリに登録されています。
デフォルトでは接続しないため、接続先として設定してあげる必要がありました。

package-install

パッケージリストを更新しておきましょう。
Emacsを起動して以下を実行します。

M-x package-refresh-contents

M-xが何かわからない方は、取り合えず Alt+xを押すと良いと思います。
具体的には、以下の手順を踏みます。

  • emacsを起動する
  • Alt+x(M-x)を押す
  • 下の方にM-xと出るので、package-refresh-contentsと入力してEnter

続いて以下のコマンドを打ちます

M-x package-install flycheck
M-x package-install company-go
  • Alt+x(M-x)を押す
  • 下の方にM-xと出るので、package-installと入力してEnter
  • Install package:と出るので、flycheckと入力してEnter
  • company-goについても同様

最後に

先ほどinit.elを作成した場所と同じところにgolang-conf.elという名前のファイルを作成します。
内容は、参考に書いてあるサイトを参照してください。

お疲れ様でした!
上手く設定できていれば、Goのファイルを開くとシンタックスハイライトがついているはずです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで実装する並列処理(Hello, world!を10000回出力してみる)

"Hello, world!" が100行書き込まれた write1.txtwrite100.txt という名前の100個のテキストファイルを作成する場合、並列の有無で実行時間が変わるのか調べました。

Goのバージョン: go1.15.2 windows/amd64
PCの環境: Windows 10 Pro / Intel(R) Core(TM) i5-6300U CPU @2.40GHz (コア数:2, プロセッサ数:4)
プログラムはテキストエディタAtom上で動かしており、下記のコードからは時間計測用の部分を除いています。下記の実行時間はコンパイルの時間を含みません。

・並列せずに実行する

package main
import ("os"; "strconv")

func writeByres(num int) error {
    filename := "write" + strconv.Itoa(num) + ".txt"
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    b := []byte("Hello, world!\n")
    for i := 0; i < 100; i++ {
        _, err2 := file.Write(b)
        if err2 != nil {
            return err2
        }
    }
    return nil
}

func main() {
    i := 1
    for i <= 100 {
        writeByres(i)
        i++
    }
}

100回の平均実行時間は 0.50095418 秒でした。

・Goroutineで並列する

4並列に制限した場合の例
package main
import ("fmt"; "sync"; "os"; "strconv"; "io/ioutil")

func writeByres(num int, wg *sync.WaitGroup) error {
    filename := "write" + strconv.Itoa(num) + ".txt"
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    b := []byte("Hello, world!\n")
    for i := 0; i < 100; i++ {
        _, err2 := file.Write(b)
        if err2 != nil {
            return err2
        }
    }
    return nil
}

func main() {
    concurrency := 4
    limit := make(chan int, concurrency)
    var wg sync.WaitGroup
    i := 1
    for i <= 100 {
        wg.Add(1)
        go func(i int, wg *sync.WaitGroup, limit chan int) {
            defer wg.Done()
            limit <- 1
            writeByres(i,wg)
            fmt.Fprintln(ioutil.Discard, "(%d <-limit)\n", <-limit) // 出力を破棄   
            // fmt.Printf("No. %d done. (%d <-limit)\n", i, <-limit)
        }(i, &wg, limit)
        i++
    }
    wg.Wait()
}

このスクリプトでは concurrency の値で並列数の上限を規定しています。これを変えたときの結果を以下に示します。

concurrency(並列数) 100回の平均実行時間 
1 0.61596947
2 0.42971765
4 0.25714048
8 0.24074216
16 0.27779638
32 0.28692588
64 0.35032400

並列せずに実行すると約 0.5秒 を要していましたが、4並列の場合はその半分の時間で済むという結果になりました。並列処理の方が明らかに短時間で済んでいます。

また、4並列以上では実行時間の改善が見られず、頭打ちになっている(寧ろ時間が掛かってしまう)ことも観察できました。これはコードの問題というよりはマシン側の制約による所が大きいと考えられます。

Go言語は並列処理が得意だというのは聞いていましたが、実際に簡単な例で並列処理のメリットを体感することができました。ただし、やたらと並列すれば作業が効率化できるかと言われるとそうではなく、使用できるコア数やスレッド数、処理すべき作業の量などを踏まえた上で能率の最大化を目指すべきですね。

・参考資料

Golang ゴールーチンで並列数を指定して実行
go での非同期処理 その2
Go言語初心者がGo言語をさわってみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goの学習備忘録 #2 goenvでバージョン管理しようとしたら古いバージョンしか見つかりません

概要

Go言語を扱う際に、goenvを導入しようとしたら、古いバージョンしか見つからず詰まった話

homebrewでgoenvをダウンロード

macだしhomebrewでいいかなと思い、
$ brew install goenv
無事にインストールは完了

インストールできるgolangのバージョンを確認

$ goenv install -l
=> Available versions:
  1.2.2
  1.3.0
  1.3.1
  :
  :
  1.11.0
  1.11beta2
  1.11beta3
  1.11rc1
  1.11rc2
  1.11.1
  1.11.2
  1.11.3
  1.11.4
  1.12beta1

あれ?1.15.2がstable versionになってたから入れたいのにリストにない
あーgoenvのバージョン古いからか

$ brew upgrade goenv
=> Warning: goenv 1.23.3 is already installed

ん?どういうこと?

goenvでバージョン管理

homebrewじゃなくてgitでインストール

1.23系から2.0系にする場合は、git cloneで入れればよいという記事がちらほらあったので、gitでインストールする

公式に倣いインストール
$ git clone https://github.com/syndbg/goenv.git ~/.goenv

PATHを通す

export GOENV_ROOT=$HOME/.goenv
export PATH=$GOENV_ROOT/bin:$PATH
eval "$(goenv init -)"
export PATH="$GOROOT/bin:$PATH"
export PATH="$PATH:$GOPATH/bin"

PATHを通したところで、コードを実行

$ go run main.go 
main.go:1499:2: package myProject/mylib is not in GOROOT (/myProject/mylib)
main.go:1500:2: package myProject/mylib/hogehoge is not in GOROOT (myProject/mylib/hogehoge)

$ go build
go: cannot find main module; see 'go help modules'

runもbuildもうまく動かない

 gomod

Goのコードはライブラリも含めてすべて\$GOPATH/src以下に置くという約束になっている。
将来の Go 1.12 からこのやり方を改め、\$GOPATH/srcやvendorは廃止となりGo Modulesという物を採用する事になった。

今回は1.15.2を使用しているので、gomodを作成する必要があるみたい

golangの設定変更

go env -w GO111MODULE=on 

gomod作成

プロジェクトの直下で

$ go mod init myProject
$ go build
$ go run main.go
=> Human!!

無事動いた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む