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
Substring
undIsPrefix
sind 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.
text
nicht zu lang sind, können Sie tunif (compareInfo.IndexOf(text, candidate, position, options) == position)
. msdn.microsoft.com/en-us/library/ms143031.aspx Aber wenntext
es sehr lang ist, wird es viel Zeit verschwenden, darüber hinaus zu suchen, wo es nötig ist.String
Klasse überhaupt in diesem Fall und verwendet eineChar[]
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 ;-)Antworten:
Ich werde zuerst das Problem vieler <-> einer / vieler Fallzuordnungen und getrennt von der Behandlung verschiedener Normalisierungsformen betrachten.
Beispielsweise:
Stimmt überein
heisse
, bewegt dann aber Cursor 1 zu stark. Und: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ß
zuss/SS
in Groß- und Kleinschreibung - Modus , obwohl.Compare
und.IndexOf
tut. 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,
.Length
da der Cursor zur internen Darstellung dient. Sie erhalten auch den größten Teil der verlorenen Leistung zurück, wenn Sie nicht verwenden müssenCompareOptions.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
ToUpper
Verfahren nicht drehtß
inSS
.Dies funktioniert beispielsweise in Java (und sogar in Javascript), wenn eine Zeichenfolge in normaler Form C vorliegt:
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:
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
IsNormalized
Sie 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.
quelle
æ
gleichae
undffi
gleichffi
. Die C-Normalisierung verarbeitet Ligaturen überhaupt nicht, da sie nur Kompatibilitätszuordnungen zulässt (die normalerweise auf das Kombinieren von Zeichen beschränkt sind).ffi
, aber vermissen andere, wie zæ
. Das Problem wird durch die Diskrepanzen zwischen den Kulturen verschlimmert -æ
entsprichtae
unter 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.Überprüfen Sie, ob dies die Anforderung erfüllt.
compareInfo.Compare
führt nur einmalsource
begonnen mitprefix
; wenn nicht, dannIsPrefix
kehrt zurück-1
; Andernfalls wird die Länge der insource
.Ich habe jedoch keine Ahnung , außer Schritt
length2
durch1
mit dem folgenden Fall:Update :
Ich habe versucht, die Leistung ein wenig zu verbessern, aber es ist nicht bewiesen, ob der folgende Code einen Fehler enthält:
Ich habe mit dem speziellen Fall getestet und den Vergleich auf ungefähr 3 reduziert.
quelle
IndexOf
Operation von der Startposition aus die gesamte Zeichenfolge durchsehen. Dies wäre ein Leistungsschmerz, wenn die Eingabezeichenfolge lang ist.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
MatchCaseInsensitive
Methode aus ValueCursor.cs in Noda Time erstellt und leicht geändert, damit sie in einem statischen Kontext verwendet werden kann:(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:
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:
Diese Methode verwendet die folgenden zwei Helfer:
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:
Die Tupelwerte sind:
Das Ausführen dieser Tests mit den drei Methoden ergibt das folgende Ergebnis:
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.
quelle