[C#] よく使う文字列の比較判定を高速で安全に

2018-05-22 (火)

高速な完全比較。大文字/小文字、半角/全角、ひらがな/カタカナの比較方法。

環境

  • Windows 10 Pro 64bit 1709
  • Visual Studio Community 2017 15.5.7
  • .NET Framework 4.6.1
  • C# 7.0

日本語環境を前提とします。

// "ja-JP"
Console.WriteLine(System.Globalization.CultureInfo.CurrentCulture.Name);

結果

拡張メソッドを実装した使用例です。

// 完全比較 (高速で確実(環境非依存)なバイナリ比較)
s1 == s2;                  // 通常のやり方で良い
s1.Contains(s2);           // 通常のやり方で良い
s1.CompareToOptions(s2);
s1.IsPrefixOptions(s2);    // s1.StartsWith(s2, StringComparison.Ordinal) と同じ
s1.IsSuffixOptions(s2);    // s1.EndsWith(s2, StringComparison.Ordinal) と同じ
s1.IndexOfOptions(s2);     // s1.IndexOf(s2, StringComparison.Ordinal) と同じ
s1.LastIndexOfOptions(s2); // s1.LastIndexOf(s2, StringComparison.Ordinal) と同じ

// 大文字/小文字を無視 (少し高速で確実(環境非依存)なバイナリ比較)
s1.Equals(s2, StringComparison.OrdinalIgnoreCase);           // 通常のメソッドにオプション指定
s1.ContainsOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.IndexOf(s2, StringComparison.OrdinalIgnoreCase) >= 0 と同じ
s1.CompareToOptions(s2, CompareOptions.OrdinalIgnoreCase);
s1.IsPrefixOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.StartsWith(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.IsSuffixOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.EndsWith(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.IndexOfOptions(s2, CompareOptions.OrdinalIgnoreCase);     // s1.IndexOf(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.LastIndexOfOptions(s2, CompareOptions.OrdinalIgnoreCase); // s1.LastIndexOf(s2, StringComparison.OrdinalIgnoreCase) と同じ

// ひらがな/カタカナを無視
s1.EqualsOptions(s2, CompareOptions.IgnoreKanaType);
s1.ContainsOptions(s2, CompareOptions.IgnoreKanaType);
s1.CompareToOptions(s2, CompareOptions.IgnoreKanaType);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreKanaType);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreKanaType);
s1.IndexOfOptions(s2, CompareOptions.IgnoreKanaType);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreKanaType);

// 全角/半角を無視
s1.EqualsOptions(s2, CompareOptions.IgnoreWidth);
s1.ContainsOptions(s2, CompareOptions.IgnoreWidth);
s1.CompareToOptions(s2, CompareOptions.IgnoreWidth);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreWidth);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreWidth);
s1.IndexOfOptions(s2, CompareOptions.IgnoreWidth);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreWidth);

// ひらがな/カタカナ、全角/半角、大文字/小文字を無視
s1.EqualsOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.ContainsOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.CompareToOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IndexOfOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);

以下は完全一致ではなく(言語環境に依存するし)高速でもないため、通常は使用しなくて良いと思います。

s1.CompareTo(s2);
s1.IndexOf(s2);
s1.LastIndexOf(s2);
s1.StartsWith(s2);
s1.EndsWith(s2);

CompareOptions 列挙型 (System.Globalization)

項目カルチャ内容速度
Ordinal非依存完全一致、バイナリ比較高速
OrdinalIgnoreCase非依存大文字と小文字を無視、バイナリ比較少し高速
None依存遅い
IgnoreCase依存大文字と小文字を無視遅い
IgnoreKanaType依存ひらがなとカタカナを無視遅い
IgnoreWidth依存全角と半角を無視遅い
IgnoreNonSpace依存濁点、発音記号、①の○を無視遅い
IgnoreSymbols依存空白、句読点、通貨記号、数学記号、%や&などの記号を無視遅い
StringSort依存ハイフンやアポストロフィが英数字や他記号よりも前になる比較遅い

StringComparison 列挙型 (System)

実装

public static class StringExtension
{
    private const CompareOptions Ordinal = CompareOptions.Ordinal;

    public static int CompareToOptions(this string string1, string string2, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, string2, options);

    public static int CompareToOptions(this string string1, int offset1, string string2, int offset2,
        CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, string2, offset2, options);

    public static int CompareToOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, options);

    public static bool ContainsOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options) >= 0;

    public static bool ContainsOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options) >= 0;

    public static bool ContainsOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options) >= 0;

    public static bool ContainsOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options) >= 0;

    public static bool ContainsOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options) >= 0;

    public static bool ContainsOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options) >= 0;

    public static bool EqualsOptions(this string string1, string string2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(string2, options) == 0;

    public static bool EqualsOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(offset1, string2, offset2, options) == 0;

    public static bool EqualsOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(offset1, length1, string2, offset2, length2, options) == 0;

    public static bool IsPrefixOptions(this string source, string prefix, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IsPrefix(source, prefix, options);

    public static bool IsSuffixOptions(this string source, string suffix, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IsSuffix(source, suffix, options);

    private static int IndexOfOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);

    public static int IndexOfOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);

    public static int IndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);

    public static int IndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);

    public static int IndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int IndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int LastIndexOfOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);

    public static int LastIndexOfOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);

    public static int LastIndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);

    public static int LastIndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);

    public static int LastIndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int LastIndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, count, options);
}

C# 6.0 より前のコードです。

using System.Globalization;

public static class StringExtension
{
    private const CompareOptions Ordinal = CompareOptions.Ordinal;

    public static int CompareToOptions(this string string1, string string2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, string2, options);
    }

    public static int CompareToOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, string2, offset2, options);
    }

    public static int CompareToOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, options);
    }

    public static bool EqualsOptions(this string string1, string string2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(string2, options) == 0;
    }

    public static bool EqualsOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(offset1, string2, offset2, options) == 0;
    }

    public static bool EqualsOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(offset1, length1, string2, offset2, length2, options) == 0;
    }

    public static bool IsPrefixOptions(this string source, string prefix, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(source, prefix, options);
    }

    public static bool IsSuffixOptions(this string source, string suffix, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(source, suffix, options);
    }

    public static int IndexOfOptions(this string source, char value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);
    }

    public static int IndexOfOptions(this string source, string value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);
    }

    public static int IndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);
    }

    public static int IndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);
    }

    public static int IndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int IndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int LastIndexOfOptions(this string source, char value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);
    }

    public static int LastIndexOfOptions(this string source, string value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);
    }

    public static int LastIndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);
    }

    public static int LastIndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);
    }

    public static int LastIndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int LastIndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, count, options);
    }
}

感謝

2018-05-22 (火)