public Normalizer(UnicodeDataRecord[] unicodeData, NormalizationProps normalizationProps)
        {
            // CCCテーブル (共通)
            _canonicalCombiningClassTable = unicodeData.Where(x => x.CanonicalCombiningClass != 0)
                                            .ToDictionary(x => x.CodePoint, x => x.CanonicalCombiningClass);

            // 分解テーブル (Decomposer)
            _decompositionTable = CreateDecompositionTable(unicodeData);

            // 合成テーブル (Composer, OptimizedComposer)
            _compositionTable = CreateCompositionTable(unicodeData, normalizationProps);

            // クイックチェックテーブル (OptimizedComposer)
            _compositionQuickCheckTable = CreateCompositionQuickCheckTable(unicodeData, normalizationProps);
        }
        static void Main(string[] args)
        {
            var norm = new Normalizer(
                UnicodeData.Load("UnicodeData.txt"),
                NormalizationProps.Load("DerivedNormalizationProps.txt")
                );
            var tests = NormalizationTest.Load("NormalizationTest.txt");

            TestNFD(norm, tests);
            TestNFKD(norm, tests);
            TestNFC(norm, tests);
            TestNFKC(norm, tests);
            TestOptimizedNFC(norm, tests);
            TestOptimizedNFKC(norm, tests);
            BenchNFC(norm, tests);
            BenchOptimizedNFC(norm, tests);
        }
        /// <summary>合成テーブルを作成します。</summary>
        /// <param name="unicodeData">UnicodeData.txt の内容</param>
        /// <param name="normalizationProps">DerivedNormalizationProps.txt の内容</param>
        private static IReadOnlyDictionary <CodePointPair, uint> CreateCompositionTable(UnicodeDataRecord[] unicodeData, NormalizationProps normalizationProps)
        {
            var fullCompositionExclusion = new HashSet <uint>(normalizationProps.FullCompositionExclusion);

            return(unicodeData.Where(x => x.DecompositionMapping != null &&
                                     x.DecompositionMapping.Type == null && // 互換分解でない
                                     x.DecompositionMapping.Mapping.Length == 2 && // 1文字の分解でない -> 2文字
                                     !fullCompositionExclusion.Contains(x.CodePoint) // Full_Composition_Exclusion
                                     ).ToDictionary(
                       x => new CodePointPair(x.DecompositionMapping.Mapping[0], x.DecompositionMapping.Mapping[1]),
                       x => x.CodePoint
                       ));
        }
 /// <summary>合成のクイックチェック用CCCテーブルを作成します。</summary>
 /// <param name="unicodeData">UnicodeData.txt の内容</param>
 /// <param name="normalizationProps">DerivedNormalizationProps.txt の内容</param>
 /// <returns>Key: コードポイント, Value: CCC または、 NFC_QCがNかMなら255、NFKC_QCがNかMなら256</returns>
 private static IReadOnlyDictionary <uint, int> CreateCompositionQuickCheckTable(UnicodeDataRecord[] unicodeData, NormalizationProps normalizationProps)
 {
     return(unicodeData.Where(x => x.CanonicalCombiningClass != 0).Where(x => !normalizationProps.NfcQC.ContainsKey(x.CodePoint)).Select(x => new { x.CodePoint, Ccc = x.CanonicalCombiningClass }) // CCC
            .Concat(normalizationProps.NfcQC.Select(x => new { CodePoint = x.Key, Ccc = 255 }))                                                                                                     // NFC_QC
            .Concat(normalizationProps.NfkcQC.Where(x => !normalizationProps.NfcQC.ContainsKey(x.Key)).Select(x => new { CodePoint = x.Key, Ccc = 256 }))                                           // NFKC_QC
            .ToDictionary(x => x.CodePoint, x => x.Ccc));
 }