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

입력:

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

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

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



답변

가능하다면 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>


답변

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


답변

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

첫째, 프로그래밍 언어에 따라 문자열은 바이트 목록, 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>


답변

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

const text = 'Hello world?‍??‍?‍?‍?';

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

console.log(reversed);


답변

대체 솔루션은 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"


답변

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

문자열을 이러한 클러스터로 나누는 것은 까다 롭습니다 (예 : 이러한 유니 코드 문서 참조 ). 직접 구현하는 데 의존하지 않고 기존 라이브러리를 사용합니다. 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('');

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

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

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


답변

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

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