Wie kann ich eine kultursensitive "Start-with" -Operation von der Mitte eines Strings aus durchführen?

106

Ich habe eine Verpflichtung , die relativ dunkel ist, aber es fühlt sich an wie es sollte möglich sein , die BCL verwenden.

Für den Kontext analysiere ich eine Datums- / Zeitzeichenfolge in Noda Time . Ich behalte einen logischen Cursor für meine Position innerhalb der Eingabezeichenfolge. Während die vollständige Zeichenfolge "3. Januar 2013" sein kann, befindet sich der logische Cursor möglicherweise auf "J".

Jetzt muss ich den Monatsnamen analysieren und ihn mit allen bekannten Monatsnamen für die Kultur vergleichen:

  • Kulturbewusst
  • Groß- und Kleinschreibung wird nicht berücksichtigt
  • Nur von der Spitze des Cursors (nicht später; ich möchte sehen, ob der Cursor den Monatsnamen des Kandidaten "ansieht")
  • Schnell
  • ... und ich muss später wissen, wie viele Zeichen verwendet wurden

Der aktuelle Code dafür funktioniert im Allgemeinen mit CompareInfo.Compare. Es ist effektiv so (nur für den passenden Teil - es gibt mehr Code in der realen Sache, aber es ist nicht relevant für das Match):

internal bool MatchCaseInsensitive(string candidate, CompareInfo compareInfo)
{
    return compareInfo.Compare(text, position, candidate.Length,
                               candidate, 0, candidate.Length, 
                               CompareOptions.IgnoreCase) == 0;
}

Dies hängt jedoch davon ab, dass der Kandidat und die Region, die wir vergleichen, gleich lang sind. Meistens in Ordnung, in einigen besonderen Fällen jedoch nicht in Ordnung. Angenommen, wir haben so etwas wie:

// U+00E9 is a single code point for e-acute
var text = "x b\u00e9d y";
int position = 2;
// e followed by U+0301 still means e-acute, but from two code points
var candidate = "be\u0301d";

Jetzt wird mein Vergleich fehlschlagen. Ich könnte gebrauchen IsPrefix:

if (compareInfo.IsPrefix(text.Substring(position), candidate,
                         CompareOptions.IgnoreCase))

aber:

  • Dafür muss ich einen Teilstring erstellen, den ich lieber vermeiden möchte. (Ich betrachte Noda Time als eine effektive Systembibliothek. Die Analyse der Leistung kann für einige Clients sehr wichtig sein.)
  • Es sagt mir nicht, wie weit ich den Cursor danach bewegen soll

In Wirklichkeit vermute ich stark, dass dies nicht sehr oft vorkommt ... aber ich würde hier wirklich gerne das Richtige tun. Ich würde es auch wirklich gerne tun können, ohne Unicode-Experte zu werden oder es selbst zu implementieren :)

(Wird in Noda Time als Fehler 210 ausgelöst, falls jemand einer möglichen Schlussfolgerung folgen möchte.)

Ich mag die Idee der Normalisierung. Ich muss das im Detail auf a) Richtigkeit und b) Leistung überprüfen. Vorausgesetzt, ich kann es richtig machen, bin ich mir immer noch nicht sicher, ob es sich lohnt, alles zu ändern - so etwas wird im wirklichen Leben wahrscheinlich nie auftauchen, könnte aber die Leistung aller meiner Benutzer beeinträchtigen: (

Ich habe auch die BCL überprüft - was anscheinend auch nicht richtig funktioniert. Beispielcode:

using System;
using System.Globalization;

class Test
{
    static void Main()
    {
        var culture = (CultureInfo) CultureInfo.InvariantCulture.Clone();
        var months = culture.DateTimeFormat.AbbreviatedMonthNames;
        months[10] = "be\u0301d";
        culture.DateTimeFormat.AbbreviatedMonthNames = months;

        var text = "25 b\u00e9d 2013";
        var pattern = "dd MMM yyyy";
        DateTime result;
        if (DateTime.TryParseExact(text, pattern, culture,
                                   DateTimeStyles.None, out result))
        {
            Console.WriteLine("Parsed! Result={0}", result);
        }
        else
        {
            Console.WriteLine("Didn't parse");
        }
    }
}

Wenn Sie den benutzerdefinierten Monatsnamen in "Bett" mit dem Textwert "bEd" ändern, wird dies in Ordnung analysiert.

Okay, noch ein paar Datenpunkte:

  • Die Kosten für die Verwendung Substringund IsPrefixsind erheblich, aber nicht schrecklich. Bei einem Beispiel von "Friday April 12 2013 20:28:42" auf meinem Entwicklungs-Laptop ändert sich die Anzahl der Analysevorgänge, die ich in einer Sekunde ausführen kann, von ungefähr 460 KB auf ungefähr 400 KB. Ich würde diese Verlangsamung lieber vermeiden, wenn es möglich ist, aber es ist nicht so schlimm.

  • Normalisierung ist weniger machbar als ich dachte - weil sie in tragbaren Klassenbibliotheken nicht verfügbar ist. Ich könnte es möglicherweise nur für Nicht-PCL-Builds verwenden, sodass die PCL-Builds etwas weniger korrekt sind. Der Leistungstreffer beim Testen auf Normalisierung ( string.IsNormalized) reduziert die Leistung auf etwa 445.000 Anrufe pro Sekunde, mit denen ich leben kann. Ich bin mir immer noch nicht sicher, ob es alles tut, was ich brauche - zum Beispiel sollte ein Monatsname, der "ß" enthält, in vielen Kulturen mit "ss" übereinstimmen, glaube ich ... und Normalisierung macht das nicht.

Jon Skeet
quelle
Ich verstehe zwar Ihren Wunsch, den Leistungseinbruch beim Erstellen eines Teilstrings zu vermeiden, aber es ist vielleicht am besten, dies zu tun, aber zu Beginn des Spiels, indem Sie zunächst alles auf eine ausgewählte Unicode-Normalisierungsform verschieben und dann wissen, dass Sie "Punkt für Punkt" gehen können ". Wahrscheinlich D-Form.
IDisposable
@IDisposable: Ja, darüber habe ich mich gewundert. Natürlich kann ich die Monatsnamen vorher selbst normalisieren. Zumindest kann ich die Normalisierung nur einmal durchführen. Ich frage mich, ob das Normalisierungsverfahren prüft, ob zuerst etwas getan werden muss. Ich habe nicht viel Erfahrung mit Normalisierung - definitiv ein Weg, den ich mir ansehen sollte.
Jon Skeet
1
Wenn Sie textnicht zu lang sind, können Sie tun if (compareInfo.IndexOf(text, candidate, position, options) == position). msdn.microsoft.com/en-us/library/ms143031.aspx Aber wenn textes sehr lang ist, wird es viel Zeit verschwenden, darüber hinaus zu suchen, wo es nötig ist.
Jim Mischel
1
Nur Bypass der Verwendung von StringKlasse überhaupt in diesem Fall und verwendet eine Char[]direkt. Sie werden am Ende mehr Code schreiben, aber das passiert, wenn Sie hohe Leistung wünschen ... oder vielleicht sollten Sie in C ++ / CLI programmieren ;-)
intrepidis
1
Wird CompareOptions.IgnoreNonSpace dies nicht automatisch für Sie erledigen? Es sieht für mich so aus (vom Docco, nicht in der Lage, von diesem iPad aus zu testen, sorry!), Als ob dies ein ( der ?) Anwendungsfall für diese Option sein könnte. „ Zeigt an, dass die String - Vergleich ignorieren müssen Ohne Zwischenraum kombiniert Zeichen wie Umlaute.
Sepster

Antworten:

41

Ich werde zuerst das Problem vieler <-> einer / vieler Fallzuordnungen und getrennt von der Behandlung verschiedener Normalisierungsformen betrachten.

Beispielsweise:

x heiße y
  ^--- cursor

Stimmt überein heisse, bewegt dann aber Cursor 1 zu stark. Und:

x heisse y
  ^--- cursor

Stimmt überein heiße, bewegt dann aber Cursor 1 zu wenig.

Dies gilt für alle Zeichen, die keine einfache Eins-zu-Eins-Zuordnung haben.

Sie müssen die Länge des Teilstrings kennen, der tatsächlich übereinstimmt. Aber Compare, IndexOf..etc diese Informationen wegzuwerfen. Es könnte mit regulären Ausdrücken möglich sein , aber die Umsetzung ist nicht vollständig Fall Falten und so nicht überein ßzu ss/SSin Groß- und Kleinschreibung - Modus , obwohl .Compareund .IndexOftut. Und es wäre wahrscheinlich kostspielig, ohnehin für jeden Kandidaten neue reguläre Ausdrücke zu erstellen.

Die einfachste Lösung hierfür besteht darin, Zeichenfolgen nur in Groß- und Kleinschreibung intern zu speichern und binäre Vergleiche mit Kandidaten für Groß- und Kleinschreibung durchzuführen. Dann können Sie den Cursor korrekt bewegen, .Lengthda der Cursor zur internen Darstellung dient. Sie erhalten auch den größten Teil der verlorenen Leistung zurück, wenn Sie nicht verwenden müssen CompareOptions.IgnoreCase.

Leider gibt es keinen Fall Falzfunktion eingebaut und der Fall Faltung des armen Mannes funktioniert auch nicht , weil es keine vollständige Fallzuordnung ist - das ToUpperVerfahren nicht dreht ßin SS.

Dies funktioniert beispielsweise in Java (und sogar in Javascript), wenn eine Zeichenfolge in normaler Form C vorliegt:

//Poor man's case folding.
//There are some edge cases where this doesn't work
public static String toCaseFold( String input, Locale cultureInfo ) {
    return input.toUpperCase(cultureInfo).toLowerCase(cultureInfo);
}

Es macht Spaß zu bemerken, dass Javas Vergleich von Groß- und Kleinschreibung keine vollständige Faltung von Groß- und Kleinschreibung wie bei C # bewirkt CompareOptions.IgnoreCase. In dieser Hinsicht sind sie also gegensätzlich: Java führt eine vollständige Fallabbildung durch, aber eine einfache Fallfaltung - C # führt eine einfache Fallabbildung durch, aber eine vollständige Fallfaltung.

Daher ist es wahrscheinlich, dass Sie eine Bibliothek eines Drittanbieters benötigen, um Ihre Zeichenfolgen zu falten, bevor Sie sie verwenden.


Bevor Sie etwas tun, müssen Sie sicherstellen, dass Ihre Zeichenfolgen in normaler Form C vorliegen. Sie können diese vorläufige Schnellprüfung verwenden, die für lateinische Schrift optimiert ist:

public static bool MaybeRequiresNormalizationToFormC(string input)
{
    if( input == null ) throw new ArgumentNullException("input");

    int len = input.Length;
    for (int i = 0; i < len; ++i)
    {
        if (input[i] > 0x2FF)
        {
            return true;
        }
    }

    return false;
}

Dies führt zu falsch positiven, aber nicht zu falsch negativen Ergebnissen. Ich erwarte nicht, dass es 460.000 Parsen / s verlangsamt, wenn lateinische Schriftzeichen verwendet werden, obwohl es für jede Zeichenfolge ausgeführt werden muss. Mit einem falschen Positiv würden IsNormalizedSie ein echtes Negativ / Positiv erhalten und erst danach bei Bedarf normalisieren.


Zusammenfassend muss bei der Verarbeitung also zuerst die normale Form C und dann die Fallfalte sichergestellt werden. Führen Sie binäre Vergleiche mit den verarbeiteten Zeichenfolgen durch und bewegen Sie den Cursor, während Sie ihn gerade verschieben.

Esailija
quelle
Vielen Dank dafür - ich muss mich eingehender mit der Normalisierungsform C befassen, aber das sind großartige Hinweise. Ich denke, ich kann mit dem "es funktioniert nicht ganz richtig unter der PCL" leben (was keine Normalisierung bietet). Die Verwendung einer Drittanbieter-Bibliothek zum Falten von Fällen wäre hier übertrieben - wir haben derzeit keine Abhängigkeiten von Drittanbietern, und die Einführung einer Bibliothek nur für einen Eckfall, den selbst die BCL nicht behandelt, wäre schmerzhaft. Vermutlich ist Case-Folding kulturabhängig, übrigens (zB türkisch)?
Jon Skeet
2
@ JonSkeet Ja, Turkic verdient seinen eigenen Modus in den Casefold-Zuordnungen: P Siehe den Formatabschnitt
Esailija
Diese Antwort scheint einen fundamentalen Fehler zu haben, da sie impliziert, dass Zeichen nur beim Falten von Groß- und Kleinschreibung Ligaturen zugeordnet werden (und umgekehrt). Das ist nicht der Fall; Es gibt Ligaturen, die unabhängig vom Gehäuse als gleichwertig angesehen werden. Zum Beispiel ist in der en-US-Kultur ægleich aeund gleich ffi. Die C-Normalisierung verarbeitet Ligaturen überhaupt nicht, da sie nur Kompatibilitätszuordnungen zulässt (die normalerweise auf das Kombinieren von Zeichen beschränkt sind).
Douglas
KC- und KD-Normalisierung behandeln einige Ligaturen wie , aber vermissen andere, wie z æ. Das Problem wird durch die Diskrepanzen zwischen den Kulturen verschlimmert - æentspricht aeunter en-US, jedoch nicht unter da-DK, wie in der MSDN-Dokumentation für Zeichenfolgen erläutert . Daher sind Normalisierung (in irgendeiner Form) und Fallzuordnung keine ausreichende Lösung für dieses Problem.
Douglas
Kleine Korrektur zu meinem früheren Kommentar: Die C-Normalisierung erlaubt nur kanonische Zuordnungen (z. B. zum Kombinieren von Zeichen), keine Kompatibilitätszuordnungen (z. B. für Ligaturen).
Douglas
21

Überprüfen Sie, ob dies die Anforderung erfüllt.

public static partial class GlobalizationExtensions {
    public static int IsPrefix(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, CompareOptions options
        ) {
        if(compareInfo.IndexOf(source, prefix, startIndex, options)!=startIndex)
            return ~0;
        else
            // source is started with prefix
            // therefore the loop must exit
            for(int length2=0, length1=prefix.Length; ; )
                if(0==compareInfo.Compare(
                        prefix, 0, length1, 
                        source, startIndex, ++length2, options))
                    return length2;
    }
}

compareInfo.Compareführt nur einmal sourcebegonnen mit prefix; wenn nicht, dann IsPrefixkehrt zurück -1; Andernfalls wird die Länge der in source.

Ich habe jedoch keine Ahnung , außer Schritt length2durch 1mit dem folgenden Fall:

var candidate="ßssß\u00E9\u0302";
var text="abcd ssßss\u0065\u0301\u0302sss";

var count=
    culture.CompareInfo.IsPrefix(text, candidate, 5, CompareOptions.IgnoreCase);

Update :

Ich habe versucht, die Leistung ein wenig zu verbessern, aber es ist nicht bewiesen, ob der folgende Code einen Fehler enthält:

public static partial class GlobalizationExtensions {
    public static int Compare(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, ref int length2, 
        CompareOptions options) {
        int length1=prefix.Length, v2, v1;

        if(0==(v1=compareInfo.Compare(
            prefix, 0, length1, source, startIndex, length2, options))
            ) {
            return 0;
        }
        else {
            if(0==(v2=compareInfo.Compare(
                prefix, 0, length1, source, startIndex, 1+length2, options))
                ) {
                ++length2;
                return 0;
            }
            else {
                if(v1<0||v2<0) {
                    length2-=2;
                    return -1;
                }
                else {
                    length2+=2;
                    return 1;
                }
            }
        }
    }

    public static int IsPrefix(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, CompareOptions options
        ) {
        if(compareInfo.IndexOf(source, prefix, startIndex, options)
                !=startIndex)
            return ~0;
        else
            for(int length2=
                    Math.Min(prefix.Length, source.Length-(1+startIndex)); ; )
                if(0==compareInfo.Compare(
                        source, prefix, startIndex, ref length2, options))
                    return length2;
    }
}

Ich habe mit dem speziellen Fall getestet und den Vergleich auf ungefähr 3 reduziert.

Ken Kin
quelle
Ich würde wirklich lieber nicht so eine Schleife machen müssen. Zugegeben, mit dem frühen Out muss es nur eine Schleife geben, wenn es etwas gefunden hat, aber ich würde immer noch lieber keine 8-String-Vergleiche durchführen müssen, um beispielsweise mit "Februar" übereinzustimmen. Es fühlt sich an, als müsste es einen besseren Weg geben. Außerdem muss die anfängliche IndexOfOperation von der Startposition aus die gesamte Zeichenfolge durchsehen. Dies wäre ein Leistungsschmerz, wenn die Eingabezeichenfolge lang ist.
Jon Skeet
@ JonSkeet: Danke. Möglicherweise kann etwas hinzugefügt werden, um festzustellen, ob die Schleife verringert werden kann. Ich werde darüber nachdenken.
Ken Kin
@ JonSkeet: Würden Sie Reflexion verwenden? Da ich die Methoden verfolgt habe, fallen sie nicht weit davon aus, native Methoden aufzurufen.
Ken Kin
3
Tatsächlich. Noda Time will nicht in das Geschäft mit Unicode-Details einsteigen :)
Jon Skeet
2
Ich habe ein ähnliches Problem einmal wie dieses gelöst (Hervorheben von Suchzeichenfolgen in HTML). Ich habe es ähnlich gemacht. Sie können die Schleifen- und Suchstrategie so einstellen, dass sie sehr schnell abgeschlossen wird, indem Sie zuerst die wahrscheinlichen Fälle überprüfen. Das Schöne daran ist, dass es völlig korrekt zu sein scheint und keine Unicode-Details in Ihren Code gelangen.
usr
9

Dies ist tatsächlich ohne Normalisierung und ohne Verwendung möglich IsPrefix.

Wir müssen die gleiche Anzahl von Textelementen mit der gleichen Anzahl von Zeichen vergleichen, aber dennoch die Anzahl der übereinstimmenden Zeichen zurückgeben.

Ich habe eine Kopie der MatchCaseInsensitiveMethode aus ValueCursor.cs in Noda Time erstellt und leicht geändert, damit sie in einem statischen Kontext verwendet werden kann:

// Noda time code from MatchCaseInsensitive in ValueCursor.cs
static int IsMatch_Original(string source, int index, string match, CompareInfo compareInfo)
{
    unchecked
    {
        if (match.Length > source.Length - index)
        {
            return 0;
        }

        // TODO(V1.2): This will fail if the length in the input string is different to the length in the
        // match string for culture-specific reasons. It's not clear how to handle that...
        if (compareInfo.Compare(source, index, match.Length, match, 0, match.Length, CompareOptions.IgnoreCase) == 0)
        {
            return match.Length;
        }

        return 0;
    }
}

(Nur als Referenz enthalten, es ist der Code, der nicht richtig verglichen wird, wie Sie wissen)

Die folgende Variante dieser Methode verwendet StringInfo.GetNextTextElement, das vom Framework bereitgestellt wird. Die Idee ist, Textelement für Textelement zu vergleichen, um eine Übereinstimmung zu finden, und wenn gefunden, die tatsächliche Anzahl übereinstimmender Zeichen in der Quellzeichenfolge zurückzugeben:

// Using StringInfo.GetNextTextElement to match by text elements instead of characters
static int IsMatch_New(string source, int index, string match, CompareInfo compareInfo)
{
    int sourceIndex = index;
    int matchIndex = 0;

    // Loop until we reach the end of source or match
    while (sourceIndex < source.Length && matchIndex < match.Length)
    {
        // Get text elements at the current positions of source and match
        // Normally that will be just one character but may be more in case of Unicode combining characters
        string sourceElem = StringInfo.GetNextTextElement(source, sourceIndex);
        string matchElem = StringInfo.GetNextTextElement(match, matchIndex);

        // Compare the current elements.
        if (compareInfo.Compare(sourceElem, matchElem, CompareOptions.IgnoreCase) != 0)
        {
            return 0; // No match
        }

        // Advance in source and match (by number of characters)
        sourceIndex += sourceElem.Length;
        matchIndex += matchElem.Length;
    }

    // Check if we reached end of source and not end of match
    if (matchIndex != match.Length)
    {
        return 0; // No match
    }

    // Found match. Return number of matching characters from source.
    return sourceIndex - index;
}

Diese Methode funktioniert zumindest gemäß meinen Testfällen (die im Grunde nur ein paar Varianten der von Ihnen bereitgestellten Zeichenfolgen testen: "b\u00e9d"und "be\u0301d") einwandfrei .

Die GetNextTextElement- Methode erstellt jedoch für jedes Textelement einen Teilstring, sodass für diese Implementierung viele Teilstring-Vergleiche erforderlich sind, was sich auf die Leistung auswirkt.

Also habe ich eine andere Variante erstellt, die GetNextTextElement nicht verwendet, sondern stattdessen Unicode überspringt, indem Zeichen kombiniert werden, um die tatsächliche Übereinstimmungslänge in Zeichen zu ermitteln:

// This should be faster
static int IsMatch_Faster(string source, int index, string match, CompareInfo compareInfo)
{
    int sourceLength = source.Length;
    int matchLength = match.Length;
    int sourceIndex = index;
    int matchIndex = 0;

    // Loop until we reach the end of source or match
    while (sourceIndex < sourceLength && matchIndex < matchLength)
    {
        sourceIndex += GetTextElemLen(source, sourceIndex, sourceLength);
        matchIndex += GetTextElemLen(match, matchIndex, matchLength);
    }

    // Check if we reached end of source and not end of match
    if (matchIndex != matchLength)
    {
        return 0; // No match
    }

    // Check if we've found a match
    if (compareInfo.Compare(source, index, sourceIndex - index, match, 0, matchIndex, CompareOptions.IgnoreCase) != 0)
    {
        return 0; // No match
    }

    // Found match. Return number of matching characters from source.
    return sourceIndex - index;
}

Diese Methode verwendet die folgenden zwei Helfer:

static int GetTextElemLen(string str, int index, int strLen)
{
    bool stop = false;
    int elemLen;

    for (elemLen = 0; index < strLen && !stop; ++elemLen, ++index)
    {
        stop = !IsCombiningCharacter(str, index);
    }

    return elemLen;
}

static bool IsCombiningCharacter(string str, int index)
{
    switch (CharUnicodeInfo.GetUnicodeCategory(str, index))
    {
        case UnicodeCategory.NonSpacingMark:
        case UnicodeCategory.SpacingCombiningMark:
        case UnicodeCategory.EnclosingMark:
            return true;

        default:
            return false;
    }
}

Ich habe kein Benchmarking durchgeführt, daher weiß ich nicht wirklich, ob die schnellere Methode tatsächlich schneller ist. Ich habe auch keine erweiterten Tests durchgeführt.

Dies sollte jedoch Ihre Frage beantworten, wie ein kultursensitiver Teilstring-Abgleich für Zeichenfolgen durchgeführt werden kann, die möglicherweise Unicode-Kombinationszeichen enthalten.

Dies sind die Testfälle, die ich verwendet habe:

static Tuple<string, int, string, int>[] tests = new []
{
    Tuple.Create("x b\u00e9d y", 2, "be\u0301d", 3),
    Tuple.Create("x be\u0301d y", 2, "b\u00e9d", 4),

    Tuple.Create("x b\u00e9d", 2, "be\u0301d", 3),
    Tuple.Create("x be\u0301d", 2, "b\u00e9d", 4),

    Tuple.Create("b\u00e9d y", 0, "be\u0301d", 3),
    Tuple.Create("be\u0301d y", 0, "b\u00e9d", 4),

    Tuple.Create("b\u00e9d", 0, "be\u0301d", 3),
    Tuple.Create("be\u0301d", 0, "b\u00e9d", 4),

    Tuple.Create("b\u00e9", 0, "be\u0301d", 0),
    Tuple.Create("be\u0301", 0, "b\u00e9d", 0),
};

Die Tupelwerte sind:

  1. Die Quellzeichenfolge (Heuhaufen)
  2. Die Startposition in der Quelle.
  3. Die Streichholzschnur (Nadel).
  4. Die erwartete Matchlänge.

Das Ausführen dieser Tests mit den drei Methoden ergibt das folgende Ergebnis:

Test #0: Orignal=BAD; New=OK; Faster=OK
Test #1: Orignal=BAD; New=OK; Faster=OK
Test #2: Orignal=BAD; New=OK; Faster=OK
Test #3: Orignal=BAD; New=OK; Faster=OK
Test #4: Orignal=BAD; New=OK; Faster=OK
Test #5: Orignal=BAD; New=OK; Faster=OK
Test #6: Orignal=BAD; New=OK; Faster=OK
Test #7: Orignal=BAD; New=OK; Faster=OK
Test #8: Orignal=OK; New=OK; Faster=OK
Test #9: Orignal=OK; New=OK; Faster=OK

Die letzten beiden Tests testen den Fall, in dem die Quellzeichenfolge kürzer als die Übereinstimmungszeichenfolge ist. In diesem Fall ist auch die ursprüngliche Methode (Noda-Zeit) erfolgreich.

Mårten Wikström
quelle
Vielen Dank dafür. Ich muss es mir genauer ansehen, um zu sehen, wie gut es funktioniert, aber es sieht nach einem guten Ausgangspunkt aus. Mehr Kenntnisse über Unicode (im Code selbst) als ich gehofft hatte, wären erforderlich, aber wenn die Plattform nicht das tut, was erforderlich ist, kann ich nicht viel dagegen tun :(
Jon Skeet
@ JonSkeet: Freut mich, Ihnen zu helfen! Und ja, Teilstring-Matching mit Unicode-Unterstützung sollte definitiv in das Framework aufgenommen worden sein ...
Mårten Wikström