Ich habe ein Problem mit Codeforces gelöst. Normalerweise überprüfe ich zuerst, ob das Zeichen ein oberer oder unterer englischer Buchstabe ist, subtrahiere oder addiere es 32
, um es in den entsprechenden Buchstaben umzuwandeln. Aber ich habe jemanden gefunden, ^= 32
der das Gleiche tut. Hier ist es:
char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a
Ich habe nach einer Erklärung dafür gesucht und es nicht herausgefunden. Warum funktioniert das so?
c++
bit-manipulation
ascii
Devon
quelle
quelle
@
mithilfe von in ` konvertieren^ 32
.toupper
undtolower
Kleinschreibung verwenden und umschalten.A
anZ
. Das ist in Ordnung, solange Sie sich nur für Englisch interessieren (und keine Schreibweisen "naiv", Wörter wie "Café" oder Namen mit diakritischen Zeichen verwenden ...), aber die Welt ist nicht nur Englisch.Antworten:
Werfen wir einen Blick auf die ASCII-Codetabelle in Binärform.
Und 32 ist
0100000
der einzige Unterschied zwischen Klein- und Großbuchstaben. Wenn Sie dieses Bit umschalten, wird der Fall eines Buchstabens umgeschaltet.quelle
{
ist kürzer als[
, es ist also ein "Kleinbuchstabe". Nein? Ok, ich werde mich zeigen: Dfoobar[]
und geltenfoobar{}
als identische Spitznamen, da bei Spitznamen die Groß- und Kleinschreibung nicht berücksichtigt wird und IRC seinen Ursprung in Skandinavien hat :)Dies nutzt die Tatsache, dass ASCII-Werte von wirklich klugen Leuten gewählt wurden.
Dadurch wird das sechstniedrigste Bit 1 von
foo
(das Großbuchstaben-Flag von ASCII) umgedreht, wodurch ein ASCII-Großbuchstabe in einen Kleinbuchstaben umgewandelt wird und umgekehrt .Beispiel
Und durch Eigentum von XOR ,
'a' ^ 32 == 'A'
.Beachten
C ++ ist nicht erforderlich, um ASCII zur Darstellung von Zeichen zu verwenden. Eine andere Variante ist EBCDIC . Dieser Trick funktioniert nur auf ASCII-Plattformen. Eine tragbarere Lösung wäre die Verwendung von
std::tolower
undstd::toupper
mit dem angebotenen Bonus, um das Gebietsschema zu kennen (es löst jedoch nicht automatisch alle Ihre Probleme, siehe Kommentare):1) Da 32
1 << 5
(2 hoch 5) ist, wird das 6. Bit umgedreht (von 1 gezählt).quelle
tolower
auf Deutsch braucht nicht nur ein Wörterbuch, es muss in der Lage sein, die Bedeutung zu analysieren.Lassen Sie mich sagen, dass dies - obwohl es klug erscheint - ein wirklich, wirklich dummer Hack ist. Wenn Ihnen dies 2019 jemand empfiehlt, schlagen Sie ihn. Schlagen Sie ihn so hart wie möglich.
Sie können dies natürlich in Ihrer eigenen Software tun, die Sie und niemand anderes verwendet, wenn Sie wissen, dass Sie sowieso nie eine andere Sprache als Englisch verwenden werden. Ansonsten nicht gehen.
Der Hack war vor 30-35 Jahren wohl "OK", als Computer nicht wirklich viel konnten, sondern Englisch in ASCII und vielleicht eine oder zwei wichtige europäische Sprachen. Aber ... nicht mehr so.
Der Hack funktioniert, weil US-lateinische Groß- und Kleinschreibung genau
0x20
voneinander getrennt sind und in derselben Reihenfolge angezeigt werden, was nur ein kleiner Unterschied ist. Was in der Tat dieses bisschen Hack umschaltet.Jetzt waren die Leute, die Codepages für Westeuropa und später das Unicode-Konsortium erstellten, klug genug, um dieses Schema für z. B. deutsche Umlaute und Vokale mit französischem Akzent beizubehalten. Nicht so für ß, die (bis jemand das Unicode-Konsortium 2017 überzeugte und ein großes Fake News-Printmagazin darüber schrieb und den Duden tatsächlich überzeugte - kein Kommentar dazu) nicht einmal als Versal existieren (verwandelt sich in SS) . Jetzt tut es existieren als versal, aber die beiden sind
0x1DBF
Positionen auseinander, nicht0x20
.Die Implementierer waren jedoch nicht rücksichtsvoll genug, um dies aufrechtzuerhalten. Wenn Sie beispielsweise Ihren Hack in einigen osteuropäischen Sprachen oder ähnlichem anwenden (ich würde nichts über Kyrillisch wissen), werden Sie eine böse Überraschung erleben. Alle diese "Beil" -Zeichen sind Beispiele dafür, Klein- und Großbuchstaben sind voneinander getrennt. Der Hack tut es also funktioniert dort nicht richtig.
Es gibt noch viel mehr zu beachten. Einige Zeichen werden beispielsweise nicht einfach von Klein- in Großbuchstaben umgewandelt (sie werden durch unterschiedliche Sequenzen ersetzt), oder sie können ihre Form ändern (was unterschiedliche Codepunkte erfordert).
Denken Sie nicht einmal darüber nach, was dieser Hack für Dinge wie Thai oder Chinesisch tun wird (es gibt Ihnen nur völligen Unsinn).
Das Speichern von ein paar hundert CPU-Zyklen hat sich vor 30 Jahren vielleicht sehr gelohnt, aber heutzutage gibt es wirklich keine Entschuldigung dafür, einen String richtig zu konvertieren. Es gibt Bibliotheksfunktionen zum Ausführen dieser nicht trivialen Aufgabe.
Die Zeit, die benötigt wird, um mehrere Dutzend Kilobyte Text richtig zu konvertieren, ist heutzutage vernachlässigbar.
quelle
Es funktioniert, weil der Unterschied zwischen 'a' und A 'in ASCII und abgeleiteten Codierungen zufällig 32 beträgt und 32 auch der Wert des sechsten Bits ist. Das Umdrehen des 6. Bits mit einem exklusiven ODER konvertiert somit zwischen oberem und unterem.
quelle
Höchstwahrscheinlich wird Ihre Implementierung des Zeichensatzes ASCII sein. Wenn wir uns die Tabelle ansehen:
Wir sehen, dass es einen Unterschied
32
zwischen dem Wert einer Klein- und Großbuchstabenzahl gibt. Wenn wir dies tun^= 32
(was dem Umschalten des 6. niedrigstwertigen Bits entspricht), ändert sich daher zwischen einem Klein- und einem Großbuchstaben.Beachten Sie, dass es mit allen Symbolen funktioniert, nicht nur mit den Buchstaben. Es schaltet ein Zeichen mit dem jeweiligen Zeichen um, wobei das 6. Bit unterschiedlich ist, was zu einem Zeichenpaar führt, das zwischen den Zeichen hin- und hergeschaltet wird. Für die Buchstaben bilden die jeweiligen Groß- / Kleinbuchstaben ein solches Paar. A
NUL
wird sich inSpace
und umgekehrt ändern und@
mit dem Backtick umschalten. Grundsätzlich wechselt jedes Zeichen in der ersten Spalte dieses Diagramms mit dem Zeichen eine Spalte weiter, und das Gleiche gilt für die dritte und vierte Spalte.Ich würde diesen Hack jedoch nicht verwenden, da es keine Garantie dafür gibt, dass er auf jedem System funktioniert. Verwenden Sie stattdessen einfach toupper und tolower und Fragen wie isupper .
quelle
32 ^ 32
ist 0, nicht 64[a-z]
und[A-Z]
sind "Buchstaben". Der Rest sind Zufälle, die der gleichen Regel folgen. Wenn dich jemand nach "Großbuchstaben]" fragen würde, was wäre das? es wäre immer noch "]" - "}" ist nicht der "Großbuchstabe" von "]".%32
im ASCII-Codierungssystem keine "Alignment" -Grenze überschreiten . Aus diesem Grund ist Bit0x20
der einzige Unterschied zwischen den Groß- / Kleinbuchstaben desselben Buchstabens. Wenn dies nicht der Fall wäre, müssten Sie addieren oder subtrahieren0x20
, nicht nur umschalten, und für einige Buchstaben würde es eine Ausführung geben, um andere höhere Bits umzudrehen. (Und die gleiche Operation konnte nicht umschalten, und es wäre schwieriger, zunächst nach alphabetischen Zeichen|= 0x20
zu suchen, da man lcase nicht erzwingen konnte.)Hier gibt es viele gute Antworten, die beschreiben, wie dies funktioniert, aber warum es so funktioniert, ist die Verbesserung der Leistung. Bitweise Operationen sind schneller als die meisten anderen Operationen innerhalb eines Prozessors. Sie können schnell einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung durchführen, indem Sie einfach nicht auf das Bit schauen, das die Groß- und Kleinschreibung bestimmt, oder die Groß- / Kleinschreibung einfach ändern, indem Sie das Bit umdrehen (die Leute, die die ASCII-Tabelle entworfen haben, waren ziemlich schlau).
Offensichtlich ist dies aufgrund schnellerer Prozessoren und Unicode nicht annähernd so wichtig wie 1960 (als die Arbeit an ASCII begann), aber es gibt immer noch einige kostengünstige Prozessoren, die einen signifikanten Unterschied bewirken könnten solange Sie nur ASCII-Zeichen garantieren können.
https://en.wikipedia.org/wiki/Bitwise_operation
HINWEIS: Ich würde aus verschiedenen Gründen (Lesbarkeit, Korrektheit, Portabilität usw.) die Verwendung von Standardbibliotheken für die Arbeit mit Zeichenfolgen empfehlen. Verwenden Sie Bit Flipping nur, wenn Sie die Leistung gemessen haben und dies Ihr Engpass ist.
quelle
So funktioniert ASCII, das ist alles.
Wenn Sie dies ausnutzen, geben Sie die Portabilität auf, da C ++ nicht auf ASCII als Codierung besteht.
Aus diesem Grund sind die Funktionen
std::toupper
undstd::tolower
in der C ++ - Standardbibliothek implementiert - Sie sollten diese stattdessen verwenden.quelle
Siehe die zweite Tabelle unter http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii und die folgenden Hinweise, die unten wiedergegeben sind:
ASCII wurde so konzipiert, dass die Tasten shiftund und ctrlohne viel (oder vielleicht auch keine ctrl) Logik implementiert werden können -shift wahrscheinlich sind nur wenige Gates erforderlich. Es war wahrscheinlich mindestens so sinnvoll, das Drahtprotokoll zu speichern wie jede andere Zeichenkodierung (keine Softwarekonvertierung erforderlich).
Der verlinkte Artikel auch erklärt , viele fremde Hacker Konventionen wie
And control H does a single character and is an old^H^H^H^H^H classic joke.
( gefunden hier ).quelle
foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20
, obwohl dies nur ASCII ist und daher aus Gründen, die in anderen Antworten angegeben sind, unklug ist. Es kann wahrscheinlich auch mit verzweigungsfreier Programmierung verbessert werden.foo ^= 0x20 >> !(foo & 0x40)
wäre einfacher. Auch ein gutes Beispiel dafür, warum knapper Code oft als unlesbar angesehen wird ^ _ ^.Beim Xoring mit 32 (00100000 in Binär) wird das sechste Bit (von rechts) gesetzt oder zurückgesetzt. Dies entspricht genau dem Addieren oder Subtrahieren von 32.
quelle
Die alphabetischen Bereiche in Klein- und Großbuchstaben
%32
überschreiten im ASCII-Codierungssystem keine "Ausrichtungsgrenze".Aus diesem Grund ist Bit
0x20
der einzige Unterschied zwischen den Groß- / Kleinbuchstaben desselben Buchstabens.Wenn dies nicht der Fall wäre, müssten Sie addieren oder subtrahieren
0x20
, nicht nur umschalten, und für einige Buchstaben würde es eine Ausführung geben, um andere höhere Bits umzudrehen. (Und es würde keine einzige Operation geben, die umschalten könnte, und es wäre schwieriger, zunächst nach alphabetischen Zeichen zu suchen, da Sie | = 0x20 nicht zwingen könnten, lcase zu erzwingen.)Verwandte Nur-ASCII-Tricks: Sie können nach einem alphabetischen ASCII-Zeichen suchen, indem Sie Kleinbuchstaben mit erzwingen
c |= 0x20
und dann prüfen, ob (ohne Vorzeichen)c - 'a' <= ('z'-'a')
. Also nur 3 Operationen: OR + SUB + CMP gegen eine Konstante 25. Natürlich wissen Compiler, wie(c>='a' && c<='z')
man so etwas für Sie optimiert , also sollten Sie denc|=0x20
Teil höchstens selbst erledigen . Es ist ziemlich unpraktisch, alle erforderlichen Castings selbst durchzuführen, insbesondere um standardmäßige Ganzzahl-Promotions für signierte zu umgehenint
.Siehe auch Konvertieren einer Zeichenfolge in C ++ in Großbuchstaben (SIMD-Zeichenfolge)
toupper
für ASCII, wobei der Operand für XOR mithilfe dieser Prüfung maskiert wird.)Außerdem erfahren Sie, wie Sie auf ein Zeichenarray zugreifen und Kleinbuchstaben in Großbuchstaben ändern und umgekehrt (C mit SIMD-Eigenheiten und skalarem x86-Asm-Fall-Flip für alphabetische ASCII-Zeichen, wobei andere unverändert bleiben).
Diese Tricks sind meistens nur nützlich, wenn Sie eine Textverarbeitung mit SIMD (z. B. SSE2 oder NEON) von Hand optimieren, nachdem Sie überprüft haben, ob bei keinem der
char
s in einem Vektor das High-Bit gesetzt ist. (Und daher ist keines der Bytes Teil einer Multi-Byte-UTF-8-Codierung für ein einzelnes Zeichen, die möglicherweise unterschiedliche Inversen in Groß- / Kleinbuchstaben aufweist.) Wenn Sie welche finden, können Sie für diesen 16-Byte-Block oder für den Rest der Zeichenfolge auf den Skalar zurückgreifen.Es gibt sogar einige Gebietsschemas, in denen
toupper()
odertolower()
auf einigen Zeichen im ASCII-Bereich Zeichen außerhalb dieses Bereichs erzeugt werden, insbesondere auf Türkisch, wo I ↔ ı und İ ↔ i. In diesen Gebietsschemas benötigen Sie eine differenziertere Prüfung oder versuchen wahrscheinlich gar nicht, diese Optimierung zu verwenden.In einigen Fällen dürfen Sie jedoch ASCII anstelle von UTF-8 annehmen, z. B. Unix-Dienstprogramme mit
LANG=C
(dem POSIX-Gebietsschema), nichten_CA.UTF-8
oder was auch immer.Aber wenn Sie überprüfen können, ob es sicher ist, können Sie
toupper
Zeichenfolgen mittlerer Länge viel schneller alstoupper()
in einer Schleife aufrufen (wie 5x), und zuletzt habe ich mit Boost 1.58 getestet , viel schneller als das,boost::to_upper_copy<char*, std::string>()
wasdynamic_cast
für jeden Charakter dumm ist .quelle