はじめに
C#定石 - ファイル一覧 - 照合規則 で、任意にピックアップした漢字を、Windows 日本語カルチャ照合規則でソートした結果を確認しました。
文字 | Unicode | JIS X0213 | 水準 | 画数 | 部首 | 音読み |
---|---|---|---|---|---|---|
一 | U+4E00 | 1-16-76 | 第1水準 | 1 | 一部 | イチ・イツ |
三 | U+4E09 | 1-27-16 | 第1水準 | 3 | 一部 | サン |
二 | U+4E8C | 1-38-83 | 第1水準 | 2 | 二部 | ニ |
亜 | U+4E9C | 1-16-1 | 第1水準 | 7 | 二部 | ア |
亞 | U+4E9E | 1-48-19 | 第2水準 | 8 | 二部 | ア |
唖 | U+5516 | 1-16-2 | 第1水準 | 10 | 口部 | ア・アク |
啞 | U+555E | 1-15-8 | 第3水準 | 11 | 口部 | ア・アク |
啡 | U+5561 | 2-4-8 | 第4水準 | 11 | 口部 | ハイ・ヒ |
排 | U+6392 | 1-39-51 | 第1水準 | 11 | 手部 | ハイ |
柛 | U+67DB | 2-14-50 | 第4水準 | 9 | 木部 | シン |
神 | U+795E | 1-31-32 | 第1水準 | 9 | 示部 | シン・ジン |
神 | U+FA19 | 1-89-28 | 第3水準 | 10 | 示部 | シン・ジン |
var arrayItems4 = new string[] { "一", "三", "二", "亜", "亞", "唖",
"啞", "啡", "排", "柛", "神", "神" };
Array.Sort(arrayItems4);
// → "亜", "唖", "一", "三", "神", "二", "排", "亞", "神", "啞", "啡", "柛"
JIS X 0213 面区点順をベースとしているようですが、「啞」がイレギュラーという結果になったので、漢字ソート順を確認してみることにします。
参考情報
下記情報を参考にさせて頂きました。
テスト環境
ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
漢字ソート順
テストデータ作成とソート実行
まず、 JIS X 0213漢字一覧 - Wikipedia から、「JIS X 0213漢字一覧の1面」「JIS X 0213漢字一覧の2面」を、Unicode タブ区切りテキストファイルに落とします。
対象ファイルの項目は「文字」「面区点」「Shift_JIS-2004」「Unicode」「水準」「備考」となっていますが、「文字」「面区点」「水準」「Unicode」「UTF16」に変更します。
// 原本 TSV → 今回利用 TSV
private void Original2Custom(string original, string custom)
{
using (var sr = new StreamReader(original, Encoding.UTF8))
using (var sw = new StreamWriter(custom, false, Encoding.UTF8))
{
// ヘッダー
// original「文字」「面区点」「Shift_JIS-2004」「Unicode」「水準」「備考」
// → custom「文字」「面区点」「水準」「Unicode」「UTF16」
var line = sr.ReadLine();
sw.WriteLine("文字\t面区点\t水準\tUnicode\tUTF16");
// データ
while (sr.Peek() > -1)
{
// 1行取得してタブ区切りで分解
line = sr.ReadLine().Trim();
var items = line.Split('\t');
if (items?.Length >= 5)
{
sw.WriteLine(string.Format("{0}\t{1}\t{2}\t{3}\t{4}",
parts[0].Trim(), parts[1].Trim(),
parts[4].Trim(), parts[3].Trim(),
HexaDumpUTF16(parts[0].Trim())));
}
}
}
}
// UTF16 ヘキサダンプ
private string HexaDumpUTF16(string target)
{
var chars = target.ToCharArray();
var sb = new StringBuilder();
foreach (char c in chars)
{
sb.Append(string.Format("0x{0:X4} ", (ushort)c));
}
return sb.ToString().TrimEnd();
}
データ管理クラスとして CharX0213 を用意、List<CharX0213>
を作成してソートします。
// 対象データ
private List<CharX0213> lstTarget = new List<CharX0213>();
// 対象ファイルロード → ソート → 結果ファイル出力
public void DoSort(string x0213_1, string x0213_2, string output)
{
// 対象ファイルロード
lstTarget.Clear();
File2List(x0213_1);
File2List(x0213_2);
// ソート
var sorted = lstTarget.OrderBy(x => x.Target);
// 結果出力
using (var sw = new StreamWriter(output, false, Encoding.UTF8))
{
// ヘッダ
sw.WriteLine("文字\t面区点\t水準\tUnicode\tUTF16");
// データ
foreach(var item in sorted)
{
sw.WriteLine(string.Format("{0}\t{1}\t{2}\t{3}\t{4}",
item.Target, item.X0213,
item.Level, item.Unicode,
item.HexaDumpUTF16));
}
}
}
// ファイル ロード
private void File2List(string path)
{
using (var sr = new StreamReader(path, Encoding.UTF8))
{
// ヘッダ
var line = sr.ReadLine();
// データ
while (sr.Peek() > -1)
{
line = sr.ReadLine().Trim();
var items = line.Split('\t');
if (items?.Length >= 5)
{
lstTarget.Add(new CharX0213(items));
}
}
}
}
public class CharX0213
{
public string Target { get; set; } // 文字
public string X0213 { get; set; } // JIS X 0213 面区点
public string Level { get; set; } // 第1水準, 第2水準, ...
public string Unicode { get; set; } // Unicode
public string HexaDumpUTF16 { get; set; } // UTF16 16進数
public CharX0213(int sirial, string [] items)
{
if (items.Length >= 5)
{
Target = items[0].Trim();
X0213 = items[1].Trim();
Level = items[2].Trim();
Unicode = items[3].Trim();
HexaDumpUTF16 = items[4].Trim();
}
}
}
掲載ソースで作成したテストデータと、Windows 日本語カルチャ照合規則でソートした結果を GitHub にアップロードしておきました。
- Windows 日本語カルチャ照合規則 確認
- JISX0213-1.txt(JIS X0213 漢字1面 区点順 テストデータ)
- JISX0213-2.txt(JIS X0213 漢字2面 区点順 テストデータ)
- JISX0213-sorted.txt(ソート結果)
結果確認
ソート結果を確認すると、末尾にサロゲートペアが固まっています。
このことから、文字クラスとして、「非サロゲートペア」<「サロゲートペア」
で分類がされているようです。
「非サロゲートペア」は、第1水準漢字と第2水準漢字については、いくつかのイレギュラー部分はありますが、JIS X 0213 面区点順をベースとしているようです。
「サロゲートペア」については、Unicode順となっています。
ソート結果全体で、JIS X 0213 面区点順ではない部分の抽出として「ひとつ前の文字の JIS X 0213 面区点よりも、該当文字の JIS X 0213 面区点が小さい」部分をピックアップすると 851ヶ所もあります。
このようなイレギュラーで、規則性を見いだせたのは、第1水準漢字の直後に、第3水準 異体字という並び順 5ヶ所のみでした。
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
殺 | 1-27-06 | 第1水準 | U+6BBA | 0x6BBA |
殺 | 1-86-41 | 第3水準 | U+F970 | 0xF970 |
薩 | 1-27-07 | 第1水準 | U+85A9 | 0x85A9 |
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
欄 | 1-45-83 | 第1水準 | U+6B04 | 0x6B04 |
欄 | 1-86-27 | 第3水準 | U+F91D | 0xF91D |
濫 | 1-45-84 | 第1水準 | U+6FEB | 0x6FEB |
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
虜 | 1-46-26 | 第1水準 | U+865C | 0x865C |
虜 | 1-91-47 | 第3水準 | U+F936 | 0xF936 |
了 | 1-46-27 | 第1水準 | U+4E86 | 0x4E86 |
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
類 | 1-46-64 | 第1水準 | U+985E | 0x985E |
類 | 1-94-04 | 第3水準 | U+F9D0 | 0xF9D0 |
令 | 1-46-65 | 第1水準 | U+4EE4 | 0x4EE4 |
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
廊 | 1-47-13 | 第1水準 | U+5ECA | 0x5ECA |
廊 | 1-84-14 | 第3水準 | U+F928 | 0xF928 |
弄 | 1-47-14 | 第1水準 | U+5F04 | 0x5F04 |
「非サロゲートペア」部分、第2水準漢字の末尾 JIS X 0213 1-84-06 以降は、第3水準漢字、第4水準漢字となります。
1-84-06 前後(前方1文字、後方15文字)のソート順を抜粋します。
文字 | 面区点 | 水準 | Unicode | UTF16 |
---|---|---|---|---|
凜 | 1-84-05 | 第2水準 | U+51DC | 0x51DC |
熙 | 1-84-06 | 第2水準 | U+7199 | 0x7199 |
纊 | 1-90-23 | 第3水準 | U+7E8A | 0x7E8A |
褜 | 1-91-78 | 第3水準 | U+891C | 0x891C |
鍈 | 1-93-25 | 第3水準 | U+9348 | 0x9348 |
銈 | 1-93-14 | 第3水準 | U+9288 | 0x9288 |
蓜 | 2-86-53 | 第4水準 | U+84DC | 0x84DC |
俉 | 1-14-25 | 第3水準 | U+4FC9 | 0x4FC9 |
炻 | 2-79-64 | 第4水準 | U+70BB | 0x70BB |
昱 | 1-85-21 | 第3水準 | U+6631 | 0x6631 |
棈 | 1-85-73 | 第3水準 | U+68C8 | 0x68C8 |
鋹 | 2-91-03 | 第4水準 | U+92F9 | 0x92F9 |
曻 | 1-85-23 | 第3水準 | U+66FB | 0x66FB |
彅 | 1-84-26 | 第3水準 | U+5F45 | 0x5F45 |
丨 | 1-14-04 | 第3水準 | U+4E28 | 0x4E28 |
仡 | 1-14-08 | 第3水準 | U+4EE1 | 0x4EE1 |
伀 | 2-01-31 | 第4水準 | U+4F00 | 0x4F00 |
画数、部首、音読みは関係なさそうですよね、、、
知識不足のため、この並び順から、規則性を見出すことはできませんでした。
自然順(エクスプローラ互換)
WIN32API - StrCmpLogicalW でソートした場合についても、Windows 日本語カルチャ照合規則と同様の結果となりました。
まとめ
Windows 日本語カルチャ照合規則、および、自然順(WIN32API - StrCmpLogicalW)で漢字をソートすると下記順序となります。
- 非サロゲートペア
- 第1水準漢字と第2水準漢字( + 第3水準漢字 5文字)
- 基本的に JIS X 0213 面区点順
- 第1水準漢字の直後に、第3水準 異体字が配置されるケース有り
- 殺(1-27-06、第1水準)殺(1-86-41、第3水準)
- 欄(1-45-83、第1水準)欄(1-86-27、第3水準)
- 虜(1-46-26、第1水準)虜(1-91-47、第3水準)
- 類(1-46-64、第1水準)類(1-94-04、第3水準)
- 廊(1-47-13、第1水準)廊(1-84-14、第3水準)
- 第3水準漢字と第4水準漢字
- 規則性を見出すことはできませんでした
- 第1水準漢字と第2水準漢字( + 第3水準漢字 5文字)
- サロゲートペア
- Unicode順