복잡한 이모티콘이 포함 된 문자열을 바꾸는 방법은 무엇입니까?


194

입력:

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

원하는 출력 :

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

몇 가지 접근 방식을 시도했지만 정답을 얻지 못했습니다.

이것은 비참하게 실패했습니다.

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

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

console.log(reversed);

이것은 다소 작동하지만 👩‍👩‍👦‍👦4 개의 다른 이모티콘으로 나뉩니다.

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

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

console.log(reversed);

나는 또한 이 질문에 대한 모든 대답을 시도 했지만 아무것도 작동하지 않습니다.

원하는 출력을 얻을 수있는 방법이 있습니까?


26
두 번째 솔루션의 문제를 볼 수 없습니다. 내가 무엇을 놓치고 있습니까?
Pedro Lima

13
따라서이 이모티콘은 실제로 조합 이모티콘입니다. 꽤 흥미 롭습니다. 먼저, 여성의 얼굴 이모티콘이 있고, 그 자체가 두 명의 캐릭터로 표시되고 , 추가 연결 캐릭터 인 charcode 8205가 있고, "빨간 머리"를 나타내는 또 다른 2 개가 있고 그 5 개의 문자가 함께 있습니다. 평균 '빨간 머리 여자 얼굴'
TKoL

11
이모지가 결합 된 문자열을 제대로 뒤집는 것은 꽤 복잡 할 것이라고 생각합니다. 각 이모 지 뒤에 문자 코드 8205가 오는지 확인해야하며, 그럴 경우 자신의 캐릭터로 취급하는 대신 이전 이모 지와 결합해야합니다. 예쁜 ... 복잡
TKoL

19
Javascript가 나를 혼란스럽게합니다. 낮은 수준과 높은 수준의 언어 개념이 가장 이상하게 혼합 된 것입니다. 그것은 완전히 메모리를 추상화한다는 점에서 수준이지만 (포인터 없음, 수동 메모리 관리) 문자열을 확장 된 자소 클러스터가 아닌 멍청한 코드 포인트로 취급 할 정도로 낮은 수준입니다. 정말 혼란스럽고이 작업을 할 때 무엇을 기대해야하는지 결코 알 수 없습니다.
Alexander는

12
@ 알렉산더 - ReinstateMonica은 모든 언어가 않습니다 기본적으로 그래 핀 분리에 의해 분할은? JS는 UTF-16으로 인코딩 된 표준 문자열을 제공합니다.
lights0123

답변:


94

가능하다면 lodash에서_.split() 제공 하는 함수를 사용 하세요 . 에서 버전 4.0 이후, _.split()분할 유니 코드 이모티콘 할 수 있습니다.

네이티브 .reverse().join('')를 사용하여 '문자'를 반전하면 너비가 0 인 조이너를 포함하는 이모 지에서도 잘 작동합니다.

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-_.split이 이모 지와 함께 작동하는지 확인했습니다"라고 언급 한 변경 로그는 4.0이 너무 이른 것 같습니다. 문자열을 분할하는 데 사용되는 코드 ( github.com/lodash/lodash/blob/4.17.15/lodash.js#L261 )의 주석은 2013 년 에 작성된 mathiasbynens.be/notes/javascript-unicode 를 참조합니다 . 그 이후로 진행된 것처럼 보이지만 많은 유니 코드 정규식을 해독하기에는 꽤 어렵습니다. 또한 코드베이스에서 유니 코드 분할에 대한 테스트를 볼 수 없습니다. 이 모든 것이 프로덕션에서 사용하는 것을 경계하게 만들 것입니다.
Michael Anderson

5
reverse("뎌쉐") "ᅰ셔 ᄃ"(3 개의 자소)을주는 이것이 실패 (한글 자소 2 개) 를 찾는 데는 약간의 검색이 필요했습니다 .
Michael Anderson

2
이 문제에 대한 쉬운 네이티브 솔루션이없는 것 같습니다. 이 문제를 해결하기 위해 라이브러리를 가져 오는 것을 선호하지는 않지만 실제로이 시점에서 가장 안정적이고 일관된 방법입니다.
Hao Wu

1
윈도우 10에 파이어 폭스에서 작성 방향을 😎 제대로 작동하려면이 점점 반전을위한 명예는 여전히 윈도우 10, 내 생각, 가능성이 다소 낮은 예산 😅이라도 약간의 glitchy (아이들이 후면에 끝낼), 그래서 lodash 비트입니다
유사시

54

TKoL의 \u200d캐릭터 사용 아이디어를 가져 와서 더 작은 스크립트를 만드는 데 사용했습니다.

참고 : 모든 컴포지션이 너비가 0 인 조이너를 사용하는 것은 아니므로 다른 컴포지션 문자에 버그가있을 수 있습니다.

for결합 된 이모티콘을 찾을 경우 일부 반복을 건너 뛰기 때문에 전통적인 루프를 사용합니다 . 내에서 for루프 A가 while뒤에 오는 것의가 있는지 확인하는 루프 \u200d문자. 하나가있는 한 다음 2 개의 문자도 추가하고 for2 개의 반복으로 루프를 전달하여 결합 된 이모티콘이 반전되지 않도록합니다.

모든 문자열에서 쉽게 사용하기 위해 문자열 객체에 대한 새로운 프로토 타입 함수로 만들었습니다.

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
브라우저에서 텍스트를 드래그하여 선택하면 👩‍👩‍👦‍👦전체적으로 만 선택할 수 있다고 생각했습니다 . 브라우저는 그것이 한 문자임을 어떻게 알 수 있습니까? 이를 수행하는 기본 제공 방법이 있습니까?
Hao Wu

10
@HaoWu 이것은 "그래프 소 클러스터"에서 "유니 코드 분할"로 알려진 것입니다. 귀하의 브라우저 (귀하의 OS에서 제공하는 것을 사용할 수 있음)가 렌더링되고 자소 클러스터 당 선택을 허용합니다. 사양은 여기에서 읽을 수 있습니다. unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
lights0123

7
@HaoWu : "브라우저가 한 문자인지 어떻게 알 수 있습니까?" – "한 문자" 가 아닙니다 . 여러 문자가 결합되어 단일 문자 소 클러스터 를 형성하고 단일 문자 모양으로 렌더링됩니다 .
Jörg W Mittag

6
여기와 동일합니다 . 모든 컴포지션이 0 너비 결합자를 사용하는 것은 아닙니다.
Holger

6
이것은 ZWJ로 구성된 문자를 제외하고는 올바르게 반전되지 않습니다. 여기뿐만 아니라 일반적으로 한 테스트 케이스에서 작동하는 맞춤형 솔루션을 해킹하는 대신 자신이하는 일을 알고있는 사람들이 작성한 외부 라이브러리를 사용하십시오. lodash 라이브러리 (I 중 하나에 대한 신뢰도를 보장 할 수 없습니다) 다른 답변에 추천했다.
benrg

47

유니 코드 텍스트를 뒤집는 것은 여러 가지 이유로 까다 롭습니다.

첫째, 프로그래밍 언어에 따라 문자열은 바이트 목록, UTF-16 코드 단위 목록 (16 비트 너비, API에서 "문자"라고도 함) 또는 ucs4 코드 포인트로 다른 방식으로 표현됩니다. (4 바이트 너비).

둘째, 다른 API는 내부 표현을 다른 각도로 반영합니다. 일부는 바이트 추상화, 일부는 UTF-16 문자, 일부는 코드 포인트에서 작업합니다. 표현이 바이트 또는 UTF-16 문자를 사용하는 경우 일반적으로이 표현의 요소에 대한 액세스를 제공하는 API 부분과 바이트 (UTF-8을 통해)에서 가져 오는 데 필요한 논리를 수행하는 부분이 있습니다. 실제 코드 포인트에 대한 UTF-16 문자.

종종 해당 로직을 수행하고 따라서 코드 포인트에 대한 액세스를 제공하는 API 부분이 나중에 추가되었습니다. 처음에는 7 비트 ASCII가 있었고 조금 후에 모두가 다른 코드 페이지를 사용하여 8 비트로 충분하다고 생각했습니다. 나중에는 16 비트가 유니 코드로 충분했습니다. 고정 된 상한이없는 정수로 코드 포인트의 개념은 역사적으로 텍스트를 논리적으로 인코딩하기위한 네 번째 공통 문자 길이로 추가되었습니다.

실제 코드 포인트에 대한 액세스를 제공하는 API를 사용하는 것은 그게 다인 것 같습니다. 그러나...

셋째, 다음 코드 포인트 또는 다음 코드 포인트에 영향을 미치는 수정 자 코드 포인트가 많이 있습니다. 예를 들어 a를 ä, e에서 ë, & c로 바꾸는 분음 부호 수식어가 있습니다. 코드 포인트를 돌리면 aë는 다른 문자로 만들어진 eä가됩니다. 예를 들어 ä를 자체 코드 포인트로 직접 표현하지만 수정자를 사용하는 것도 똑같이 유효합니다.

넷째, 모든 것이 지속적으로 유동적입니다. 예에서 사용 된 것처럼 이모 지에는 많은 수정자가 있으며 매년 더 많이 추가됩니다. 따라서 API가 코드 포인트가 수정 자인지 여부에 대한 정보에 대한 액세스를 제공하는 경우 API 버전은 특정 새 수정자를 이미 알고 있는지 여부를 결정합니다.

하지만 유니 코드는 시각적 인 모양에 관한 문제 일 때 해키 트릭을 제공합니다.

쓰기 방향 수정자가 있습니다. 예제의 경우 왼쪽에서 오른쪽 쓰기 방향이 사용됩니다. 텍스트 시작 부분에 오른쪽에서 왼쪽 쓰기 방향 수정자를 추가하고 API / 브라우저 버전에 따라 올바르게 반전 된 것처럼 보입니다 😎

'\ u202e'는 오른쪽에서 왼쪽으로 재정의라고하며 오른쪽에서 왼쪽으로 표시되는 가장 강력한 버전입니다.

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>


8
양방향 (+1 매우 창조적 인 사용 - : 그것은 팝 방향 서식 문자로 재정의를 닫 안전합니다 '\u202e' + text + '\u202c'다음 텍스트에 영향을주지 않도록 할 수 있습니다.
베니 Cherniavsky-Paskin

2
감사합니다 😎 그것은 꽤 해키 트릭 나는에 링크 된 기사의 똑똑한 그것의 방법은 HTML 속성을 사용하지만,이 방법 난 그냥 내 해킹 😂에 대한 문자열 연결을 사용할 수있는 이유를 설명 많은 세부로 간다
유사시

7
Btw. 이 컴퓨터의 내 파이어 폭스 (승리 10)는 완전히 옳지 않습니다. 오른쪽에서 왼쪽으로 쓸 때 아이들은 부모 뒤에 있습니다.이 엄청나게 복잡한 이모 지 그룹 수정 자로 쓰기 방향을 올바르게 잡는 것이 어렵습니다. ...
유사시

2
또 다른 재미있는 경우 : 깃발 이모티콘에 사용되는 지역 표시기 기호입니다. 문자열 "🇦🇨"(두 개의 코드 포인트 U + 1F1E6, U + 1F1E8, Ascension Island의 깃발 만들기)을 가져와 순진하게 뒤집으려고하면 캐나다 깃발 인 "🇨🇦"가 표시됩니다.
Adam Rosenfield

2
@yeoman 참고 : "UTF-16 문자"(여기에서 사용하는 용어)는 "UTF-16 코드 단위 " 라고도 합니다. "문자"는 많은 것을 참조 할 수 있기 때문에 용어에 대해 너무 모호한 경향이 있습니다 (그러나 유니 코드의 컨텍스트에서는 일반적으로 코드 포인트).
Inkling 2010 년

39

알아! RegExp를 사용하겠습니다. 무엇이 잘못 될 수 있습니까? (답변은 독자를위한 연습 문제로 남았습니다.)

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

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

console.log(reversed);


5
귀하의 답변은 사과적인 것처럼 들리지만 솔직히 저는이 답변을 정식에 가깝다고하겠습니다. 동일한 작업을 수동으로 시도하는 다른 답변보다 확실히 우수합니다. 문자 기반 텍스트 조작은 regex가 설계되고 탁월한 기능이며 유니 코드 컨소시엄은 필요한 정규식 기능을 명시 적으로 표준화합니다 (이 경우 ECMAScript가 올바르게 구현 됨). 즉, 결합 문자 (IIRC 정규식 . 와일드 카드로 처리 해야 함) 를 처리하지 못합니다 .
Konrad Rudolph

14
로 빌드되지 않은 컴포지션에서는 작동하지 않습니다 ( U+200D예 : 🏳️‍🌈. 작곡 된 캐릭터는 Emijoi 세계 외부에도 존재한다는 점은 주목할 가치가 있습니다.
Holger

2
@StevenPenny 🏳️‍🌈에는 두 개의 작곡이 포함되어 있으며 그중 하나는 U+200D. 그것은 ...이 답변의 코드 🏳️🌈 일을하지 않는 것을 확인하기 쉽다
홀거

1
@Holger는 사실 🏳️‍🌈에 U + 200D로 빌드되지 않은 컴포지션이 포함되어 있지만 U + 200D로 빌드 된 컴포지션도 포함되어 있으므로 꽤 나쁜 예입니다. 더 좋은 예는 🧑🏻 또는 🏳️ 같은 것
스티븐 페니

3
여기에있는 다른 주석과 반대로, 너비가 0 인 조이너의 모든 사용이 단일 자소 클러스터로 취급되어야하는 것은 아닙니다. 예를 들어, unicode 13 grapheme 테스트의 마지막 세 줄 ( unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest.txt )은 ZWJ가 다르게 처리되는 세 가지 매우 유사한 경우를 보여줍니다.
Michael Anderson

32

대체 솔루션은 runes작지만 효과적인 라이브러리 를 사용하는 것입니다 .

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
이것은 최고의 답변 tbh입니다. 이 다른 모든 답변에는 실패한 경우가 있으며이 라이브러리는 모든 가장자리 사례를 충족합니다.
Carson Graham

1
이러한 "간단한 질문"이 처음에 해결하기 쉬운 작업이 아니라는 점이 재밌습니다. Carson과 동의하십시오-라이브러리는 Emojis가 계속 진화함에 따라 업데이트 및 변경 사항으로 앞으로 나아갈 것입니다.
아르 니스 Juraga

3
약 3 년 동안 업데이트되지 않은 것 같습니다. 유니 코드 11은 그 무렵에 출시되었지만 그 이후로 상황이 바뀌었고 나중에 유니 코드 13이 출시되었습니다. 13에서 확장 된 자소 규칙에 약간의 변경이있었습니다. 그래서 이것이 처리하지 못하는 몇 가지 극단적 인 경우가있을 수 있습니다. (필자는 코드를 보았다 적이 없다 -하지만 그것은 가치와 조심입니다)
마이클 앤더슨

2
@MichaelAnderson에 동의합니다.이 라이브러리는 순진하거나 오래된 알고리즘을 사용하는 것으로 보입니다. 이 작업을 제대로 수행하려면 유니 코드로 지정된 자소 분할 알고리즘을 사용해야합니다 .
Inkling 2010 년

21

이모 지뿐만 아니라 다른 문자 조합에도 문제가 있습니다. 개별 문자처럼 느껴지지만 실제로는 하나 이상의 유니 코드 문자 인 이러한 것들을 "확장 된 자소 클러스터"라고합니다.

문자열을 이러한 클러스터로 나누는 것은 까다 롭습니다 (예 : 이러한 유니 코드 문서 참조 ). 직접 구현하는 데 의존하지 않고 기존 라이브러리를 사용합니다. Google은 grapheme-splitter 라이브러리를 가리 켰습니다 . 이 라이브러리의 문서에는 대부분의 구현을 방해 하는 몇 가지 멋진 예제 가 포함되어 있습니다 .

이것을 사용하면 다음과 같이 작성할 수 있습니다.

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

ASIDE : 미래의 방문객 또는 최첨단에서 살기를 원하는 사람들을 위해 :

자바 스크립트 표준에 grapheme segmenter를 추가 하는 제안 이 있습니다. (실제로 다른 분할 옵션도 제공합니다). 현재 승인을 위해 3 단계 검토 중이며 현재 JSC 및 V8에서 구현되고 있습니다 ( https://github.com/tc39/proposal-intl-segmenter/issues/114 참조 ).

이것을 사용하면 코드는 다음과 같습니다.

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

나보다 더 현대적인 자바 스크립트를 알고 있다면 아마도 이것을 깔끔하게 만들 수 있습니다 ...

여기에 구현이 있지만 필요한 것이 무엇인지 모르겠습니다.

참고 : 이것은 다른 답변이 아직 해결하지 못한 재미있는 문제를 지적합니다. 세그먼트 화는 문자열의 문자뿐만 아니라 사용중인 로케일에 따라 달라질 수 있습니다.


1
코드가 약 2 년 동안 업데이트되지 않은 것 같습니다. 따라서 테이블이 최신 상태가 아닐 수 있습니다. 따라서 더 최근의 것을 검색해야 할 수도 있습니다.
Michael Anderson

3
이 라이브러리의 최신 포크처럼 보인다는에서 확인할 수있다 github.com/flmnt/graphemer
마이클 앤더슨

4
실제로 정확한 답을보기 위해 여기까지 스크롤해야한다는 것에 놀랐습니다.
Lambda Fairy

1
제안 예의 경우 할 수 있습니다 const graphemes = Array.from(segment_iterator, ({segment}) => segment).
Inkling

17

그냥 재미로하기로했는데 좋은 도전 이었어요. 모든 경우에 올바른지 확신 할 수 없으므로 위험을 감수하고 사용하십시오.

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
글쎄, 실제로는 디버그 정보 때문에 길다. 정말 감사 그
하오 우

1
@AndrewSavinykh 코드 골프는 아니지만 더 우아한 솔루션을 찾고있었습니다. 한 줄짜리 미친 짓은 아니지만 기억하기 쉽습니다. 같은 정규식 솔루션은 정말 좋은 이럴입니다.
Hao Wu

0

당신이 사용할 수있는:

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

문자열을 목록으로 바꾸고 뒤집은 다음 다시 문자열로 만들어야합니다.


3
질문을 읽었습니까? 귀하의 코드는 정확히 OP가 질문에서 잘못 입증 된 코드입니다.
Washington Guedes 20.10.21

-1

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

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

console.log (역방향);

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.