Wie kann man einen String umkehren, der komplizierte Emojis enthält?

193

Eingang:

Hello world👩‍🦰👩‍👩‍👦‍👦

Gewünschte Ausgabe:

👩‍👩‍👦‍👦👩‍🦰dlrow olleH

Ich habe verschiedene Ansätze ausprobiert, aber keiner hat mir die richtige Antwort gegeben.

Dies schlug kläglich fehl:

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦';

const reversed = text.split('').reverse().join('');

console.log(reversed);

Das funktioniert irgendwie, aber es zerfällt 👩‍👩‍👦‍👦in 4 verschiedene Emojis:

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦';

const reversed = [...text].reverse().join('');

console.log(reversed);

Ich habe auch jede Antwort in dieser Frage ausprobiert , aber keine davon funktioniert.

Gibt es eine Möglichkeit, die gewünschte Ausgabe zu erhalten?

Hao Wu
quelle
26
Ich kann das Problem mit der zweiten Lösung nicht sehen. Was vermisse ich?
Pedro Lima
13
Diese Emojis sind also irgendwie kombinatorische Emojis, das ist ziemlich interessant. Zuerst haben Sie das Frauengesichts-Emoji, das selbst durch zwei Ihrer Charaktere dargestellt wird, und dann gibt es ein zusätzliches Verbindungszeichen, den Charcode 8205, und dann gibt es zwei weitere , die "rotes Haar" darstellen, und diese 5 Zeichen zusammen meine "Frauengesicht mit roten Haaren"
TKoL
11
Eine Saite mit kombinierten Emojis richtig umzukehren, wäre ziemlich kompliziert, denke ich. Sie müssten überprüfen, ob auf jedes Emoji der Zeichencode 8205 folgt, und wenn ja, müssten Sie es mit dem vorherigen Emoji kombinieren, anstatt es als eigenen Charakter zu behandeln. Ziemlich kompliziert ...
TKoL
18
Javascript verwirrt mich. Es ist die seltsamste Mischung aus Sprachkonzepten auf niedriger und hoher Ebene. Es ist insofern eben, als es den Speicher vollständig abstrahiert (keine Zeiger, manuelle Speicherverwaltung), aber so niedrig, dass Zeichenfolgen eher als dumme Codepunkte als als erweiterte Graphemcluster behandelt werden. Es ist wirklich verwirrend und lässt mich nie wissen, was mich bei der Arbeit mit diesem Ding erwartet.
Alexander - Monica
12
@ Alexander-ReinstateMonica Gibt es eine Sprache, die standardmäßig nach Graphem aufteilt? JS stellt nur in UTF-16 codierte Standardzeichenfolgen bereit.
Lichter0123

Antworten:

91

Wenn Sie dazu in der Lage sind, verwenden Sie die _.split()von lodash bereitgestellte Funktion . Ab Version 4.0 ab, _.split()ist die Aufspaltung Unicode Emojis fähig.

Die Verwendung des Native .reverse().join('')zum Umkehren der 'Zeichen' sollte mit Emojis mit Joinern mit der Breite Null problemlos funktionieren

function reverse(txt) { return _.split(txt, '').reverse().join(''); }

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦';
console.log(reverse(text));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

0stone0
quelle
3
Die Änderungsprotokolle, auf die Sie verweisen, erwähnen "v4.9.0 - Sichergestellt, dass _.split mit Emojis funktioniert". Ich denke, 4.0 ist möglicherweise zu früh. Die Kommentare im Code, der zum Teilen der Zeichenfolgen verwendet wird ( github.com/lodash/lodash/blob/4.17.15/lodash.js#L261 ), beziehen sich auf mathiasbynens.be/notes/javascript-unicode aus dem Jahr 2013. Es Es sieht so aus, als ob es sich seitdem weiterentwickelt hat, aber es verwendet eine ziemlich schwer zu entschlüsselnde Menge von Unicode-Regexen. Ich kann auch keine Tests in ihrer Codebasis für die Unicode-Aufteilung sehen. All dies würde mich vorsichtig machen, es in der Produktion zu verwenden.
Michael Anderson
5
Es dauerte nur ein wenig, bis reverse("뎌쉐") festgestellt wurde , dass dies fehlschlägt (2 koreanische Grapheme), was "ᅰ셔 ᄃ" (3 Grapheme) ergibt.
Michael Anderson
2
Es scheint, dass es keine einfache native Lösung für dieses Problem gibt. Ich würde es nicht vorziehen, eine Bibliothek nur zu importieren, um dies zu lösen, aber es ist in der Tat die zuverlässigste / konsistenteste Methode, dies zu diesem Zeitpunkt zu tun.
Hao Wu
1
Ein großes Lob dafür, dass dies richtig funktioniert 😎 Das Umkehren der Schreibrichtung in Firefox unter Windows 10 ist immer noch ein bisschen unangenehm (die Kinder landen hinten), also schlägt lodash Windows 10, was wahrscheinlich ein etwas geringeres Budget bedeutet 😅
yeoman
52

Ich nahm die Idee von TKoL, das \u200dZeichen zu verwenden, und versuchte damit, ein kleineres Skript zu erstellen.

Hinweis: Nicht alle Kompositionen verwenden einen Joiner mit einer Breite von Null, sodass er mit anderen Kompositionszeichen fehlerhaft ist.

Es verwendet die traditionelle forSchleife, da wir einige Iterationen überspringen, falls wir kombinierte Emoticons finden. Innerhalb der forSchleife gibt es eine whileSchleife, um zu überprüfen, ob ein folgendes \u200dZeichen vorhanden ist. Solange es eines gibt, fügen wir auch die nächsten 2 Zeichen hinzu und leiten die forSchleife mit 2 Iterationen weiter, damit kombinierte Emoticons nicht umgekehrt werden.

Um es einfach für jede Zeichenfolge zu verwenden, habe ich es als neue Prototypfunktion für das Zeichenfolgenobjekt erstellt.

String.prototype.reverse = function() {
  let textArray = [...this];
  let reverseString = "";

  for (let i = 0; i < textArray.length; i++) {
    let char = textArray[i];
    while (textArray[i + 1] === '\u200d') {
      char += textArray[i + 1] + textArray[i + 2];
      i = i + 2;
    }
    reverseString = char + reverseString;
  }
  return reverseString;
}

const text = "Hello world👩‍🦰👩‍👩‍👦‍👦";

console.log(text.reverse());

//Fun fact, you can chain them to double reverse :)
//console.log(text.reverse().reverse());

Mark Baijens
quelle
5
Ich dachte, wenn Sie den Text in Browsern ziehen und auswählen, 👩‍👩‍👦‍👦kann er nur als Ganzes ausgewählt werden. Woher wissen Browser, dass es sich um ein Zeichen handelt? Gibt es eine eingebaute Möglichkeit, dies zu tun?
Hao Wu
10
@HaoWu Dies ist die sogenannte "Unicode-Segmentierung" in "Grapheme-Clustern". Ihr Browser (der möglicherweise den von Ihrem Betriebssystem bereitgestellten verwendet) wird die Auswahl pro Graphemcluster rendern und zulassen. : Sie können die spec hier lesen unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
lights0123
7
@HaoWu: "Woher wissen Browser, dass es sich um ein Zeichen handelt?" - Es ist nicht "ein Charakter". Es sind mehrere Zeichen, die sich zu einem einzigen Graphemcluster verbinden , der als einzelne Glyphe gerendert wird .
Jörg W Mittag
6
Gleich wie hier ; Nicht alle Kompositionen verwenden einen Joiner mit der Breite Null.
Holger
6
Dies kehrt nichts anderes als mit ZWJ zusammengesetzte Zeichen korrekt um. Bitte verwenden Sie nicht nur hier, sondern in der Regel externe Bibliotheken, die von Personen geschrieben wurden, die wissen, was sie tun, anstatt maßgeschneiderte Lösungen zu hacken, die zufällig für einen Testfall funktionieren. Die Runen- und Lodash- Bibliotheken wurden in anderen Antworten empfohlen (ich kann auch nicht dafür bürgen).
Benrg
46

Das Umkehren von Unicode-Text ist aus vielen Gründen schwierig.

Erstens werden Zeichenfolgen je nach Programmiersprache auf unterschiedliche Weise dargestellt, entweder als Liste von Bytes, als Liste von UTF-16-Codeeinheiten (16 Bit breit, in der API häufig als "Zeichen" bezeichnet) oder als ucs4-Codepunkte (4 Bytes breit).

Zweitens spiegeln verschiedene APIs diese innere Repräsentation in unterschiedlichem Maße wider. Einige arbeiten an der Abstraktion von Bytes, andere an UTF-16-Zeichen, andere an Codepunkten. Wenn die Darstellung Bytes oder UTF-16-Zeichen verwendet, gibt es normalerweise Teile der API, die Ihnen Zugriff auf die Elemente dieser Darstellung gewähren, sowie Teile, die die erforderliche Logik ausführen, um von Bytes (über UTF-8) oder von zu erhalten UTF-16-Zeichen zu den tatsächlichen Codepunkten.

Oft wurden die Teile der API, die diese Logik ausführen und Ihnen so Zugriff auf die Codepunkte gewähren, später hinzugefügt, da zuerst 7-Bit-ASCII vorhanden waren, dann etwas später alle dachten, 8-Bit wären ausreichend, wobei verschiedene Codeseiten verwendet wurden, und sogar später reichten 16 Bit für Unicode. Der Begriff der Codepunkte als Ganzzahlen ohne feste Obergrenze wurde historisch als vierte gemeinsame Zeichenlänge für die logische Codierung von Text hinzugefügt.

Die Verwendung einer API, mit der Sie auf die tatsächlichen Codepunkte zugreifen können, scheint das Richtige zu sein. Aber...

Drittens gibt es viele Modifikatorcodepunkte, die den nächsten Codepunkt oder die folgenden Codepunkte beeinflussen. Zum Beispiel gibt es einen diakritischen Modifikator, der aus einem folgenden a ein ä, e nach ë usw. macht. Drehen Sie die Codepunkte um und aë wird zu eä, bestehend aus verschiedenen Buchstaben. Es gibt eine direkte Darstellung von zB ä als eigenen Codepunkt, aber die Verwendung des Modifikators ist genauso gültig.

Viertens ist alles in ständigem Fluss. Es gibt auch viele Modifikatoren unter den Emoji, wie im Beispiel verwendet, und jedes Jahr werden weitere hinzugefügt. Wenn Sie über eine API auf die Informationen zugreifen können, ob ein Codepunkt ein Modifikator ist, bestimmt die Version der API, ob sie bereits einen bestimmten neuen Modifikator kennt.

Unicode bietet jedoch einen hackigen Trick, wenn es nur um das visuelle Erscheinungsbild geht:

Es gibt Modifikatoren für die Schreibrichtung. Im Beispiel wird die Schreibrichtung von links nach rechts verwendet. Fügen Sie am Anfang des Textes einfach einen Modifikator für die Schreibrichtung von rechts nach links hinzu. Abhängig von der Version der API / des Browsers sieht dieser korrekt umgekehrt aus 😎

'\ u202e' heißt Override von rechts nach links, es ist die stärkste Version der Markierung von rechts nach links.

Siehe diese Erklärung von w3.org

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦'
console.log('\u202e' + text)

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦'
let original = document.getElementById('original')
original.appendChild(document.createTextNode(text))
let result = document.getElementById('result')
result.appendChild(document.createTextNode('\u202e' + text))
body {
  font-family: sans-serif
}
<p id="original"></p>
<p id="result"></p>

yeoman
quelle
8
+1 sehr kreativer Einsatz von Bidi (-: Es ist sicherer, den Override mit einem POP DIRECTIONAL FORMATTING-Zeichen '\u202e' + text + '\u202c'zu schließen, um die Beeinflussung des folgenden Textes zu vermeiden.
Beni Cherniavsky-Paskin
2
Danke 😎 Es ist ein ziemlich hackiger Trick und der Artikel, auf den ich verlinkt habe, geht sehr detailliert darauf ein, warum es viel schlauer ist, die HTML-Attribute zu verwenden, aber auf diese Weise könnte ich einfach die String-Verkettung für meinen Hack verwenden 😂
yeoman
7
Übrigens. Mein Firefox auf dieser Maschine (Gewinn 10) macht es nicht ganz richtig, die Kinder sind hinter den Eltern, wenn sie von rechts nach links schreiben. Ich denke, es ist schwierig, mit diesen massiv komplexen Emoji-Modifikatoren für Personengruppen die richtige Schreibrichtung zu finden. ..
yeoman
2
Ein weiterer Spaßfall: die regionalen Indikatorsymbole für Flaggen-Emojis. Wenn Sie die Zeichenfolge "🇦🇨" (die beiden Codepunkte U + 1F1E6, U + 1F1E8, die die Flagge für Ascension Island bilden) nehmen und versuchen, sie naiv umzukehren, erhalten Sie "🇨🇦", die Flagge für Kanada.
Adam Rosenfield
2
@yeoman FYI: „UTF-16 Zeichen“ (wie Sie den Begriff hier verwendet) werden auch als „UTF-16 - Code bekannt Einheiten “. "Zeichen" ist in der Regel zu vieldeutig für einen Begriff, da er sich auf viele Dinge beziehen kann (im Kontext von Unicode jedoch normalerweise auf einen Codepunkt).
Inkling
38

Ich kenne! Ich werde RegExp verwenden. Was könnte schiefgehen? (Antwort links als Übung für den Leser.)

const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦';

const reversed = text.match(/.(\u200d.)*/gu).reverse().join('');

console.log(reversed);

Neil
quelle
5
Ihre Antwort klingt entschuldigend, aber ehrlich gesagt würde ich diese Antwort als nahezu kanonisch bezeichnen. Es ist definitiv anderen Antworten überlegen, die versuchen, dasselbe manuell zu tun. Zeichenbasierte Textmanipulation ist das, wofür Regex entwickelt wurde und sich auszeichnet, und das Unicode-Konsortium standardisiert explizit die erforderlichen Regex-Funktionen (die ECMAScript in diesem Fall zufällig korrekt implementiert). Das heißt, es kombinierende Zeichen zu handhaben versagt (die IIRC regex sollte mit Griff .Platzhalter).
Konrad Rudolph
14
Funktioniert nicht mit Kompositionen, die nicht mit U+200Dz 🏳️‍🌈. Es ist erwähnenswert, dass komponierte Charaktere auch außerhalb der Emijoi-Welt existieren…
Holger
2
@StevenPenny 🏳️‍🌈 enthält zwei Kompositionen, von denen eine nicht verwendet wird U+200D. Es ist leicht zu überprüfen, ob 🏳️‍🌈 mit dem Code dieser Antwort nicht funktioniert…
Holger
1
@Holger ist zwar wahr, dass 🏳️‍🌈 eine Komposition enthält, die nicht mit U + 200D erstellt wurde, aber es ist ein ziemlich schlechtes Beispiel, da es auch eine Komposition mit U + 200D enthält. Ein besseres Beispiel wäre so etwas wie 🧑🏻 oder 🏳️
Steven Penny
3
Im Gegensatz zu den anderen Kommentaren hier sollte nicht jede Verwendung eines Joiners mit der Breite Null als ein einzelner Graphemcluster behandelt werden. Beispielsweise zeigen die letzten drei Zeilen des Unicode 13-Graphemtests ( unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest.txt ) drei sehr ähnliche Fälle, in denen der ZWJ unterschiedlich behandelt wird.
Michael Anderson
30

Eine alternative Lösung wäre die Verwendung einer runeskleinen, aber effektiven Bibliothek:

https://github.com/dotcypress/runes

const runes = require('runes')

// String.substring
'👨‍👨‍👧‍👧a'.substring(1) => '�‍👨‍👧‍👧a'

// Runes
runes.substr('👨‍👨‍👧‍👧a', 1) => 'a'

runes('12👩‍👩‍👦‍👦3🍕✓').reverse().join(); 
// results in: "✓🍕3👩‍👩‍👦‍👦21"
Arnis Juraga
quelle
3
Dies ist die beste Antwort. Alle diese anderen Antworten haben Fälle, in denen sie fehlschlagen. Diese Bibliothek erfüllt (hoffentlich) alle Randfälle.
Carson Graham
1
Das ist lustig, dass sich eine solche "einfache Frage" auf den ersten Blick als nicht leicht zu lösende Aufgabe herausstellte. Stimmen Sie mit Carson überein - die Bibliothek wird hoffentlich mit Updates und Änderungen fortfahren, während sich Emojis weiterentwickelt.
Arnis Juraga
3
Sieht so aus, als ob dies seit ungefähr 3 Jahren nicht mehr aktualisiert wurde. Unicode 11 wurde ungefähr zu dieser Zeit veröffentlicht, aber die Dinge haben sich seitdem geändert, und Unicode 13 wurde später veröffentlicht. Es gab einige Änderungen in den erweiterten Graphemregeln in 13. Daher kann es einige Randfälle geben, die hier nicht behandelt werden. (Ich habe den Code nicht durchgesehen - aber es lohnt sich, vorsichtig zu sein)
Michael Anderson
2
Ich stimme @MichaelAnderson zu, diese Bibliothek scheint einen naiven oder alten Algorithmus zu verwenden. Um dies richtig zu machen, sollte der in Unicode angegebene Graphemsegmentierungsalgorithmus verwendet werden .
Inkling
20

Sie haben nicht nur Probleme mit Emoji, sondern auch mit anderen kombinierenden Charakteren. Diese Dinge, die sich wie einzelne Buchstaben anfühlen, aber tatsächlich ein oder mehrere Unicode-Zeichen sind, werden als "erweiterte Graphemcluster" bezeichnet.

Das Aufteilen einer Zeichenfolge in diese Cluster ist schwierig (siehe beispielsweise diese Unicode-Dokumente ). Ich würde mich nicht darauf verlassen, es selbst zu implementieren, sondern eine vorhandene Bibliothek verwenden. Google hat mich auf die Graphem-Splitter- Bibliothek hingewiesen . Die Dokumente für diese Bibliothek enthalten einige nette Beispiele , die die meisten Implementierungen auslösen werden:

Damit sollten Sie schreiben können:

var splitter = new GraphemeSplitter();
var graphemes = splitter.splitGraphemes(string);
var reversed = graphemes.reverse().join('');

ASIDE: Für Besucher aus der Zukunft oder diejenigen, die bereit sind, auf dem neuesten Stand zu leben:

Es wird vorgeschlagen , dem Javascript-Standard einen Graphemsegmentierer hinzuzufügen. (Es bietet auch andere Segmentierungsoptionen). Es befindet sich derzeit in Phase 3 und wird derzeit in JSC und V8 implementiert (siehe https://github.com/tc39/proposal-intl-segmenter/issues/114 ).

Mit diesem würde der Code wie folgt aussehen:

var segmenter = new Intl.Segmenter("en", {granularity: "grapheme"})
var segment_iterator = segmenter.segment(string)
var graphemes = []
for (let {segment} of segment_iterator) {
    graphemes.push(segment)
}
var reversed = graphemes.reverse().join('');

Sie können dies wahrscheinlich ordentlicher machen, wenn Sie moderneres Javascript kennen als ich ...

Hier gibt es eine Implementierung - aber ich weiß nicht, was es erfordert.

Hinweis: Dies weist auf ein unterhaltsames Problem hin, das andere Antworten noch nicht angesprochen haben. Die Segmentierung kann vom verwendeten Gebietsschema abhängen - nicht nur von den Zeichen in der Zeichenfolge.

Michael Anderson
quelle
1
Es sieht so aus, als ob der Code seit ungefähr 2 Jahren nicht mehr aktualisiert wurde - daher sind seine Tabellen möglicherweise nicht aktuell. Möglicherweise müssen Sie nach etwas Neuerem suchen.
Michael Anderson
3
Es sieht so aus, als ob eine neuere Version dieser Bibliothek unter github.com/flmnt/graphemer
Michael Anderson
4
Ich bin überrascht, dass ich so weit nach unten scrollen musste, um eine Antwort zu sehen, die tatsächlich richtig ist.
Lambda Fairy
1
Für das Vorschlagsbeispiel könnten Sie tun const graphemes = Array.from(segment_iterator, ({segment}) => segment).
Inkling
17

Ich habe mich nur zum Spaß dafür entschieden, war eine gute Herausforderung. Nicht sicher, ob es in allen Fällen korrekt ist. Verwenden Sie es daher auf eigenes Risiko. Hier ist es jedoch:

function run() {
    const text = 'Hello world👩‍🦰👩‍👩‍👦‍👦';
    const newText = reverseText(text);
    console.log(newText);
}

function reverseText(text) {
    // first, create an array of characters
    let textArray = [...text];
    let lastCharConnector = false;
    textArray = textArray.reduce((acc, char, index) => {
        if (char.charCodeAt(0) === 8205) {
            const lastChar = acc[acc.length-1];
            if (Array.isArray(lastChar)) {
                lastChar.push(char);
            } else {
                acc[acc.length-1] = [lastChar, char];
            }
            lastCharConnector = true;
        } else if (lastCharConnector) {
            acc[acc.length-1].push(char);
            lastCharConnector = false;
        } else {
            acc.push(char);
            lastCharConnector = false;
        }
        return acc;
    }, []);
    
    console.log('initial text array', textArray);
    textArray = textArray.reverse();
    console.log('reversed text array', textArray);

    textArray = textArray.map((item) => {
        if (Array.isArray(item)) {
            return item.join('');
        } else {
            return item;
        }
    });

    return textArray.join('');
}

run();

TKoL
quelle
1
Naja, eigentlich ist es lang, weil die Debug-Infos. Ich weiß das wirklich zu schätzen
Hao Wu
1
@ AndrewSavinykh Kein Code-Golf, aber auf der Suche nach einer eleganteren Lösung. Vielleicht nicht wie ein Einzeiler verrückt, aber leicht zu merken. Wie die Regex-Lösung ist imho wirklich gut.
Hao Wu
0

Sie können verwenden:

yourstring.split('').reverse().join('')

Es sollte Ihre Zeichenfolge in eine Liste verwandeln, sie umkehren und dann wieder zu einer Zeichenfolge machen.

omdha0
quelle
3
Hast du die Frage gelesen? Ihr Code ist genau der Code, den OP in der Frage als falsch erwiesen hat.
Washington Guedes
-1

const text = 'Hallo Welt👩‍🦰👩‍👩‍👦‍👦';

const reverse = text.split (''). reverse (). join ('');

console.log (umgekehrt);

asfaqe hussain
quelle