Karmaşık emojiler içeren bir dizeyi nasıl ters çevirebilirim?


193

Giriş:

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

Istenilen çıktı:

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

Birkaç yaklaşım denedim ama hiçbiri bana doğru cevabı vermedi.

Bu sefil bir şekilde başarısız oldu:

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

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

console.log(reversed);

Bu tür çalışır, ancak 👩‍👩‍👦‍👦4 farklı emojiye ayrılır:

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

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

console.log(reversed);

Ben de bu sorudaki her cevabı denedim ama hiçbiri işe yaramıyor.

İstenilen çıktıyı elde etmenin bir yolu var mı?


26
İkinci çözümdeki sorunu göremiyorum. Neyi kaçırıyorum?
Pedro Lima

13
Yani bu emojiler aslında bir şekilde kombinatoryal emojiler, oldukça ilginç. İlk olarak, iki karakterinizle temsil edilen kadının yüz emojisine sahipsiniz ve ardından 8205 karakter kodu olan fazladan bir bağlantı karakteri var ve sonra "kızıl saç" ı temsil eden iki daha var ve bu 5 karakter birlikte "Kızıl saçlı kadının yüzü" anlamına gelir
TKoL

11
Bir dizeyi birleşik emojilerle doğru şekilde tersine çevirmek oldukça karmaşık olurdu, bence. Her emojinin ardından 8205 karakter kodu gelip gelmediğini kontrol etmeniz gerekir ve eğer öyleyse, onu kendi karakteri gibi ele almak yerine önceki emoji ile birleştirmeniz gerekir. Oldukça karmaşık ...
TKoL

18
Javascript kafamı karıştırıyor. Düşük ve yüksek seviyeli dil kavramlarının en tuhaf karışımı. Belleği tamamen soyutlaması açısından (işaretçi yok, manuel bellek yönetimi), ancak dizeleri genişletilmiş grafem kümeleri yerine aptal kod noktaları olarak ele alacak kadar düşük düzeyde. Gerçekten kafa karıştırıcı ve bu şeyle çalışırken ne bekleyeceğimi asla bilmeme neden oluyor.
İskender - Eski Monica

12
@ Alexander-ReinstateMonica herhangi bir dil yoktur yapar varsayılan olarak sesletim bölerek bölme? JS yalnızca UTF-16 ile kodlanmış standart dizeler sağlar.
lights0123

Yanıtlar:


91

Yapabiliyorsanız_.split() , lodash tarafından sağlanan işlevi kullanın . Gönderen sürümü 4.0 itibaren, _.split()bölme unicode emoji'yi yeteneğine sahiptir.

.reverse().join('')Yerlileri 'karakterleri' tersine çevirmek için kullanmak, sıfır genişlikli birleştiriciler içeren emojilerde gayet iyi çalışmalıdır.

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>


3
"V4.9.0 - Emojilerle çalıştığından emin olunan _.split" den bahsettiğiniz değişiklik günlükleri, bence 4.0 çok erken olabilir. Dizeleri bölmek için kullanılan koddaki yorumlar ( github.com/lodash/lodash/blob/4.17.15/lodash.js#L261 ) , 2013 tarihli mathiasbynens.be/notes/javascript-unicode'a atıfta bulunmaktadır . o zamandan beri ilerlemiş gibi görünüyor, ancak birçok unicode regex'in şifresini çözmek oldukça zor. Ayrıca kod tabanlarında unicode bölme için herhangi bir test göremiyorum. Bütün bunlar beni onu üretimde kullanmaktan çekinir.
Michael Anderson

5
Bunun başarısız olduğunu bulmak için biraz araştırma yapıldı reverse("뎌쉐") (2 Kore grafiği), bu da "ᅰ셔 ᄃ" (3 grafik) verir.
Michael Anderson

2
Görünüşe göre bu problem için kolay bir yerel çözüm yok. Sadece bunu çözmek için bir kitaplığı içe aktarmayı tercih etmezdim, ama gerçekten de bu noktada bunu yapmanın en güvenilir / tutarlı yolu budur.
Hao Wu

1
Bunun doğru bir şekilde çalışmasını sağladığım için tebrikler 😎 Windows10'da Firefox'ta yazma yönünü tersine çevirmek hala biraz sorunlu (çocuklar arkada kalıyor), bu yüzden Lodash Windows 10'u yendi, sanırım bu muhtemelen biraz daha düşük bir bütçe 😅
yeoman

52

TKoL'nin \u200dkarakteri kullanma fikrini aldım ve daha küçük bir komut dosyası oluşturmaya çalışmak için kullandım.

Not: Tüm kompozisyonlar sıfır genişlikte bir birleştirici kullanmaz, bu nedenle diğer kompozisyon karakterleriyle hatalı olacaktır.

Geleneksel fordöngüyü kullanır çünkü birleşik ifadeler bulmamız durumunda bazı yinelemeleri atlarız. forDöngü içinde whileaşağıdaki \u200dkarakterin olup olmadığını kontrol etmek için bir döngü vardır . Bir tane olduğu sürece, sonraki 2 karakteri de ekleriz ve fordöngüyü 2 yineleme ile iletiriz, böylece birleşik ifadeler tersine çevrilmez.

Herhangi bir dizede kolayca kullanmak için, dizge nesnesinde yeni bir prototip işlevi olarak yaptım.

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());


5
Tarayıcılardaki metni sürükleyip 👩‍👩‍👦‍👦seçtiğinizde ancak bir bütün olarak seçilebileceğini düşünüyordum. Tarayıcılar bunun bir karakter olduğunu nasıl anlar? Bunu yapmanın yerleşik bir yolu var mı?
Hao Wu

10
@HaoWu bu "Grapheme Clusters" üzerinde "Unicode Segmentation" olarak bilinen şeydir. Tarayıcınız (işletim sisteminiz tarafından sağlanan tarayıcıyı kullanabilir), grafem kümesi başına seçime izin verecek ve işleyecektir. Burada spec okuyabilirsiniz: unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
lights0123

7
@HaoWu: "Tarayıcılar bunun bir karakter olduğunu nereden biliyor?" - It değil "bir karakter". Tek bir glif olarak işlenmiş tek bir grafik kümesi oluşturmak için bir araya gelen birden çok karakter .
Jörg W Mittag

6
Buradaki ile aynı ; tüm bileşimlerde sıfır genişlikli bir birleştirici kullanılmaz.
Holger

6
Bu, ZWJ ile oluşturulan karakterler dışında hiçbir şeyi doğru şekilde tersine çevirmez. Lütfen, sadece burada değil, genel bir kural olarak, bir test vakası için işe yarayan ısmarlama çözümleri hacklemek yerine, ne yaptıklarını bilen kişiler tarafından yazılan harici kitaplıkları kullanın. Rünler ve lodash kütüphaneleri (Ben de kefil olamaz) diğer cevaplar da tavsiye ediliyor.
benrg

46

Unicode metnini ters çevirmek, birçok nedenden dolayı zordur.

İlk olarak, programlama diline bağlı olarak, dizeler bir bayt listesi, UTF-16 kod birimleri listesi (16 bit genişliğinde, API'de genellikle "karakterler" olarak adlandırılır) veya ucs4 kod noktaları olarak farklı şekillerde temsil edilir. (4 bayt genişliğinde).

İkinci olarak, farklı API'ler bu iç gösterimi farklı derecelerde yansıtır. Bazıları baytların soyutlanması, bazıları UTF-16 karakterleri, bazıları kod noktaları üzerinde çalışır. Gösterim bayt veya UTF-16 karakterleri kullandığında, genellikle API'nin bu temsilin öğelerine erişim sağlayan bölümleri ve baytlardan (UTF-8 aracılığıyla) veya baytlardan almak için gerekli mantığı gerçekleştiren bölümleri vardır. UTF-16 karakterlerini gerçek kod noktalarına.

Genellikle, API'nin bu mantığı gerçekleştiren ve böylece kod noktalarına erişmenizi sağlayan bölümleri daha sonra eklenmiştir, çünkü ilk önce 7 bit ascii vardı, sonra biraz sonra herkes farklı kod sayfaları kullanarak 8 bitin yeterli olduğunu düşündü ve hatta daha sonra 16 bit unicode için yeterliydi. Sabit bir üst sınırı olmayan tamsayı sayıları olarak kod noktaları kavramı, geçmişte, metni mantıksal olarak kodlamak için dördüncü ortak karakter uzunluğu olarak eklenmiştir.

Gerçek kod noktalarına erişmenizi sağlayan bir API kullanmak bu kadar görünüyor. Fakat...

Üçüncüsü, bir sonraki kod noktasını veya aşağıdaki kod noktalarını etkileyen çok sayıda değiştirici kod noktası vardır. Örneğin, aşağıdaki a'yı ä, e'den ë'ye & c'ye çeviren bir aksan değiştirici var. Kod noktalarını ters çevirin ve aë, farklı harflerden oluşan eä olur. Örneğin, kendi kod noktası olarak ä'nın doğrudan bir temsili vardır, ancak değiştiriciyi kullanmak da aynı derecede geçerlidir.

Dördüncüsü, her şey sürekli akış halindedir. Örnekte kullanıldığı gibi emoji arasında birçok değiştirici vardır ve her yıl yenileri eklenmektedir. Bu nedenle, bir API, bir kod noktasının bir değiştirici olup olmadığına ilişkin bilgilere erişmenizi sağlarsa, API'nin sürümü, belirli bir yeni değiştiriciyi zaten bilip bilmediğini belirleyecektir.

Unicode, yalnızca görsel görünümle ilgili olduğu zaman için bir hile sağlar:

Yazı yönü değiştiricileri var. Örnek durumunda, soldan sağa yazma yönü kullanılır. Metnin başına bir sağdan sola yazma yönü değiştiricisi ekleyin ve API / tarayıcının sürümüne bağlı olarak doğru şekilde tersine çevrilmiş görünecektir 😎

"\ u202e", sağdan sola geçersiz kılma olarak adlandırılır, sağdan sola işaretçinin en güçlü sürümüdür.

W3.org tarafından hazırlanan bu açıklamaya bakın

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>


8
+1 çok yaratıcı bidi kullanımı (-: '\u202e' + text + '\u202c'Aşağıdaki metni etkilemekten kaçınmak için geçersiz kılmayı POP YÖNLÜ BİÇİMLENDİRME karakteriyle kapatmak daha güvenlidir .
Beni Cherniavsky-Paskin

2
Teşekkürler 😎 Bu oldukça hileli bir numara ve bağlantı verdiğim makale, html özelliklerini kullanmanın neden daha akıllıca olduğunu açıklayan birçok ayrıntıya giriyor, ancak bu şekilde hack'im için dize birleştirmeyi kullanabilirim 😂
yeoman

7
Btw. Bu makinedeki firefox'um (win 10) tamamen doğru anlamıyor, çocuklar sağdan sola yazarken ebeveynlerin arkasında, bu çok karmaşık emoji grupları değiştiricileriyle doğru yazma yönünü bulmak zor sanırım. ..
yeoman

2
Başka bir eğlenceli durum: bayrak emojileri için kullanılan bölgesel gösterge sembolleri. Eğer "🇦🇨" (iki kod noktası U + 1F1E6, U + 1F1E8, Yükseliş Adası için bayrak oluşturur) alır ve saf bir şekilde tersine çevirmeye çalışırsanız, Kanada bayrağı olan "🇨🇦" alırsınız.
Adam Rosenfield

2
@yeoman Bilginize: "UTF-16 karakterleri" (buradaki terimi kullandığınız gibi), aksi takdirde "UTF-16 kod birimleri " olarak bilinir . "Karakter", bir terim için çok belirsiz olma eğilimindedir, çünkü birçok şeye işaret edebilir (ancak Unicode bağlamında genellikle bir kod noktasıdır).
Inkling

38

Biliyorum! RegExp kullanacağım. Ne ters gidebilir? (Okuyucu için alıştırma olarak soldaki cevap.)

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

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

console.log(reversed);


5
Cevabınız özür dileyecek gibi görünüyor, ancak dürüst olmak gerekirse, bu cevabı standartlara yakın olarak adlandırıyorum. Aynı şeyi elle yapmaya çalışan diğer cevaplardan kesinlikle daha üstün. Karakter tabanlı metin işleme, normal ifadenin tasarlandığı ve üstün olduğu şeydir ve Unicode konsorsiyumu, gerekli normal ifade özelliklerini (bu durumda ECMAScript doğru şekilde uygulanır) açıkça standartlaştırır. Yani, birleştirme karakterler (IIRC normal ifade kulpların başarısız, bahsedilen gerekir ile ele .Joker).
Konrad Rudolph

14
U+200DÖrn 🏳️‍🌈. İle oluşturulmayan kompozisyonlarla çalışmaz . Oluşturulan karakterlerin Emijoi dünyasının dışında da var olduğunu belirtmekte fayda var ...
Holger

2
@StevenPenny 🏳️‍🌈 iki kompozisyon içerir ve bunlardan biri kullanmaz U+200D. 🏳️‍🌈'nin bu cevabın koduyla çalışmadığını doğrulamak kolaydır…
Holger

1
@Holger, 🏳️‍🌈'nin U + 200D ile oluşturulmamış bir kompozisyon içerdiği doğru olsa da, U + 200D ile bir kompozisyon içerdiği için oldukça kötü bir örnek. Daha iyi bir örnek 🧑🏻 veya 🏳️ gibi bir şey olabilir
Steven Penny

3
Buradaki diğer yorumların tersine, sıfır genişlikli bir birleştiricinin her kullanımı tek bir grafem kümesi olarak ele alınmamalıdır. Örneğin, Unicode 13 sesletim testi (son üç satır unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest.txt ) ZWJ farklı şekilde ele alınır, üç çok benzer durumları gösterir.
Michael Anderson

30

Alternatif çözüm, runeskitaplık kullanmak olabilir , küçük ama etkili bir çözüm:

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"

3
Bu en iyi cevap tbh. Diğer tüm yanıtların başarısız oldukları durumlar vardır, bu kitaplık (umarız) tüm uç durumları karşılar.
Carson Graham

1
İlk bakışta böyle "basit bir sorunun" çözülmesi kolay bir iş haline gelmemesi komik. Carson ile anlaşın - kitaplık, Emojiler gelişmeye devam ettikçe güncellemeler ve değişikliklerle ilerleyecektir.
Arnis Juraga

3
Görünüşe göre bu yaklaşık 3 yıldır güncellenmemiş. Unicode 11 o zamanlar hakkında piyasaya sürüldü, ancak o zamandan beri işler değişti, Unicode 13 daha sonra piyasaya sürüldü. 13'te genişletilmiş grafik kurallarında bazı değişiklikler vardı. Dolayısıyla, bunun üstesinden gelemeyeceği bazı uç durumlar olabilir. (Kodu incelemedim - ama dikkatli olmaya değer)
Michael Anderson

2
@MichaelAnderson'a katılıyorum, bu kitaplık saf veya eski bir algoritma kullanıyor gibi görünüyor. Bunu düzgün bir şekilde yapmak için Unicode'da belirtilen grafik bölümleme algoritmasını kullanmalıdır .
Inkling

20

Sadece emoji ile değil, aynı zamanda diğer kombinasyon karakterleriyle de sorun yaşıyorsunuz. Tek tek harfler gibi hissettiren, ancak aslında bir veya daha fazla unicode karakter olan bu şeylere "genişletilmiş grafik kümeleri" denir.

Bir dizeyi bu kümelere bölmek zordur (örneğin, bu unicode belgelerine bakın ). Kendim uygulamaya güvenmek yerine mevcut bir kütüphaneyi kullanırım. Google beni grapheme-splitter kitaplığına işaret etti. Bu kitaplığın belgeleri, çoğu uygulamayı tetikleyecek bazı güzel örnekler içerir :

Bunu kullanarak şunları yazabilmelisin:

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

YANINDE: Gelecekten gelen ziyaretçiler veya kanamanın eşiğinde yaşamaya istekli olanlar için:

JavaScript standardına bir grafik segmenter eklemek için bir teklif var . (Aslında başka bölümleme seçenekleri de sağlar). Şu anda kabul için aşama 3 incelemesinde ve şu anda JSC ve V8'de uygulanmaktadır (bkz. Https://github.com/tc39/proposal-intl-segmenter/issues/114 ).

Bunu kullanarak kod şöyle görünür:

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('');

Benden daha modern bir javascript biliyorsanız, muhtemelen bunu daha düzgün yapabilirsiniz ...

Burada bir uygulama var - ama ne gerektirdiğini bilmiyorum.

Not: Bu, diğer yanıtların henüz ele alınmadığı eğlenceli bir soruna işaret etmektedir. Segmentasyon, yalnızca dizedeki karakterlere değil, kullandığınız yerel ayara bağlı olabilir.


1
Görünüşe göre kod yaklaşık 2 yıldır güncellenmemiş - bu nedenle tabloları güncel olmayabilir. Bu nedenle, daha yeni bir şey aramanız gerekebilir.
Michael Anderson

3
Görünüşe göre bu kitaplığın daha yeni bir çatalı github.com/flmnt/graphemer
Michael Anderson

4
Aslında doğru olan cevabı görmek için bu kadar aşağı kaydırmam gerektiğine şaşırdım.
Lambda Fairy

1
Teklif örneği için yapabilirsin const graphemes = Array.from(segment_iterator, ({segment}) => segment).
Inkling

17

Sadece eğlence için yapmaya karar verdim, iyi bir meydan okumaydı. Her durumda doğru olduğundan emin değilsiniz, bu yüzden kendi sorumluluğunuzda kullanın, ancak işte burada:

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();


1
Aslında uzun çünkü hata ayıklama bilgileri. Gerçekten minnettarım
Hao Wu

1
@AndrewSavinykh Kod golfü değil, daha zarif bir çözüm arıyordu. Belki tek satırlık deliler gibi değil, ama hatırlaması kolay. Normal ifade çözümü gibi gerçekten iyi bir imho.
Hao Wu

0

Kullanabilirsiniz:

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

Dizinizi bir listeye dönüştürmeli, ters çevirmeli ve sonra tekrar dizgiye dönüştürmelidir.


3
Soruyu okudun mu Kodunuz, soruda yanlış olduğu kanıtlanan koddur.
Washington Guedes

-1

const text = 'Merhaba dünya👩‍🦰👩‍👩‍👦‍👦';

const tersine çevrildi = metin.split (''). ters (). birleştir ('');

console.log (tersine çevrilmiş);

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.