Làm thế nào để đảo ngược một chuỗi có chứa các biểu tượng cảm xúc phức tạp?


193

Đầu vào:

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

Kết quả mong muốn:

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

Tôi đã thử một số cách tiếp cận nhưng không có cách nào cho tôi câu trả lời chính xác.

Điều này thất bại thảm hại:

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

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

console.log(reversed);

Loại này hoạt động nhưng nó chia 👩‍👩‍👦‍👦thành 4 biểu tượng cảm xúc khác nhau:

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

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

console.log(reversed);

Tôi cũng đã thử mọi câu trả lời trong câu hỏi này nhưng không có câu trả lời nào hoạt động.

Có cách nào để có được đầu ra mong muốn?


26
Tôi không thể thấy vấn đề với giải pháp thứ hai. Tôi đang thiếu gì?
Pedro Lima

13
Vì vậy, những biểu tượng cảm xúc này thực sự là biểu tượng cảm xúc tổ hợp bằng cách nào đó, nó khá thú vị. Đầu tiên, bạn có biểu tượng cảm xúc khuôn mặt phụ nữ, biểu tượng này được đại diện bởi hai trong số các ký tự của bạn , sau đó có thêm một ký tự kết nối, đó là mã charcode 8205, và sau đó có hai khác đại diện cho "tóc đỏ" và 5 ký tự đó cùng nhau có nghĩa là 'khuôn mặt phụ nữ với mái tóc đỏ'
TKoL

11
Tôi nghĩ để đảo ngược một chuỗi với các biểu tượng cảm xúc kết hợp sẽ khá phức tạp. Bạn phải kiểm tra xem mỗi biểu tượng cảm xúc có được theo sau bởi mã charcode 8205 hay không, và nếu có thì bạn phải kết hợp nó với biểu tượng cảm xúc trước đó thay vì coi nó như một ký tự riêng. Khá phức tạp ...
TKoL

18
Javascript làm tôi bối rối. Đó là sự pha trộn kỳ lạ nhất giữa các khái niệm ngôn ngữ cấp thấp và cấp cao. Đây là cấp độ mà nó hoàn toàn trừu tượng hóa bộ nhớ (không có con trỏ, quản lý bộ nhớ thủ công), nhưng cấp độ thấp đến mức coi các chuỗi như các điểm mã câm thay vì các cụm grapheme mở rộng. Nó thực sự khó hiểu, và nó khiến tôi không bao giờ biết phải mong đợi điều gì khi làm việc với thứ này.
Alexander - Phục hồi Monica

12
@ Alexander-ReinstateMonica có ngôn ngữ nào thực hiện chia tách bằng cách tách grapheme theo mặc định không? JS chỉ cung cấp các chuỗi tiêu chuẩn được mã hóa trong UTF-16.
lights0123

Câu trả lời:


91

Nếu bạn có thể, hãy sử dụng _.split()chức năng do lodash cung cấp . Từ phiên bản 4.0 trở đi, _.split()có khả năng tách các biểu tượng cảm xúc unicode.

Sử dụng bản gốc .reverse().join('')để đảo ngược các 'ký tự' sẽ hoạt động tốt với các biểu tượng cảm xúc có chứa các bộ nối không độ rộng

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
Các thay đổi mà bạn đề cập đến "v4.9.0 - _.split đảm bảo hoạt động với biểu tượng cảm xúc", tôi nghĩ 4.0 có thể là quá sớm. Nhận xét trong mã được sử dụng để chia chuỗi ( github.com/lodash/lodash/blob/4.17.15/lodash.js#L261 ) tham chiếu đến mathiasbynens.be/notes/javascript-unicode có từ năm 2013. Nó Có vẻ như nó đã tiếp tục kể từ đó, nhưng nó sử dụng khá nhiều khó khăn để giải mã các regexes unicode. Tôi cũng không thể thấy bất kỳ thử nghiệm nào trong cơ sở mã của họ để tách mã unicode. Tất cả những điều này sẽ khiến tôi cảnh giác khi sử dụng nó trong sản xuất.
Michael Anderson

5
Chỉ mất một chút tìm kiếm để tìm ra rằng điều này không thành công reverse("뎌쉐") (2 grapheme Hàn Quốc) cho "ᅰ셔 ᄃ" (3 grapheme).
Michael Anderson

2
Có vẻ như không có giải pháp gốc dễ dàng cho vấn đề này. Bạn không muốn nhập một thư viện chỉ để giải quyết vấn đề này, nhưng nó thực sự là cách đáng tin cậy / nhất quán nhất để làm điều đó vào thời điểm này.
Hao Wu

1
Kudo để làm cho điều này hoạt động chính xác 😎 Đảo ngược hướng viết trong Firefox trên Windows10 vẫn còn là một chút trục trặc (trẻ em cuối cùng ở phía sau), vì vậy, tôi đoán rằng lodash đánh bại Windows 10, có khả năng ngân sách thấp hơn một chút 😅
yeoman

52

Tôi lấy ý tưởng sử dụng \u200dký tự của TKoL và sử dụng nó để cố gắng tạo ra một tập lệnh nhỏ hơn.

Lưu ý: Không phải tất cả các tác phẩm đều sử dụng ký tự nối chiều rộng bằng không, vì vậy nó sẽ có lỗi với các ký tự sáng tác khác.

Nó sử dụng forvòng lặp truyền thống vì chúng tôi bỏ qua một số lần lặp trong trường hợp chúng tôi tìm thấy các biểu tượng cảm xúc kết hợp. Trong forvòng lặp có một whilevòng lặp để kiểm tra xem có \u200dký tự sau không . Miễn là có một, chúng tôi cũng thêm 2 ký tự tiếp theo và chuyển tiếp forvòng lặp với 2 lần lặp để các biểu tượng cảm xúc kết hợp không bị đảo ngược.

Để dễ dàng sử dụng nó trên bất kỳ chuỗi nào, tôi đã đặt nó làm hàm nguyên mẫu mới trên đối tượng chuỗi.

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
Tôi đã nghĩ, khi bạn kéo và chọn văn bản trên trình duyệt, 👩‍👩‍👦‍👦chỉ có thể được chọn toàn bộ. Làm thế nào để trình duyệt biết đó là một ký tự? Có một cách tích hợp để làm điều đó?
Hao Wu

10
@HaoWu đây được gọi là "Phân đoạn Unicode" trên "Grapheme Clusters". Trình duyệt của bạn (có thể sử dụng trình duyệt do hệ điều hành của bạn cung cấp) sẽ hiển thị và cho phép lựa chọn trên mỗi cụm grapheme. Bạn có thể đọc thông số kỹ thuật tại đây: unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
lights0123

7
@HaoWu: "Làm thế nào để trình duyệt biết đó là một ký tự?" - Nó không phải là "một nhân vật". Đó là nhiều ký tự kết hợp để tạo thành một cụm grapheme duy nhất , được hiển thị dưới dạng một glyph duy nhất .
Jörg W Mittag

6
Giống như ở đây ; không phải tất cả các tác phẩm đều sử dụng bộ nối chiều rộng bằng không.
Holger

6
Điều này không đảo ngược chính xác bất cứ điều gì ngoài các ký tự được tạo bằng ZWJ. Xin vui lòng, không chỉ ở đây mà theo quy tắc chung, hãy sử dụng các thư viện bên ngoài được viết bởi những người biết họ đang làm gì, thay vì hack các giải pháp dành riêng cho một trường hợp thử nghiệm. Các thư viện runelodash được đề xuất trong các câu trả lời khác (tôi cũng không thể xác nhận).
benrg

46

Đảo ngược văn bản Unicode rất khó vì nhiều lý do.

Đầu tiên, tùy thuộc vào ngôn ngữ lập trình, các chuỗi được biểu diễn theo các cách khác nhau, hoặc dưới dạng danh sách byte, danh sách các đơn vị mã UTF-16 (rộng 16 bit, thường được gọi là "ký tự" trong API) hoặc dưới dạng điểm mã ucs4 (Rộng 4 byte).

Thứ hai, các API khác nhau phản ánh sự thể hiện bên trong đó ở các mức độ khác nhau. Một số hoạt động dựa trên sự trừu tượng của byte, một số dựa trên ký tự UTF-16, một số dựa trên các điểm mã. Khi biểu diễn sử dụng byte hoặc ký tự UTF-16, thường có các phần của API cung cấp cho bạn quyền truy cập vào các phần tử của biểu diễn này, cũng như các phần thực hiện logic cần thiết để lấy từ byte (thông qua UTF-8) hoặc từ UTF-16 ký tự cho điểm mã thực tế.

Thông thường, các phần của API thực hiện logic đó và do đó cung cấp cho bạn quyền truy cập vào các điểm mã đã được thêm vào sau đó, vì đầu tiên có 7 bit ascii, sau đó một chút mọi người nghĩ rằng 8 bit là đủ, sử dụng các trang mã khác nhau và thậm chí sau đó 16 bit là đủ cho unicode. Khái niệm về điểm mã là số nguyên không có giới hạn trên cố định đã được thêm vào lịch sử làm độ dài ký tự chung thứ tư để mã hóa văn bản một cách logic.

Sử dụng một API cho phép bạn truy cập vào các điểm mã thực tế có vẻ như vậy. Nhưng...

Thứ ba, có rất nhiều điểm mã bổ trợ ảnh hưởng đến điểm mã tiếp theo hoặc các điểm mã tiếp theo. Ví dụ: có một công cụ sửa đổi dấu phụ chuyển sau a thành ä, e thành ë, & c. Xoay các điểm mã xung quanh và aë trở thành eä, được tạo từ các chữ cái khác nhau. Có một biểu diễn trực tiếp của ví dụ ä như là điểm mã riêng của nó nhưng sử dụng công cụ sửa đổi cũng hợp lệ.

Thứ tư, mọi thứ đều liên tục. Ngoài ra còn có rất nhiều công cụ sửa đổi trong số các biểu tượng cảm xúc, như được sử dụng trong ví dụ và nhiều hơn nữa được thêm vào mỗi năm. Do đó, nếu một API cấp cho bạn quyền truy cập vào thông tin liệu một điểm mã có phải là công cụ sửa đổi hay không, thì phiên bản của API sẽ xác định xem nó đã biết một công cụ sửa đổi mới cụ thể hay chưa.

Tuy nhiên, Unicode cung cấp một thủ thuật hacky khi nó chỉ về hình thức:

Có bổ ngữ hướng viết. Trong trường hợp của ví dụ, hướng viết từ trái sang phải được sử dụng. Chỉ cần thêm công cụ sửa đổi hướng viết từ phải sang trái ở đầu văn bản và tùy thuộc vào phiên bản của API / trình duyệt, nó sẽ được đảo ngược một cách chính xác 😎

'\ u202e' được gọi là ghi đè từ phải sang trái, đây là phiên bản mạnh nhất của điểm đánh dấu từ phải sang trái.

Xem lời giải thích này của 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 cách sử dụng bidi rất sáng tạo (-: An toàn hơn khi đóng ghi đè bằng biểu đồ ĐỊNH DẠNG HƯỚNG POP '\u202e' + text + '\u202c'để tránh ảnh hưởng đến văn bản sau.
Beni Cherniavsky-Paskin

2
Cảm ơn 😎 Đây là một thủ thuật khá hack và bài viết tôi liên kết đi vào rất nhiều chi tiết giải thích lý do tại sao cách sử dụng các thuộc tính html thông minh hơn nhưng theo cách này, tôi chỉ có thể sử dụng nối chuỗi cho bản hack của mình 😂
yeoman

7
Btw. firefox của tôi trên máy này (win 10) không hoàn toàn đúng, trẻ em đứng sau cha mẹ khi viết từ phải sang trái, tôi đoán thật khó để có được hướng viết đúng với các công cụ sửa đổi nhóm biểu tượng cảm xúc phức tạp này. ..
yeoman

2
Một trường hợp thú vị khác: các biểu tượng chỉ báo khu vực được sử dụng cho biểu tượng cảm xúc cờ. Nếu bạn lấy chuỗi "🇦🇨" (hai điểm mã U + 1F1E6, U + 1F1E8, làm cờ cho Đảo Thăng thiên) và cố gắng đảo ngược nó một cách ngây thơ, bạn sẽ nhận được "🇨🇦", cờ cho Canada.
Adam Rosenfield

2
@yeoman FYI: "Ký tự UTF-16" (như bạn đang sử dụng thuật ngữ ở đây) còn được gọi là " đơn vị mã UTF-16 ". "Ký tự" có xu hướng là một thuật ngữ quá mơ hồ vì nó có thể đề cập đến rất nhiều thứ (nhưng trong ngữ cảnh của Unicode thường là một điểm mã).
Inkling

38

Tôi biết! Tôi sẽ sử dụng RegExp. Điều gì có thể xảy ra? (Câu trả lời còn lại như một bài tập cho người đọc.)

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

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

console.log(reversed);


5
Câu trả lời của bạn nghe có vẻ có lỗi nhưng thành thật mà nói, tôi gọi câu trả lời này gần với câu trả lời chuẩn. Nó chắc chắn vượt trội so với các câu trả lời khác cố gắng làm điều tương tự theo cách thủ công. Thao tác văn bản dựa trên ký tự là thứ mà regex được thiết kế và vượt trội hơn cả, và tập đoàn Unicode tiêu chuẩn hóa rõ ràng các tính năng regex cần thiết (trong trường hợp này, ECMAScript sẽ triển khai chính xác). Điều đó nói rằng, nó không xử lý được việc kết hợp các ký tự (mà IIRC regex sẽ xử lý bằng các .ký tự đại diện).
Konrad Rudolph

14
Không làm việc với tác phẩm không được xây dựng với U+200D, ví dụ 🏳️‍🌈. Cần lưu ý rằng các nhân vật sáng tác cũng tồn tại bên ngoài thế giới Emijoi…
Holger

2
@StevenPenny 🏳️‍🌈 chứa hai tác phẩm và một trong số chúng không sử dụng U+200D. Thật dễ dàng để xác minh rằng 🏳️‍🌈 không hoạt động với mã của câu trả lời này…
Holger

1
@Holger trong khi đúng là 🏳️‍🌈 chứa một bố cục không được xây dựng bằng U + 200D, đây là một ví dụ khá tệ vì nó cũng chứa bố cục với U + 200D. Một ví dụ tốt hơn sẽ là một cái gì đó như 🧑🏻 hoặc 🏳️
Steven Penny

3
Ngược lại với các nhận xét khác ở đây, không phải mọi việc sử dụng phép nối chiều rộng bằng không đều nên được coi là một cụm grapheme duy nhất. Ví dụ: ba dòng cuối cùng của bài kiểm tra grapheme unicode 13 ( unicode.org/Public/13.0.0/ucd/auxvacy/GraphemeBreakTest.txt ) hiển thị ba trường hợp rất giống nhau trong đó ZWJ được xử lý khác nhau.
Michael Anderson

30

Giải pháp thay thế sẽ là sử dụng runesthư viện, giải pháp nhỏ nhưng hiệu quả:

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
Đây là câu trả lời tốt nhất tbh. Tất cả các câu trả lời khác này đều có trường hợp thất bại, thư viện này (hy vọng) đáp ứng tất cả các trường hợp cạnh.
Carson Graham

1
Thật là buồn cười khi "một câu hỏi đơn giản" thoạt nhìn lại không phải là một nhiệm vụ dễ giải quyết. Đồng ý với Carson - thư viện, hy vọng, sẽ tiếp tục với các bản cập nhật và thay đổi khi Biểu tượng cảm xúc tiếp tục phát triển.
Arnis Juraga

3
Có vẻ như điều này đã không được cập nhật trong khoảng 3 năm. Unicode 11 đã được phát hành vào khoảng thời gian đó, nhưng mọi thứ đã thay đổi kể từ đó, với Unicode 13 được phát hành sau đó. Có một số thay đổi trong các quy tắc grapheme mở rộng trong 13. Vì vậy, có thể có một số trường hợp cạnh mà điều này không xử lý được. (Tôi chưa xem qua mã - nhưng nó đáng để cẩn thận với)
Michael Anderson

2
Tôi đồng ý với @MichaelAnderson, thư viện này dường như sử dụng một thuật toán ngây thơ hoặc cũ. Để làm điều này đúng cách, nó nên sử dụng thuật toán phân đoạn grapheme được chỉ định trong Unicode .
Inkling

20

Bạn không chỉ gặp rắc rối với biểu tượng cảm xúc mà còn với các ký tự kết hợp khác. Những thứ này có cảm giác giống như các chữ cái riêng lẻ nhưng thực sự là một hoặc nhiều ký tự unicode được gọi là "cụm grapheme mở rộng".

Việc ngắt một chuỗi thành các cụm này rất khó (ví dụ: xem các tài liệu unicode này ). Tôi sẽ không dựa vào việc tự mình thực hiện nó mà sử dụng một thư viện hiện có. Google đã chỉ tôi đến thư viện grapheme-splitter . Các tài liệu cho thư viện này chứa một số ví dụ hay sẽ giúp tăng cường hầu hết các triển khai:

Sử dụng cái này, bạn sẽ có thể viết:

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

BÊN NGOÀI: Đối với những du khách đến từ tương lai, hoặc những người sẵn sàng sống bên bờ vực:

Có một đề xuất để thêm một trình phân đoạn grapheme vào tiêu chuẩn javascript. (Nó thực sự cũng cung cấp các tùy chọn phân đoạn khác). Nó đang ở giai đoạn 3 xem xét để chấp nhận vào lúc này và hiện đang được triển khai trong JSC và V8 (xem https://github.com/tc39/proposal-intl-segmenter/issues/114 ).

Sử dụng mã này, mã sẽ giống như sau:

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

Bạn có thể làm cho nó gọn gàng hơn nếu bạn biết javascript hiện đại hơn tôi ...

Có một triển khai ở đây - nhưng tôi không biết nó yêu cầu những gì.

Lưu ý: Điều này chỉ ra một vấn đề thú vị mà các câu trả lời khác chưa giải quyết được. Việc phân đoạn có thể phụ thuộc vào ngôn ngữ mà bạn đang sử dụng - không chỉ các ký tự trong chuỗi.


1
Có vẻ như mã đã không được cập nhật trong khoảng 2 năm - vì vậy các bảng của mã có thể không được cập nhật. Vì vậy, bạn có thể cần tìm kiếm thứ gì đó gần đây hơn.
Michael Anderson

3
Có vẻ như bản fork gần đây hơn của thư viện này có sẵn tại github.com/flmnt/graphemer
Michael Anderson

4
Tôi ngạc nhiên rằng tôi đã phải cuộn xuống dưới này để xem câu trả lời thực sự chính xác.
Lambda Fairy

1
Đối với ví dụ đề xuất bạn có thể làm const graphemes = Array.from(segment_iterator, ({segment}) => segment).
Inkling

17

Tôi chỉ quyết định làm điều đó cho vui, là một thử thách tốt. Không chắc nó chính xác trong mọi trường hợp, vì vậy hãy tự chịu rủi ro khi sử dụng, nhưng đây là:

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
Thực ra nó dài vì thông tin gỡ lỗi. Tôi thực sự đánh giá cao điều đó
Hao Wu

1
@AndrewSavinykh Không phải chơi gôn mã, mà đang tìm kiếm một giải pháp thanh lịch hơn. Có thể không giống như điên một chữ, nhưng dễ nhớ. Chẳng hạn như giải pháp regex là một imho thực sự tốt.
Hao Wu

0

Bạn có thể dùng:

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

Nó sẽ biến chuỗi của bạn thành một danh sách, đảo ngược nó rồi biến nó thành một chuỗi một lần nữa.


3
Bạn đã đọc câu hỏi? Mã của bạn chính xác là mã OP được chứng minh là sai trong câu hỏi.
Washington Guedes

-1

const text = 'Xin chào thế giới👩‍🦰👩‍👩‍👦‍👦';

const đảo ngược = text.split (''). reverse (). join ('');

console.log (đảo ngược);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.