0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

.NET FrameworkとPythonで合成文字の前方一致を比較

Last updated at Posted at 2020-06-07

.NET Framework と Python で前方一致の挙動が違うことに気付きました。Unicode の等価性に関係します。一部 Mono の挙動にも注意が必要です。

正規化

Unicode には濁点やアクセントなどの付加記号が付いた文字を、まとめて 1 文字(合成済み文字)として扱う方法と、ベースの文字(基底文字)と記号(結合文字)とを分離して扱う方法があります。どちらかに統一することを正規化と呼びます。

Unicodeの正規化手段の基礎は、文字の合成と分解という概念である。文字の合成とは、基底文字と結合文字の組み合わせによる結合文字列を、単一の符号位置である合成済み文字にする手続きである。

身近な例では濁点付きの仮名が対象となります。

  • "か" U+304B + "゛" U+3099 ⇔ "が" U+304C

【参考】 Unicodeでは濁点や半濁点を別扱いしてることがあるので結合した - はてなの鴨澤

.NET Framework の文字列には正規化のためのメソッドがあります。

F#
> "\u304b\u3099".Normalize() |> Seq.map (int >> sprintf "%x");;
val it : seq<string> = seq ["304c"]

正準等価

分離した形と結合した形とを等価として扱う方法を正準等価と呼びます。

たとえば、ダイアクリティカルマークを持つ合成済みの文字は、分解すると「基底文字+結合文字のダイアクリティカルマーク」の文字列に変わるが、いずれも等価であるとみなされる。言いかえると合成済み文字 ‘ü’ は ‘u’ と結合文字の分音記号 ‘¨’ を並べたものと正準等価である。

.NET Framework や Python では、単なる文字列比較では正準等価は考慮されません。

F#
> "\u304b\u3099" = "\u304c";;
val it : bool = false
Python
>>> "\u304b\u3099" == "\u304c"
False

メソッドによっては考慮されます。

StartsWith

以下の例では、.NET Framework で正準等価が考慮されますが、Python では考慮されません。

F#
> "\u304b\u3099".StartsWith "\u304c";;
val it : bool = true

> "\u304c".StartsWith "\u304b\u3099";;
val it : bool = true
Python
>>> "\u304b\u3099".startswith("\u304c")
False
>>> "\u304c".startswith("\u304b\u3099")
False

以下の例では、文字コードの並びを見ているかどうかの違いが分かります。

F#
> "\u304b\u3099".StartsWith "\u304b";;
val it : bool = false
Python
>>> "\u304b\u3099".startswith("\u304b")
True

合成済み文字が用意されていない組み合わせでも、.NET Framework では結合文字として扱われます。

F#
> "=\u3099".StartsWith "=";;
val it : bool = false
Python
>>> "=\u3099".startswith("=")
True

= を増やすと、.NET Framework と Mono で挙動が変わります。

.NET
> "==\u3099".StartsWith "==";;
val it : bool = false
Mono
> "==\u3099".StartsWith "==";;
val it : bool = true
Python
>>> "==\u3099".startswith("==")
True

※ .NET Core 3.1 では .NET Framework 4.8 と同じ結果になることを確認しました。

移植の際に挙動の違いに悩まされました。

Python に合わせるには部分文字列を比較する方法があります。

F#
> "==\u3099".Substring(0, 2) = "==";;
val it : bool = true

※ 一般化する場合、部分文字列を取る前に長さがはみ出さないか確認する必要があります。

正規表現でも正準等価は考慮されません。

F#
> open System.Text.RegularExpressions;;
> let r = Regex "^=";;
val r : Regex = ^=

> r.Match "=\u3099";;
val it : Match = = {Captures = seq [...];
                    Groups = seq [...];
                    Index = 0;
                    Length = 1;
                    Name = "0";
                    Success = true;
                    Value = "=";}

追記

Mono の件に関してコメントをいただきました。難しい問題のようです。

関連記事

以下の記事を書くための調査をしているときに気付きました。

== の言語名に余分な結合文字が含まれていましたが、修正されていることを確認しました。定期的にチェックしているようです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?