Trang 2 trên tổng số 3 Đầu tiênĐầu tiên 123 Cuối cùngCuối cùng
Từ 11 tới 20 trên tổng số 22 kết quả

Đề tài: Thực nghiệm thư viện ICU để so sánh xâu tiếng Việt

  1. #11
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Mặc định Thực nghiệm thư viện ICU để so sánh xâu tiếng Việt

    Trích dẫn Nguyên bản được gửi bởi khoaph Xem bài viết
    Vậy "a Di Đà Phật" và "à da" thì cái nào đứng trước?
    Vẫn như trường hợp a cê ti len < , ở đây a Di Đà Phật < à da. Bởi vì a << à còn a <<< A thôi.

    Chi tiết mới rút ra từ quyển Từ điển tiếng Việt (Hoàng Phê) chỉ liên quan đến việc phân biệt chữ hoa/thường. Chữ hoa/thường đúng là được phân biệt ở cấp ưu tiên 3, nhưng khác với phân biệt dấu thanh ở cấp ưu tiên 2 và phân biệt chữ cái ở cấp ưu tiên 1, phạm vi phân biệt không phải là một chữ, mà là toàn thể cụm chữ (từ, ngữ, câu).

    Nói cách khác, thuật toán so sánh 2 cụm chữ Việt như sau.

    • Bỏ qua khác biệt hoa/thường, so sánh từng [đôi] chữ. Nếu kết quả là "bằng nhau" thì mới dùng khác biệt hoa/thường để quyết định.
    • So sánh 2 chữ là trước hết bỏ qua dấu thanh, so sánh từng [đôi] chữ cái. Nếu kết quả là "bằng nhau" thì mới dùng khác biệt dấu thanh để quyết định.


    Lưu ý, khi so sánh 2 chữ cái: a/ă/â, d/đ, e/ê, o/ô/ơ, u/ư là những chữ cái phân biệt.

    - - - Nội dung đã được cập nhật ngày 19-08-2021 lúc 12:09 AM - - -

    Khác biệt của thuật toán cho tiếng Việt ngoài khuôn khổ thuật toán chuẩn (UCA, EOR, ISO) là ở chỗ trong thuật toán chuẩn, mọi cấp so sánh đều xác định trong phạm vi toàn xâu. Thuật toán chuẩn làm việc với xâu chứ không làm việc với cụm chữ/cụm từ. Nói cách khác, nó không đòi hỏi phân tích xâu, xác định các chữ/từ (tokenisation).
    Đã được chỉnh sửa lần cuối bởi Ada : 31-08-2021 lúc 10:06 AM. Lý do: Phát biểu lại thuật toán cho ngắn gọn
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  2. #12
    Ngày gia nhập
    01 2014
    Bài viết
    44

    Tiếng Việt có 1 rắc rối nhỏ vì có 2 lối bỏ dấu:

    hỏi đứng trước hóa
    hay
    hoá đứng trước hỏi

  3. #13
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Trích dẫn Nguyên bản được gửi bởi huycan Xem bài viết
    Tiếng Việt có 1 rắc rối nhỏ vì có 2 lối bỏ dấu:

    hỏi đứng trước hóa
    hay
    hoá đứng trước hỏi
    Vậy à? Bạn thử dùng chương trình ở bài #1 xem kết quả thế nào cho 2 trường hợp này. Thử cả so sánh hóa với hoá luôn.

    - - - Nội dung đã được cập nhật ngày 19-08-2021 lúc 08:59 PM - - -

    Để đối chiếu, mình xem lại Từ điển Việt - Anh của Viện khoa học xã hội VN (Đặng Chấn Liêu - Lê Khả Kế - Phạm Duy Trọng biên soạn, nhà xuất bản khoa học xã hội, 1993). Thứ tự cũng thế: 3 từ ai ai, Ai cập học, ai đời được liệt kê tuần tự.

    - - - Nội dung đã được cập nhật ngày 20-08-2021 lúc 11:49 AM - - -

    Để so sánh thuật toán sắp xếp thứ tự tiếng Việt với các tiếng khác, mình đã xem 2 từ điển:

    * Từ điển Anh - Việt của Viện ngôn ngữ học. Hồ Hải Thụy, Chu Khắc Thuật, Cao Xuân Phổ chủ biên. Nhà xuất bản TP HCM. 1994. Bảng từ được sao chép từ Oxford Advanced Learner's Dictionary (Oxford University Press, 1992).

    * Từ điển Pháp - Việt. Nguyễn Văn Dương chủ biên. Nhà xuất bản Thanh Niên. 2003. Bảng từ được sao chép từ Petite dictionnaire universel (Hachette, 1993).

    Trong quyển đầu, thấy tuần tự Anglicize (-ise), Anglo-American, ..., Anglo-Indian, Anglophile,... , Anglophone, Anglo-Saxon, angora. Nghĩa là từ đi trước và từ theo sau dấu gạch nối ( - ) đã được nối liền làm một còn bản thân dấu gạch nối được bỏ qua, ít ra là ở 3 cấp ưu tiên cao nhất, khi cụm từ ấy được đem ra so sánh với một [cụm] từ khác.

    Trong quyển sau, cũng thế: angliciste, anglo-américain, anglomanie, ..., anglophone, anglo-saxon (-onne), angloissant (-ante).

    Đấy là đối với dấu gạch nối. Thế còn các dấu khác thì sao?

    Dấu nháy đơn ( ' ) và dấu chấm ( . ) được xử lý giống như dấu gạch nối trong cả hai quyển.

    Chỉ còn một trường hợp cuối cùng, dấu cách.

    Trong quyển đầu, thấy tuần tự airmail, Air Marshall, airplane, air pocket. Nghĩa là trong tiếng Anh, dấu cách được xử lý như dấu gạch nối.

    Trong quyển sau, thấy tuần tự avatar, à vau-l'eau, ave ou Ave Maria, avec. Thứ tự ba [cụm] từ đầu cho thấy dấu cách đã được bỏ qua, giống như tiếng Anh. Nhưng so sánh cụm từ thứ ba và từ thứ tư cho thấy dấu cách có tác dụng tách từ và so sánh là theo từng từ ít ra ở cấp ưu tiên 1 (avec đã được so sánh với ave hơn là aveouAveMaria), giống như tiếng Việt. Do dữ liệu khảo sát là từ điển, các mục từ chủ yếu là từ, còn cụm từ thì hiếm và cụm từ có dấu cách thì rất hiếm, không đủ dữ liệu để xác định trong tiếng Pháp, trong 2 quy luật này, cái nào phổ biến.
    Đã được chỉnh sửa lần cuối bởi Ada : 20-08-2021 lúc 01:52 PM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  4. #14
    Ngày gia nhập
    01 2014
    Bài viết
    44

    Bạn càng đi càng xa vời... không thấy dính dáng gì đến 2 từ hóa và hoá cả...

    ---

    Tưởng tượng đây là cuộc ghi âm... đừng để ý đến cách bỏ dấu:

    A: Anh B nè, hóa và hỏi... cái nào xếp trước theo thứ tự abc..
    B: À, ý anh nói là hóa nào?
    A: Ý anh là sao? hóa đơn, hóa học... thoái hóa, biến hóa.... hóa nào tiếng Việt cũng chỉ có một mà...
    B: Ý tôi nói là hó + a.... hay là.... ho + á....
    A: Còn có vụ này nữa? Vậy nếu tôi lập 1 cuốn từ điển... trong đó phải có hai nhóm hóa khác nhau..???
    B gãi đầu: Tôi không biết... hay hôm nào tôi phải đi mua 1 cuốn từ điển về xem mới biết được...

  5. #15
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Trích dẫn Nguyên bản được gửi bởi huycan Xem bài viết
    Bạn càng đi càng xa vời... không thấy dính dáng gì đến 2 từ hóa và hoá cả...

    ---
    Không xa vời đâu. Xa tận chân trời, gần ngay trước mắt. Bạn sẽ giải quyết thế nào để sắp xếp danh sách đội tuyển bóng đá quốc gia của VN?

    Tiêu chuẩn quốc gia về so sánh và thứ tự sắp xếp xâu phải xử lý được xâu bất kỳ, chứ không giới hạn trong một ngôn ngữ. VN chưa có chuẩn chính thức, nên thư viện ICU dựa vào thực tiễn VN để xây dựng thuật toán cho VN, trong khuôn khổ của chuẩn quốc tế. Còn chủ đề này là áp dụng thư viện ICU để ra kết quả đúng như kỳ vọng. Trước hết là trường hợp đơn giản nhất, so sánh 2 chữ Việt, rồi trường hợp phức tạp hơn, so sánh 2 cụm chữ Việt, rồi đến các trường hợp khác gai góc hơn nữa. Đó là cách của mình.


    Trích dẫn Nguyên bản được gửi bởi huycan Xem bài viết
    Tưởng tượng đây là cuộc ghi âm... đừng để ý đến cách bỏ dấu:

    A: Anh B nè, hóa và hỏi... cái nào xếp trước theo thứ tự abc..
    B: À, ý anh nói là hóa nào?
    A: Ý anh là sao? hóa đơn, hóa học... thoái hóa, biến hóa.... hóa nào tiếng Việt cũng chỉ có một mà...
    B: Ý tôi nói là hó + a.... hay là.... ho + á....
    A: Còn có vụ này nữa? Vậy nếu tôi lập 1 cuốn từ điển... trong đó phải có hai nhóm hóa khác nhau..???
    B gãi đầu: Tôi không biết... hay hôm nào tôi phải đi mua 1 cuốn từ điển về xem mới biết được...
    Thắc mắc của hai người A và B là so sánh 2 chữ Việt, trường hợp đơn giản nhất. Ai theo dõi chủ đề này cũng đã có thể biết cách. (Bạn khoaph đã viết 1 bài, mình cũng đã viết 1 bài). Ai vẫn chưa biết, có thể thực nghiệm bằng chương trình, cách mình đã khuyên bạn ở trên. Nếu vẫn nửa tin nửa ngờ, có thể xem từ điển, như ý kiến của B. Đó cũng là cách mình đã làm cho những trường hợp khác mà mình cần làm rõ. Cuối cùng, nếu đã thử hết cách mà vẫn còn thắc mắc, có thể đặt câu hỏi, mình sẽ giải đáp. OK?
    Đã được chỉnh sửa lần cuối bởi Ada : 21-08-2021 lúc 08:10 AM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  6. #16
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Mặc định Thực nghiệm thư viện ICU để so sánh xâu tiếng Việt

    Bài trước, mình đã dừng lại ở việc chỉ rõ thuật toán so sánh xâu cho tiếng Việt. Để thực hiện nó, cần phân tích xâu, tách từng chữ.

    Nhưng, tách chữ Việt là một bài toán văn phạm. Tuy có thể giải tự động bằng biểu thức chính quy (regex) hoặc bằng tay với một bộ phân tích từ (lexer), nhưng tốc độ thế là quá chậm cho 1 phép so sánh xâu.

    Vả lại, tách chữ bằng văn phạm chỉ tách được các chữ Việt đúng 100%. Thử nghĩ, ngựa Gióng chắc chắn là một [cụm] từ Việt, nhưng còn khuyều truếm luyệng thì sao? Chưa chắc. Và còn "aquaφοβία", "I like to eat apples. 我喜欢吃苹果。", "Приве́т नमस्ते שָׁלוֹם" hay "ðƺչⲯфქⱘωⵞᚏᛉߟꔶصאܞঅ ഗᜤକꠞழขぁᎯጄᖪ䷇ℝ∳ɦ" thì sao nữa? Có lẽ là không rồi. Các trường hợp sau này, giải quyết sao đây?

    Nên phải dùng một cách khác, đơn giản hơn, đó là tìm ranh chữ. Thư viện ICU thực thi các thuật toán tìm ranh, đặc tả bởi Phụ chương 29 chuẩn Unicode. Tài liệu ấy cũng giải thích khái niệm "ranh".

    Ranh nghĩa là lằn ranh, ranh giới, biên giới. Một văn bản có 2 ranh: ranh đầu, ở trước con chữ đầu tiên, và ranh cuối, ở sau con chữ cuối cùng (1). Tất nhiên, trừ phi văn bản rỗng. Vì khi ấy, hai ranh trùng nhau.

    Từ đó, tự nhiên định nghĩa được ranh của con chữ, của chữ, từ, ngữ, câu, đoạn văn, v.v.

    Có thể liên tưởng đến con chạy (caret) của một chương trình biên soạn văn bản. Con chạy chỉ có thể nằm ở đầu văn bản, ở cuối văn bản, hay ở khe hở giữa 2 con chữ. Các vị trí khả dĩ của con chạy chính là các ranh của các con chữ trong văn bản. Khi ấn phím Left hay Right, con chạy di chuyển sang ranh kế cận. Khi ấn Ctrl+Left hay Ctrl+Right, con chạy cũng di chuyển sang ranh kế cận. Khác biệt là trong trường hợp đầu, nó di chuyển trên các ranh con chữ, còn trong trường hợp sau, nó di chuyển trên các ranh chữ (hoặc từ, đối với các thứ tiếng khác).

    Như vậy, các ranh trong 1 xâu có thứ tự và vị trí. Nhưng thứ tự, vị trí được đánh số như thế nào? Bắt đầu từ đâu và tính bằng gì? Mỗi ranh có thể theo sau 0 hoặc 1 con chữ và có 0 hoặc 1 con chữ theo sau. Số thứ tự của ranh là của con chữ dẫn trước hay con chữ theo sau nó? Khi mã hóa ở định dạng UTF-8 hay UTF-16, không những con chữ, mà cả ký tự cũng có độ dài biến thiên. Vị trí của ranh được tính bằng số con chữ, số ký tự, số WORD, DWORD, QWORD, hay số byte?

    Chương trình sau đây sẽ giải đáp những câu hỏi ấy. Và nhiều câu hỏi khác.

    Chương trình này minh họa break iterator, nghĩa là con chạy trên ranh (2).
    (tệp nguồn ở định dạng UTF-8)
    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <cstdio>
    3. #include "unicode/utext.h"
    4. #include "unicode/brkiter.h"
    5.  
    6. using namespace icu;
    7. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    8.  
    9. void listBreaks(const std::string& s) {
    10.    UErrorCode status = U_ZERO_ERROR;
    11.    UText t = UTEXT_INITIALIZER;
    12.    utext_openUTF8(&t, s.c_str(), -1, &status);
    13.  
    14.    BreakIterator*bi =
    15.       BreakIterator::createCharacterInstance(Locale::getUS(), status);
    16.    bi->setText(&t, status);
    17.    int32_t b = bi->first();
    18.    while (b != BreakIterator::DONE) {
    19.       printf("Break at %2d\n", b);
    20.       b = bi->next();
    21.    }
    22.    delete bi;
    23.    utext_close(&t);
    24. }
    25. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    26.  
    27. int main()
    28. {
    29.    UErrorCode status = U_ZERO_ERROR;
    30.    listBreaks("ngựa Gióng");
    31.    status = U_ZERO_ERROR;
    32.    return 0;
    33. }
    34. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/

    Màn hình hiển thị khi dữ liệu được mã hoá bằng Unicode dựng sẵn (chẳng hạn, bằng cách gõ từ "ngựa Gióng" với Unikey theo bảng mã Unicode). Để ý rằng vị trí DONE không trùng với bất cứ vị trí ranh nào, kể cả ranh cuối.
    Code:
    Break at  0
    Break at  1
    Break at  2
    Break at  5
    Break at  6
    Break at  7
    Break at  8
    Break at  9
    Break at 11
    Break at 12
    Break at 13
    BỔ SUNG 2021-08-29. Màn hình hiển thị khi dữ liệu được mã hoá bằng Unicode tổ hợp (chẳng hạn, bằng cách gõ lại từ "ngựa Gióng" với Unikey theo bảng mã Composite Unicode). Để ý rằng tuy số ký tự tăng lên nhưng số con chữ không thay đổi.
    Code:
    Break at  0
    Break at  1
    Break at  2
    Break at  6
    Break at  7
    Break at  8
    Break at  9
    Break at 10
    Break at 13
    Break at 14
    Break at 15
    Mã nguồn nguyên là 1 thí dụ của thư viện ICU. Mình đã cải biên, dùng UText làm đệm giữa BreakIterator với xâu dữ liệu đầu vào std::string vốn có định dạng UTF-8. Lý do là BreakIterator làm việc trên xâu trừu tượng (UText,...), chứ không làm việc trên xâu ở một định dạng cụ thể nào đó (mảng byte, UTF-8,...). Ngoại lệ duy nhất, định dạng cụ thể duy nhất được phép, di sản của quá khứ (3), là UnicodeString, tức xâu thực hiện bằng mảng các đơn vị mã là số 16 bit, chứa ký tự UTF-16, mỗi ký tự chiếm 1 - 2 đơn vị mã.

    Hàm createCharacterInstance dùng để tạo một BreakIterator chạy trên các ranh con chữ. Để làm việc trên các ranh chữ, chỉ cần thay thế Character bằng Word.

    Đối với tiếng Hán, Thái, Lào, Miên,... word break iterator chạy ranh từ. Đối với tiếng Anh, Pháp, Đức, Ý,... nó cũng chạy ranh từ. Nhưng đối với tiếng Việt, nó chạy ranh chữ. Đó là một điểm đặc thù, có một không hai, của tiếng Việt.

    Có thể, một ngày nào đó, thư viện ICU sẽ sửa đổi word break iterator cho tiếng Việt để chạy ranh từ. Khi ấy, chương trình của mình sẽ chạy sai nếu dùng bộ luật tách từ Việt (locale = vi_VN). Để phòng ngừa tương lai ảm đạm ấy, mình đã dùng bộ luật Mỹ (Locale::getUS(), tương đương với locale = en_US) (4).




    __________________________
    (1) Một con chữ ở đây là một ký tự biểu kiến, tức ký tự mà ta nhìn thấy trên màn hình máy tính. Chữ dễ chẳng hạn, luôn chỉ có đúng 2 con chữ bất kể nó được mã hóa bằng 2 ký tự (d ễ), 3 ký tự (d ê ngã hoặc d ẽ nón) hay 4 ký tự (d e nón ngã hoặc d e ngã nón). Để phân biệt rõ ràng cái trên màn hình với cái trong bộ nhớ, các xâu ký tự ê ngã, nón, e nón ngã, e ngã nón còn được gọi là cụm tự vị (grapheme cluster) trong các tài liệu chuẩn hóa. (Trong đó, giống như trong các từ "âm vị", "hình vị", "tiết vị", chữ "vị" nghĩa là đơn vị.) Và để cho nhất quán, một cụm tự vị có thể chỉ gồm 1 tự vị. Nghĩa là tự vị (ký tự) và thậm chí cả d cũng là một cụm tự vị. Xâu ngựa Gióng có 10 con chữ (10 cụm tự vị), được mã hóa bằng 10 ký tự dựng sẵn nhưng bằng 12 ký tự tổ hợp. Xâu Приве́т नमस्ते שָׁלוֹם có 16 con chữ (16 cụm tự vị) nhưng phải mã hóa bằng 20 ký tự dựng sẵn (NFC) hoặc 22 ký tự tổ hợp (NFD) [UTF-8 Everywhere Manifesto].

    (2) Đúng ra phải gọi là boundary iterator; vì boundary mới là ranh, còn break là điểm ngắt, nói thật đúng là điểm ngắt khả dĩ, trong văn bản (để xuống hàng). Do một điểm ngắt khả dĩ phải là một ranh, hai từ này được dùng lẫn lộn khá thoải mái trong mã nguồn và trong các tài liệu, bài viết.

    (3) ICU ban đầu được viết bằng ngôn ngữ Java, sau được chuyển đặt sang C và sau nữa được phát triển chủ yếu bằng C++. Bản Java tuy vẫn được bảo trì nhưng đã ngừng phát triển. Nó không hỗ trợ UTF-8.

    (4) Có thể mình đã lo quá xa. Lịch sử 1 thế kỷ qua cho thấy chữ Việt phát triển theo xu hướng ngược lại điều mình dự báo. Những từ thế-lực, đấng-toàn-năng, chủ-nghĩa-duy-tâm trong sách báo đầu thế kỷ 20 đã dần dần bị thay thế bởi thế lực, đấng toàn năngchủ nghĩa duy tâm. Về lý luận, lấy gì để phân định từ và ngữ là điều còn tranh cãi. Những đấng-toàn-năng, chủ-nghĩa-duy-tâm không có cơ sở thuyết phục hơn đấng toàn-năng, chủ-nghĩa duy-tâm hay thậm chí đấng toàn năng, chủ nghĩa duy tâm. Và cứ cho rằng nhà-để-xe, kho-chứa-hàng là đúng hơn nhà để-xe, nhà-để xe, nhà để xe, kho-chứa hàng, kho chứa-hàng, kho chứa hàng một cách thuyết phục, thì quy luật "đầy tính thuyết phục" ấy liệu có đơn giản để bất cứ học sinh tiểu học nào cũng có thể hiểu, nhớ, dùng đúng mà không tra từ điển? Về thực tiễn, tìm thông tin theo từ (hơn là theo chữ) tuy có lợi ích thấy rõ, nhưng lợi ích ấy chưa hẳn đã "sát sườn" hơn việc ấn Ctrl+Left hay Ctrl+Right để di chuyển con chạy một chữ (hơn là một từ). Vả lại, với một thư viện thích hợp, lập trình viên có những cách khác để bảo đảm kết quả tìm kiếm nhà chứa xác không bao hàm nhà chứacô gái ngành y không bao hàm cả gái ngành, miễn là dữ liệu đầu vào đã qua sàng lọc và tinh chế, trong khi không có cách nào khác hơn dựa vào một thư viện ở mức cơ bản như ICU để so sánh xâu, một phép toán cơ bản cho việc xử lý mọi dữ liệu, kể cả việc sàng lọc và tinh chế dữ liệu thô.
    Đã được chỉnh sửa lần cuối bởi Ada : 12-09-2021 lúc 10:07 AM. Lý do: Chỉnh mã nguồn
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  7. #17
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Bài này trình bày một hàm tách chữ (từ).

    Mình dựa vào mã nguồn nguyên là một thí dụ Java, sử dụng bản Java của thư viện ICU.

    (Tách từ trong Java)
    Java Code:
    1. import java.text.BreakIterator;
    2. import java.util.Locale;
    3. /*  w  w w .  j  a  v  a  2 s .  c o m*/
    4. public class Main {
    5.   public static void main(String[] args) {
    6.     String text = "this is a test(this is a test).";
    7.     BreakIterator wordIterator = BreakIterator.getWordInstance(Locale
    8.         .getDefault());
    9.     extractWords(text, wordIterator);
    10.   }
    11.  
    12.   static void extractWords(String target, BreakIterator wordIterator) {
    13.     wordIterator.setText(target);
    14.     int start = wordIterator.first();
    15.     int end = wordIterator.next();
    16.  
    17.     while (end != BreakIterator.DONE) {
    18.       String word = target.substring(start, end);
    19.       if (Character.isLetterOrDigit(word.charAt(0))) {
    20.         System.out.println(word);
    21.       }
    22.       start = end;
    23.       end = wordIterator.next();
    24.     }
    25.   }
    26. }

    Dịch mã nguồn trên sang C++, chỉ thay thế định dạng UTF-16 đã lỗi thời bằng UTF-8, thì được chương trình sau.

    (v1, định dạng tệp nguồn: UTF-8)
    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <cstdio>
    3. #include "unicode/brkiter.h"
    4. #include "unicode/utext.h"
    5. #include "unicode/uchar.h"
    6. #include "unicode/utf8.h"
    7.  
    8.  
    9. using namespace icu;
    10. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    11.  
    12. void listWords(const std::string& s) {
    13.    UErrorCode status = U_ZERO_ERROR;
    14.    UText t = UTEXT_INITIALIZER;
    15.    utext_openUTF8(&t, s.c_str(), -1, &status);
    16.  
    17.    BreakIterator*bi =
    18.       BreakIterator::createWordInstance(Locale::getUS(), status);
    19.    bi->setText(&t, status);
    20.  
    21.    int32_t b0 = bi->first();
    22.    int32_t b1 = bi->next();
    23.  
    24.    while (b1 != BreakIterator::DONE) {
    25.       std::string w = s.substr(b0, b1-b0);
    26.       int32_t i = 0;
    27.       UChar32 c;
    28.       U8_NEXT(w.data(), i, w.length(), c);
    29.       if(u_isalnum(c)) {
    30.          printf_s("Word at %2d:%.*s\n", b0, w.length(), w.c_str());
    31.       }
    32.       b0 = b1;
    33.       b1 = bi->next();
    34.    }
    35.    delete bi;
    36.    utext_close(&t);
    37. }
    38. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    39.  
    40. int main()
    41. {
    42.    UErrorCode status = U_ZERO_ERROR;
    43.    listWords("Buồn trông gió cuốn mặt duềnh,\n\
    44.          Ầm ầm tiếng sóng kêu quanh ghế ngồi.\n");
    45.    status = U_ZERO_ERROR;
    46.    return 0;
    47. }
    48. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/

    Mã trên có 2 điểm yếu.

    • Truy vấn thuộc tính của một ký tự (ký tự c) để xác định thể loại của một xâu (xâu w). Nghĩa là phải tra cứu cái gọi là UTrie, một "từ điển" của ICU chứa mọi thuộc tính của các điểm mã (ký tự). Cấu trúc UTrie rất phức tạp do điểm mã có tính chất phức tạp (hàng trăm thuộc tính) và số lượng lớn (hàng chục vạn). Tra một ký tự ASCII mất 1 lần đọc bộ nhớ, một ký tự "đặc thù VN" như é, ê, ế mất 2-3 lần đọc bộ nhớ, một ký tự Unicode "hiếm" mất 4 lần đọc bộ nhớ. Nghĩa là xâu có bao nhiêu chữ thì thêm ít nhất bấy nhiêu lần đọc bộ nhớ.
    • Tạo xâu mới (w) để chứa bản sao của một chữ của xâu nguồn (s). Nghĩa là xâu nguồn có bao nhiêu chữ thì bấy nhiêu lần cấp phát bộ nhớ, sao chép bộ nhớ, và giải phóng bộ nhớ.

    Và để cho tồi tệ hơn, trong đấy, mỗi dấu cách (SP, HT, VT, CR, LF, FF, NBSP,...) và dấu ngăn (gạch nối, phẩy, chấm, nháy đơn, nháy kép, mở/đóng ngoặc đơn, mở/đóng ngoặc kép,...) cũng đều được tính là một "chữ".

    Điểm yếu thứ nhất do bản Java của thư viện ICU, vốn đã ngừng phát triển (tuy vẫn được bảo trì). Điểm yếu thứ hai do hạn chế của chính ngôn ngữ lập trình (Java). Đối với nhiều ứng dụng, tốc độ không phải là vấn đề lớn và những điểm yếu đó có thể bỏ qua. (Đó là lý do Java ICU vẫn được dùng rộng rãi, chẳng hạn, trên các hệ điều hành Android và Tizen.) Nhưng để thực hiện một phép so sánh xâu, nơi tốc độ là vấn đề mấu chốt, cách trên không chấp nhận được.

    Sau đây là phiên bản mới, đã khắc phục 2 điểm yếu trên. Thủ thuật getRuleStatus() mình chép từ mã nguồn thí dụ "đếm các từ trong xâu" của tài liệu hướng dẫn ICU. Thủ thuật StringPiece là hết sức cơ bản, được trình bày chi tiết trong tài liệu tra cứu ICU.

    (v2, định dạng tệp nguồn: UTF-8)
    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <cstdio>
    3. #include "unicode/utext.h"
    4. #include "unicode/brkiter.h"
    5.  
    6. using namespace icu;
    7. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    8.  
    9. void listWords(const std::string& s) {
    10.    UErrorCode status = U_ZERO_ERROR;
    11.    UText t = UTEXT_INITIALIZER;
    12.    utext_openUTF8(&t, s.c_str(), -1, &status);
    13.  
    14.    BreakIterator*bi =
    15.       BreakIterator::createWordInstance(Locale::getUS(), status);
    16.    bi->setText(&t, status);
    17.  
    18.    int32_t b0 = bi->first();
    19.    int32_t b1 = bi->next();
    20.  
    21.    while (b1 != BreakIterator::DONE) {
    22.       if(bi->getRuleStatus() != UBRK_WORD_NONE){
    23.          StringPiece w(&s[b0], b1-b0);
    24.          printf_s("Word at %2d:%.*s\n", b0, w.length(), w.data());
    25.       }
    26.       b0 = b1;
    27.       b1 = bi->next();
    28.    }
    29.    delete bi;
    30.    utext_close(&t);
    31. }
    32. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    33.  
    34. int main()
    35. {
    36.    UErrorCode status = U_ZERO_ERROR;
    37.    listWords("Buồn trông gió cuốn mặt duềnh,\n\
    38.          Ầm ầm tiếng sóng kêu quanh ghế ngồi.\n");
    39.    status = U_ZERO_ERROR;
    40.    return 0;
    41. }
    42. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    Hai điểm khác biệt ở phiên bản mới là:
    • Thể loại của đoạn xâu vừa quét qua được xác định mà không tra UTrie. Nó được suy đoán từ thuộc tính của ký tự dẫn trước ranh hiện thời, vốn đã được tra sẵn rồi, nên giờ chỉ cần 1 lời gọi getRuleStatus() để đọc lại là xong. Biến chữ w chỉ được khởi tạo khi thực sự cần, nghĩa là khi đoạn xâu ấy đã được xác định là một chữ. Nếu nó chỉ là [những] dấu ngăn/cách, nó sẽ bị bỏ qua và không một xung nhịp nào bị tiêu phí cho những ký tự như thế.
    • Chữ (w) không được thực hiện bằng một std::string, mà bằng một icu::StringPiece. Khi khởi tạo, nó không xin cấp phát bộ nhớ cho dữ liệu (và tất nhiên, không sao chép dữ liệu) mà dùng "ngay và luôn" bộ nhớ của chính xâu nguồn (s) cùng với nội dung hiện hữu; khi làm việc, nó có thể thay đổi nội dung dữ liệu, nếu cần (trong trường hợp này thì không cần); và khi hủy, dĩ nhiên nó không giải phóng bộ nhớ của dữ liệu, mà để nguyên. Tóm lại, phương châm là "4 tại chỗ": vay tài nguyên tại chỗ, nhận đầu vào tại chỗ, sinh đầu ra tại chỗ, trả tài nguyên tại chỗ.


    Tư tưởng thiết kế của kiểu StringPiece giống như kiểu UText. Khác biệt chỉ ở chỗ UText trừu tượng hóa một xâu trừu tượng; nghĩa là xâu ấy có thể được biểu diễn theo bất kỳ cách nào, chẳng hạn, có thể lấy bộ nhớ dưới dạng một danh sách liên kết nhiều vùng nhớ rải rác và không thuần nhất; còn StringPiece trừu tượng hóa một xâu có biểu diễn cụ thể, là một mảng (mảng byte, mảng WORD hay tổng quát, một mảng của kiểu T bất kỳ gọi là đơn vị mã), giả thiết 2 phương thức data()size(); nghĩa là nó tựa như std::string (và sát hơn, giống như std::string_view của chuẩn C++ 17). Cho nên StringPiece có cấu tạo gọn nhẹ, tạo và hủy nhanh chóng.

    Nhờ thế, hàm tách chữ (từ) đạt tốc độ có thể so sánh được với strtok() của thư viện chuẩn ngôn ngữ C.


    Kết quả (cho cả 2 phiên bản v1, v2)
    Code:
    Word at  0:Buồn
    Word at  7:trông
    Word at 14:gió
    Word at 19:cuốn
    Word at 26:mặt
    Word at 32:duềnh
    Word at 51:Ầm
    Word at 56:ầm
    Word at 61:tiếng
    Word at 69:sóng
    Word at 75:kêu
    Word at 80:quanh
    Word at 86:ghế
    Word at 92:ngồi
    Hàm tách chữ trên sẽ không được sử dụng trực tiếp để thực hiện phép so sánh 2 xâu (vì sao?) nhưng tư tưởng và phương pháp của nó sẽ thấm nhuần vào không những phép toán ấy, mà còn mọi phép toán khác ở mọi nơi dùng bản C/C++ của thư viện ICU.
    Đã được chỉnh sửa lần cuối bởi Ada : 06-09-2021 lúc 09:23 PM. Lý do: Chỉnh mã nguồn
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  8. #18
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Khi đã phân tách chữ được, so sánh 2 xâu là dễ dàng, đương nhiên. Dưới đây là phiên bản đúng của chương trình sắp xếp từ điển [bài #2], thực thi và sử dụng thuật toán so sánh mình đã chỉ ra [bài #11].

    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <vector>
    3. #include <algorithm>
    4. #include <iostream>
    5. #include <cstdio>
    6. #include "unicode/unistr.h"
    7. #include "unicode/utypes.h"
    8. #include "unicode/locid.h"
    9. #include "unicode/coll.h"
    10. #include "unicode/tblcoll.h"
    11. #include "unicode/coleitr.h"
    12. #include "unicode/sortkey.h"
    13. #include "unicode/utext.h"
    14. #include "unicode/brkiter.h"
    15.  
    16. #include <windows.h>    // Uncomment this line if trouble
    17.  
    18. using namespace icu;
    19. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    20.  
    21. struct Entry {
    22.    std::string Key;
    23.    std::string Val;
    24. };
    25. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    26.  
    27. void scan(Entry& y)
    28. {
    29.    // scan one line to an Entry
    30.    getline(std::cin,y.Key,':');
    31.    getline(std::cin,y.Val);
    32. }
    33. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    34.  
    35. void print(const Entry& x)
    36. {
    37.    // print an Entry to one line
    38.    std::cout << x.Key << ':' << x.Val << '\n';
    39.    //printf_s("%s:%s\n",x.Key.c_str(),x.Val.c_str());
    40. }
    41. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    42.  
    43. UBool collateWithLocaleUTF8(const Locale& locale, UErrorCode& status)
    44. {
    45.    UnicodeString dispName;
    46.    UCollationResult result; //    = UCOL_EQUAL;
    47.    Collator      *myCollator = 0;
    48.    UErrorCode error = U_ZERO_ERROR;
    49.    if (U_FAILURE(status))
    50.    {
    51.       return false;
    52.    }
    53.    myCollator = Collator::createInstance(locale, status);
    54.    if (U_FAILURE(status))
    55.    {
    56.       locale.getDisplayName(dispName);
    57.       /*Report the error with display name... */
    58.       fprintf(stderr,
    59.       "%s: Failed to create the collator for : \"%s\"\n", dispName);
    60.       return false;
    61.    }
    62.    myCollator->setStrength(Collator::SECONDARY);
    63.  
    64.    std::vector<Entry> td;
    65.    for(;;){
    66.       Entry e;
    67.       scan(e);
    68.       if(e.Key.size() == 0) break;
    69.       td.push_back(e);
    70.    }
    71.  
    72.    BreakIterator * bis =
    73.       BreakIterator::createWordInstance(Locale::getUS(), status);
    74.    BreakIterator * bit =
    75.       BreakIterator::createWordInstance(Locale::getUS(), status);
    76.  
    77.    UText us = UTEXT_INITIALIZER;
    78.    UText ut = UTEXT_INITIALIZER;
    79.  
    80.    std::sort(td.begin(), td.end(), [myCollator,bis,bit,&us,&ut,
    81.       &status,&error] (const Entry& s, const Entry& t) {
    82.          utext_openUTF8(&us, s.Key.c_str(), -1, &status);
    83.          utext_openUTF8(&ut, t.Key.c_str(), -1, &status);
    84.          bis->setText(&us, status);
    85.          bit->setText(&ut, status);
    86.          int32_t s0 = bis->first(), s1;
    87.          int32_t t0 = bit->first(), t1;
    88.          int32_t const DONE = BreakIterator::DONE;
    89.          for(;; s0=s1, t0=t1){
    90.             for(;; s0=s1){
    91.                s1 = bis->next();
    92.                if(s1 == DONE) break;
    93.                if(bis->getRuleStatus() != UBRK_WORD_NONE) break;
    94.             }
    95.             for(;; t0=t1){
    96.                t1 = bit->next();
    97.                if(t1 == DONE) break;
    98.                if(bit->getRuleStatus() != UBRK_WORD_NONE) break;
    99.             }
    100.             if(s1 != DONE && t1 != DONE){
    101.                StringPiece ws(&s.Key[s0], s1-s0);
    102.                StringPiece wt(&t.Key[t0], t1-t0);
    103.                int cr = myCollator->compareUTF8(ws, wt, error);
    104.                if(cr > 0) return false; // GREATER
    105.                if(cr < 0) return true;  // LESS
    106.             }
    107.             else if(s1 != DONE && t1 == DONE)   return false; // GREATER
    108.             else if(s1 == DONE && t1 != DONE)   return true;  // LESS
    109.             else /* s1 == DONE && t1 == DONE */ return false; // EQUAL
    110.          }
    111.       }
    112.    );
    113.  
    114.    for(auto e:td){ print(e); }
    115.  
    116.    utext_close(&us);
    117.    utext_close(&ut);
    118.    delete bis;
    119.    delete bit;
    120.    delete myCollator;
    121.    return true;
    122. }
    123. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    124.  
    125. int main()
    126. {
    127.    SetConsoleOutputCP(CP_UTF8); // Uncomment this line if trouble
    128.    UErrorCode status = U_ZERO_ERROR;
    129.    if (collateWithLocaleUTF8(Locale("vi","VN"), status) != true)
    130.    {
    131.         fprintf(stderr,
    132.         "\nCollate with locale in C++ failed.\n");
    133.    }
    134.    status = U_ZERO_ERROR;
    135.    return 0;
    136. }
    137. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/


    Kết quả (trích đoạn)
    Code:
    a : (1) (exclamation of surprise, regret, etc.); (2) sickle; (3) to rush, dash; (4) to gather; (5) to flatter, curry favor with, kiss up to; (6) area of 100 square meters
    A Căn Đình : Argentina, Argentine, Argentinean
    a cê ti len : acetylene
    a cít : acid
    a cít hóa : to acidify
    a cít kế : acidity meter
    A di đà phật : Amnida Buddha; thank heavens!
    A Dong : Adam
    a dua : to ape, imitate, follow
    A đam : Adam
    a đrê na lin : adrenaline
    a ga : agar
    a giao : glue, gelatin
    a hoàn : Abigail; maidservant, maid, servant
    A La Hán : Arhant, Lohan (follower of Buddha)
    a léc : alert
    a lê : go ahead!, come on!; outward journey or trip
    A Lịch Sơn : Alexander
    A Lịch Sơn Đắc Lộ : Alexandre de Rhodes
    a lô : hello (on phone, in announcements), attention!
    a ma tơ : amateur
    a măng : lover, sweetheart
    a men : amen
    a mi : close or intimate friend; to flatter
    a mi ăng : asbestos
    a mi đan : tonsil
    a min : amine
    a míp : amoeba, ameba
    a mô ni ắc : ammonia
    a ni lin : aniline
    a nốt : anode
    a pa tít : apatite
    a pác thai : apartheid
    a pê ri típ : apéritif
    a phiến : opium
    a phiến trắng : morphine
    A Phú Hãn : Afghanistan; Afghanistani, Afghan
    a sen : arsenic
    a tòng : to follow, imitate, act as (an accomplice) come around (to a way of thinking); accomplice
    A, tội nghiệp quá : Oh!, What a pity!
    a trô pin : atropine
    a tùng : to follow
    a vào người nào : to rush at someone
    a xê ti len : acetylene
    a xê ton : acetone
    a xít : acid
    a xít a min : amino acid
    a xít béo : aliphatic or fatty acid
    a xpi rin : acetylsalicylic acid
    à : (1) (indicates surprise, sympathy); (2) (sentence starting particle), oh, by the way; (3) to rush, flood
    à này : by the way; incidentally
    à quên : oh, I almost forgot
    à uôm : to lump, group together
    ả : woman, lass, girl
    ả đào : geisha, songstress
    ả đầu : geisha, songstress
    ả giang hồ : prostitute, street-walker
    ...


    Mọi dấu ngăn/cách, kể cả những chuỗi nhiều dấu ngăn cách liên tiếp, đều bị bỏ qua:

    Code:
    ...
    nhất nguyên luận : monism
    nhất nhất : one and all, all, everything, everything without exception, each and every one
    nhất ... nhì : first (do sth), then (do sth else)
    nhất phẩm : highest rank (of mandarins)
    nhất quán : consistent; consistence
    ...
    nhỡ tàu : to miss the boat (literally and figuratively)
    nhỡ thì : (of women) too old to get married
    nhỡ … thì sao : what if
    nhỡ thời : miss a chance
    nhớ : to remember, recall, miss
    ...


    Do các dấu ngăn cách đều bị bỏ qua, khi so sánh hai từ chỉ sai biệt ở dấu ngăn/cách, dấu cách (chẳng hạn, dấu trắng) có thể được xếp trước hoặc sau dấu ngăn (chẳng hạn, dấu gạch nối). Thứ tự khi ấy là ngẫu nhiên và, nếu đúng, chỉ là tình cờ:

    Code:
    ...
    ô rê ô mi xin : auromycine
    ô-rê-ô-mi-xin : aureomycin
    ô rô : bear’s breech (tree)
    ô tạp : miscellaneous
    ô tặc cốt : cuttle bone
    ô ten : hotel
    ô-ten : hotel
    ô tô : automobile, car
    ô-tô : auto
    ô tô buýt : autobus
    ô-tô-buýt : autobus
    ô tô ca : autocar
    ô-tô-ca : autocar
    ô tô ma tích : automatic
    ô-tô-ma-tích : automatic
    ô tô mat : automaton
    ô-tô-mát : automaton, robot
    ô tô ray : railcar
    ô-tô-ray : railcar
    ô trọc : impure, corrupt
    ô tủ : wardrobe
    ô uế : dirty, impure, filthy; filth
    ô văng : awning, canopy
    ô-văng : awning, canopy, porch roof
    ô vuông : square
    ô xi : oxygen
    ô-xi : oxygen
    ô xít : oxide
    ô-xít : oxide
    ô zôn : ozone
    ô-zôn : ozone
    ...


    Tạm thời, khác biệt chữ hoa/thường được bỏ qua. Mã phân biệt hoa/thường, nếu có đi nữa cũng hầu như không được chạy cho từ điển thí dụ này, vì có rất ít cặp từ chỉ sai biệt ở chữ hoa/thường, như:

    Code:
    ...
    ba lê : ballet
    Ba Lê : Paris
    ...

    Một lần nữa, do kết quả so sánh hai từ này là "bằng nhau", thứ tự của chúng ở đây chỉ là đúng một cách tình cờ.


    EDIT. Để thấy tận mắt rằng những thứ tự trên chỉ là tình cờ đúng, trước khi sắp xếp từ điển, hãy xáo trộn nó:
    C++ Code:
    1. std::random_shuffle(td.begin(),td.end());
    Đã được chỉnh sửa lần cuối bởi Ada : 04-09-2021 lúc 02:38 AM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  9. #19
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Bài vừa rồi, mình đã thực thi được một phần thuật toán so sánh 2 xâu Việt. Cụ thể, 2 cấp so sánh thô, là chữ cái và dấu thanh, tạm bỏ qua 2 cấp so sánh tinh, là phân biệt chữ hoa/thường và các dấu ngăn/cách.

    Bài này, mình sẽ thực thi hoàn toàn cả 4 cấp, tiến đến hoàn thành chủ đề.

    Trước hết, thực thi đến cấp 3 (phân biệt hoa/thường), tạm bỏ qua cấp 4 (các dấu ngăn cách).

    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <vector>
    3. #include <algorithm>
    4. #include <iostream>
    5. #include <cstdio>
    6. #include "unicode/unistr.h"
    7. #include "unicode/utypes.h"
    8. #include "unicode/locid.h"
    9. #include "unicode/coll.h"
    10. #include "unicode/tblcoll.h"
    11. #include "unicode/coleitr.h"
    12. #include "unicode/sortkey.h"
    13. #include "unicode/utext.h"
    14. #include "unicode/brkiter.h"
    15.  
    16. #include <windows.h>    // Uncomment this line if trouble
    17.  
    18. using namespace icu;
    19. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    20.  
    21. struct Entry {
    22.    std::string Key;
    23.    std::string Val;
    24. };
    25. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    26.  
    27. void scan(Entry& y)
    28. {
    29.    // scan one line to an Entry
    30.    getline(std::cin,y.Key,':');
    31.    getline(std::cin,y.Val);
    32. }
    33. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    34.  
    35. void print(const Entry& x)
    36. {
    37.    // print an Entry to one line
    38.    std::cout << x.Key << ':' << x.Val << '\n';
    39.    //printf_s("%s:%s\n",x.Key.c_str(),x.Val.c_str());
    40. }
    41. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    42.  
    43. UBool collateWithLocaleUTF8(const Locale& locale, UErrorCode& status)
    44. {
    45.    UnicodeString dispName;
    46.    UCollationResult result; //    = UCOL_EQUAL;
    47.    Collator *myCollator = 0, *myCollcase = 0;
    48.    UErrorCode error = U_ZERO_ERROR;
    49.    if (U_FAILURE(status))
    50.    {
    51.       return false;
    52.    }
    53.    myCollator = Collator::createInstance(locale, status);
    54.    if (U_FAILURE(status))
    55.    {
    56.       locale.getDisplayName(dispName);
    57.       /*Report the error with display name... */
    58.       fprintf(stderr,
    59.       "%s: Failed to create the collator for : \"%s\"\n", dispName);
    60.       return false;
    61.    }
    62.    myCollator->setStrength(Collator::SECONDARY);
    63.  
    64.    myCollcase = Collator::createInstance(locale, status);
    65.    myCollcase->setStrength(Collator::PRIMARY);
    66.    myCollcase->setAttribute(UCOL_CASE_LEVEL, UCOL_ON, status);
    67.    myCollcase->setAttribute(UCOL_ALTERNATE_HANDLING,UCOL_SHIFTED, status);
    68.  
    69.    std::vector<Entry> td;
    70.    for(;;){
    71.       Entry e;
    72.       scan(e);
    73.       if(e.Key.size() == 0) break;
    74.       td.push_back(e);
    75.    }
    76.  
    77.    BreakIterator * bis =
    78.       BreakIterator::createWordInstance(Locale::getUS(), status);
    79.    BreakIterator * bit =
    80.       BreakIterator::createWordInstance(Locale::getUS(), status);
    81.  
    82.    UText us = UTEXT_INITIALIZER;
    83.    UText ut = UTEXT_INITIALIZER;
    84.  
    85.    std::sort(td.begin(), td.end(), [myCollator,myCollcase,bis,bit,
    86.       &us,&ut,&status,&error] (const Entry& s, const Entry& t) {
    87.          utext_openUTF8(&us, s.Key.c_str(), -1, &status);
    88.          utext_openUTF8(&ut, t.Key.c_str(), -1, &status);
    89.          bis->setText(&us, status);
    90.          bit->setText(&ut, status);
    91.          int32_t s0 = bis->first(), s1;
    92.          int32_t t0 = bit->first(), t1;
    93.          int32_t const DONE = BreakIterator::DONE;
    94.          for(;; s0=s1, t0=t1){
    95.             for(;; s0=s1){
    96.                s1 = bis->next();
    97.                if(s1 == DONE) break;
    98.                if(bis->getRuleStatus() != UBRK_WORD_NONE) break;
    99.             }
    100.             for(;; t0=t1){
    101.                t1 = bit->next();
    102.                if(t1 == DONE) break;
    103.                if(bit->getRuleStatus() != UBRK_WORD_NONE) break;
    104.             }
    105.             if(s1 != DONE && t1 != DONE){
    106.                StringPiece ws(&s.Key[s0], s1-s0);
    107.                StringPiece wt(&t.Key[t0], t1-t0);
    108.                int cr = myCollator->compareUTF8(ws, wt, error);
    109.                if(cr > 0) return false; // GREATER
    110.                if(cr < 0) return true;  // LESS
    111.             }
    112.             else if(s1 != DONE && t1 == DONE) return false; // GREATER
    113.             else if(s1 == DONE && t1 != DONE) return true;  // LESS
    114.             else /* s1 == DONE && t1 == DONE */ {
    115.                return myCollcase->compareUTF8(s.Key, t.Key, error) < 0;
    116.             }
    117.          }
    118.       }
    119.    );
    120.    for(auto e:td){ print(e); }
    121.    utext_close(&us);
    122.    utext_close(&ut);
    123.    delete bis;
    124.    delete bit;
    125.    delete myCollator;
    126.    delete myCollcase;
    127.    return true;
    128. }
    129. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    130.  
    131. int main()
    132. {
    133.    SetConsoleOutputCP(CP_UTF8); // Uncomment this line if trouble
    134.    UErrorCode status = U_ZERO_ERROR;
    135.    if (collateWithLocaleUTF8(Locale("vi","VN"), status) != true)
    136.    {
    137.         fprintf(stderr,
    138.         "\nCollate with locale in C++ failed.\n");
    139.    }
    140.    status = U_ZERO_ERROR;
    141.    return 0;
    142. }
    143. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/

    Khác biệt so với phiên bản trước là nếu so sánh thô (bằng một Collator tên là myCollator) cho kết quả EQUAL (bằng nhau) thì tiếp tục so sánh tinh (bằng một Collator khác, tên là myCollcase). Khác myCollator vốn so sánh từng chữ, myCollcase làm việc trên toàn xâu.

    Tuy có thể setStrength(TERTIARY), nhưng mình không làm thế, vì làm thế là phải so sánh 2 xâu từ đầu đến cuối [thêm] những 3 lần, dẫu đã thừa biết 2 lần đầu cho kết quả "bằng nhau". Thay vào đó, mình setStrength(PRIMARY) để chỉ so sánh [thêm] 1 lần, và bật UCOL_CASE_LEVEL để phân biệt case (chữ hoa/chữ thường) ngay ở lần so sánh đầu tiên này. Nói bằng thuật ngữ, UCOL_CASE_LEVEL đẩy phân biệt case lên cấp 1 và PRIMARY tắt hết các cấp còn lại.

    Khi so sánh đa cấp, một yêu cầu về tính đúng đắn là phép so sánh tinh phải tương đồng với phép so sánh thô, nghĩa là gọi < là phép so sánh thô còn << là phép so sánh tinh, phải bảo đảm với 2 xâu s, t bất kỳ, nếu s < t thì s << t. Cụ thể, ở đây, < là phép so sánh bằng myCollator còn << là phép so sánh bằng myCollatormyCollcase; và 2 phép so sánh ấy tương đồng có nghĩa là myCollcase tôn trọng kết quả của myCollator. Nghĩa là myCollcase phải đảm bảo, chẳng hạn, ngựa Gióng = NGỰA GIÓNG sau 2 cấp so sánh đầu tiên của mình, bất chấp 2 từ ấy đã bị biến dạng ra sao bằng các dấu ngăn cách được viết thêm vào trước, sau hay giữa 2 chữ của chúng, vì đó chính là kết quả của myCollator.

    Đó là mục đích của lựa chọn UCOL_SHIFTED. Lựa chọn này dịch (shift) trọng số của các dấu ngăn cách từ cấp 1 xuống cấp 4. Nghĩa là khi so sánh ở cấp 1, 2 và 3, mọi dấu ngăn cách bị bỏ qua.


    Để kiểm nghiệm chương trình này, từ điển Denisowski không thích hợp. Mình dùng một "từ điển" nặng tính nhân tạo, chứa 32 từ Việt bằng nhau ở 2 cấp so sánh thô -- 32 biến thể của từ ngựa Gióng định danh bằng phần giải nghĩa sau dấu hai chấm ( : ). Tệp đính kèm. Ký hiệu C và CD, lần lượt, chỉ từ Việt được mã hóa bằng chuỗi ký tự dựng sẵn và tổ hợp. Để hiển thị rõ ràng, mình dùng dấu gạch nối ( - ) thay cho dấu cách.

    Kết quả
    Code:
    -ngựa-gióng: 1 C
    ngựa--gióng: 2 C
    ngựa-gióng-: 3 C
    ngựa-gióng:  0 CD
    -ngựa-gióng: 1 CD
    ngựa--gióng: 2 CD
    ngựa-gióng-: 3 CD
    ngựa-gióng:  0 C
    ngựa-giÓng:  8 C
    ngựa-giÓng-: b CD
    ngựa--giÓng: a CD
    -ngựa-giÓng: 9 CD
    ngựa-giÓng:  8 CD
    ngựa-giÓng-: b C
    ngựa--giÓng: a C
    -ngựa-giÓng: 9 C
    ngựa-Gióng:  c C
    -ngựa-Gióng: d C
    ngựa--Gióng: e C
    ngựa-Gióng-: f C
    ngựa-Gióng:  c CD
    -ngựa-Gióng: d CD
    ngựa--Gióng: e CD
    ngựa-Gióng-: f CD
    ngỰa-gióng-: 7 CD
    ngỰa--gióng: 6 CD
    -ngỰa-gióng: 5 CD
    ngỰa-gióng:  4 CD
    ngỰa-gióng-: 7 C
    ngỰa--gióng: 6 C
    -ngỰa-gióng: 5 C
    ngỰa-gióng:  4 C
    Do các dấu ngăn cách vẫn đang được tạm thời bỏ qua, các từ 0, 1, 2, 3 bất kể định dạng C hay CD, chẳng hạn, vốn đều là các biến thể của ngựa gióng, đều "bằng nhau" và vì thế, chúng được sắp xếp gần sát nhau, theo một thứ tự ngẫu nhiên nào đó. Cả 8 biến thể của ngựa giÓng, ngựa GióngngỰa gióng cũng thế.



    Cuối cùng, là thực thi cả 4 cấp so sánh, nghĩa là xem xét cả sai khác ở các dấu cách, ngăn. Trên lý thuyết, sai khác đó vẫn chưa phải là nhỏ nhất và do thế, so sánh 4 cấp vẫn chưa phải là phép so sánh tinh nhất. Trên thực tế, có những thứ tiếng đòi hỏi phải so sánh đến cấp cuối cùng (cấp 5); nhưng trên dữ liệu chủ yếu là tiếng Việt, trong hầu hết ứng dụng, 4 cấp là đủ lắm rồi.

    Mình không post code cho phiên bản này nữa. Từ phiên bản trên đến phiên bản này, chỉ cần loại bỏ câu setAttribute(UCOL_CASE_LEVEL,...) và thay thế PRIMARY bằng QUATERNARY là xong.

    Kết quả
    Code:
    -ngựa-gióng: 1 C
    -ngựa-gióng: 1 CD
    ngựa--gióng: 2 C
    ngựa--gióng: 2 CD
    ngựa-gióng:  0 CD
    ngựa-gióng:  0 C
    ngựa-gióng-: 3 CD
    ngựa-gióng-: 3 C
    -ngựa-giÓng: 9 CD
    -ngựa-giÓng: 9 C
    ngựa--giÓng: a C
    ngựa--giÓng: a CD
    ngựa-giÓng:  8 C
    ngựa-giÓng:  8 CD
    ngựa-giÓng-: b CD
    ngựa-giÓng-: b C
    -ngựa-Gióng: d C
    -ngựa-Gióng: d CD
    ngựa--Gióng: e C
    ngựa--Gióng: e CD
    ngựa-Gióng:  c CD
    ngựa-Gióng:  c C
    ngựa-Gióng-: f CD
    ngựa-Gióng-: f C
    -ngỰa-gióng: 5 CD
    -ngỰa-gióng: 5 C
    ngỰa--gióng: 6 C
    ngỰa--gióng: 6 CD
    ngỰa-gióng:  4 C
    ngỰa-gióng:  4 CD
    ngỰa-gióng-: 7 CD
    ngỰa-gióng-: 7 C
    Ở cấp so sánh tinh nhất này, hai từ 0 C và 0 CD chẳng hạn là "bằng nhau" và do đó, thứ tự của chúng là ngẫu nhiên; hai từ 1 C và 1 CD cũng thế. Nhưng từ 0 (C, CD) và từ 1 (C, CD) chẳng hạn là "khác nhau" và thứ tự của chúng được xác định, cụ thể, theo hướng dẫn Ignore Punctuation Options của thư viện ICU, như sau.

    Các dấu ngăn/cách thuộc về một nhóm lớn các ký tự biến thiên (variable) khác với các ký tự thông thường như chữ cái và chữ số. Khi so sánh các xâu đã "bằng nhau" đến cấp 3, tức các xâu có cùng chữ cái, dấu thanh và cùng kiểu chữ hoa/thường, thì:
    • Các ký tự biến thiên được xếp "nhỏ hơn" (đứng trên) các ký tự thông thường.
    • Một ký tự biến thiên được nối thêm vào một xâu sẽ tạo thành một xâu biến thể xếp xuống hàng "lớn hơn" (đứng dưới) xâu nguyên thủy. Thí dụ, từ 3 xếp dưới từ 0.
    • Một ký tự biến thiên được chèn vào một xâu sẽ tạo thành một xâu biến thể xếp lên hàng "nhỏ hơn" (đứng trên) xâu nguyên thủy. Thí dụ, từ 2 và từ 1 xếp trên từ 0.
    • Việc chèn một ký tự biến thiên vào gần đầu một xâu sẽ tạo ra một xâu biến thể được xếp vào hàng "nhỏ hơn" (đứng trên) một biến thể khác với chính ký tự ấy chèn vào ở xa đầu hơn. Thí dụ, từ 1 xếp trên từ 2, còn từ 2 xếp trên từ 3.

    Tác dụng thực tế của UCOL_SHIFTED có thể thấy rõ bằng cách thực nghiệm với nó trên chương trình so sánh 2 xâu ở [bài #1].

    Ai cảm thấy chủ đề này hữu ích và có ý định sử dụng thư viện ICU trong dự án của mình, nên thử thay thế dấu gạch nối bằng dấu gạch dưới ( _ ) trong tệp dữ liệu đính kèm và chạy lại chương trình với dữ liệu mới đó. Nếu ngạc nhiên về kết quả, nên quay lại thực nghiệm tách chữ (từ) bằng chương trình mình đã gửi ở [bài #17], với các dấu ngăn/cách khác nhau, ít ra là các dấu thông dụng (sẵn có trên bàn phím), để thấy tác động khác nhau của chúng trong việc tách chữ/từ.
    Attached Files Attached Files
    Đã được chỉnh sửa lần cuối bởi Ada : 21-09-2021 lúc 11:15 AM. Lý do: Densowski -> Denisowski
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  10. #20
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    742

    Ở bài #17, mình nói rằng hàm tách chữ đạt tốc độ có thể so sánh được với hàm thư viện chuẩn strtok(). Hàm ý là các phép so sánh xâu dựa trên đó (bài #18, #19) có thể so sánh được với phép so sánh "chuẩn" của thư viện ICU (bài #2). Tuy nhiên, "có thể so sánh được" chỉ là gần bằng, chứ chưa bằng. Thực nghiệm trên từ điển Việt-Anh (đính kèm bài #2) cho thấy câu lệnh std::sort() ở chương trình #18, #19 chạy lâu gấp 10 lần câu lệnh ấy ở chương trình #2.

    Điều đó chứng tỏ rằng phép tách chữ (từ) bằng BreakIterator vẫn còn ở mức quá cao. Dùng nó cho một phép toán mức thấp như phép so sánh và sắp thứ tự xâu, giống như dùng trọng pháo để bắn... ruồi.

    Dưới đây, mình gửi phiên bản mới của chương trình sắp xếp từ điển. Nhanh như #2.

    Nó tìm ranh chữ (từ) theo dấu hiệu là byte có giá trị nhỏ hơn 64, biết rằng mọi ký tự là chữ đều có điểm mã >= 64, còn mọi ký tự nhỏ hơn 64 đều là dấu ngăn/cách hoặc chữ số. Nghĩa là nó tìm một ký tự ASCII trong xâu bằng cách quét xâu như một mảng byte mà không cần giải mã, lợi dụng tính chất của mã UTF-8 là mọi ký tự với điểm mã < 128 (ASCII) đều đơn byte và có mã trùng với điểm mã, còn mọi ký tự có điểm mã >= 128 (phi ASCII) đều đa byte và mỗi byte đều >= 128.

    So sánh thô (không phân biệt chữ hoa/thường, bỏ qua các dấu ngăn/cách)
    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <vector>
    3. #include <algorithm>
    4. #include <iostream>
    5. #include <cstdio>
    6. #include <ctime>
    7. #include "unicode/unistr.h"
    8. #include "unicode/utypes.h"
    9. #include "unicode/locid.h"
    10. #include "unicode/coll.h"
    11. #include "unicode/tblcoll.h"
    12. #include "unicode/coleitr.h"
    13. #include "unicode/sortkey.h"
    14. #include "unicode/utext.h"
    15. #include "unicode/brkiter.h"
    16.  
    17. #include <windows.h>    // Uncomment this line if trouble
    18.  
    19. using namespace icu;
    20. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    21.  
    22. struct Entry {
    23.    std::string Key;
    24.    std::string Val;
    25. };
    26. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    27.  
    28. void scan(Entry& y)
    29. {
    30.    // scan one line to an Entry
    31.    getline(std::cin,y.Key,':');
    32.    getline(std::cin,y.Val);
    33. }
    34. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    35.  
    36. void print(const Entry& x)
    37. {
    38.    // print an Entry to one line
    39.    std::cout << x.Key << ':' << x.Val << '\n';
    40.    //printf_s("%s:%s\n",x.Key.c_str(),x.Val.c_str());
    41. }
    42. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    43.  
    44. UBool collateWithLocaleUTF8(const Locale& locale, UErrorCode& status)
    45. {
    46.    UnicodeString dispName;
    47.    UCollationResult result; //    = UCOL_EQUAL;
    48.    Collator *myCollator = 0, *myCollcase = 0;
    49.    UErrorCode error = U_ZERO_ERROR;
    50.    if (U_FAILURE(status))
    51.    {
    52.       return false;
    53.    }
    54.    myCollator = Collator::createInstance(locale, status);
    55.    if (U_FAILURE(status))
    56.    {
    57.       locale.getDisplayName(dispName);
    58.       /*Report the error with display name... */
    59.       fprintf(stderr,
    60.       "%s: Failed to create the collator for : \"%s\"\n", dispName);
    61.       return false;
    62.    }
    63.    myCollator->setStrength(Collator::SECONDARY);
    64.  
    65.    std::vector<Entry> td;
    66.    for(;;){
    67.       Entry e;
    68.       scan(e);
    69.       if(e.Key.size() == 0) break;
    70.       td.push_back(e);
    71.    }
    72.  
    73.    std::random_shuffle(td.begin(),td.end());
    74.  
    75.    std::sort(td.begin(), td.end(),
    76.       [myCollator,&error,&status](const Entry& s, const Entry& t){
    77.          char const *s0 = &s.Key[0], *s1 = s0;
    78.          char const *t0 = &t.Key[0], *t1 = t0;
    79.          char const * const DONE = nullptr;
    80.          for(;; s0=s1, t0=t1){
    81.             for(;; s1++,s0=s1){
    82.                if(*s1==0) {
    83.                   s1 = DONE;
    84.                   break;
    85.                }
    86.                else if(uint8_t(*s1) >= 64){
    87.                   do{ s1++; }while(uint8_t(*s1) >= 64);
    88.                   break;
    89.                }
    90.             }
    91.             for(;; t1++,t0=t1){
    92.                if(*t1==0) {
    93.                   t1 = DONE;
    94.                   break;
    95.                }
    96.                else if(uint8_t(*t1) >= 64) {
    97.                   do{ t1++; }while(uint8_t(*t1) >= 64);
    98.                   break;
    99.                }
    100.             }
    101.             if(s1 != DONE && t1 != DONE){
    102.                StringPiece ws(s0, s1-s0);
    103.                StringPiece wt(t0, t1-t0);
    104.                int cr = myCollator->compareUTF8(ws, wt, error);
    105.                if(cr > 0) return false; // GREATER
    106.                if(cr < 0) return true;  // LESS
    107.             }
    108.             else if(s1 != DONE && t1 == DONE)   return false;// GREATER
    109.             else if(s1 == DONE && t1 != DONE)   return true; // LESS
    110.             else /* s1 == DONE && t1 == DONE */ return false;// EQUAL
    111.          }
    112.       }
    113.    );
    114.  
    115.    for(auto e:td){ print(e); }
    116.  
    117.    delete myCollator;
    118.    return true;
    119. }
    120. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    121.  
    122. int main()
    123. {
    124.    SetConsoleOutputCP(CP_UTF8); // Uncomment this line if trouble
    125.    UErrorCode status = U_ZERO_ERROR;
    126.    if (collateWithLocaleUTF8(Locale("vi","VN"), status) != true)
    127.    {
    128.         fprintf(stderr,
    129.         "\nCollate with locale in C++ failed.\n");
    130.    }
    131.    status = U_ZERO_ERROR;
    132.    return 0;
    133. }
    134. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/

    So sánh tinh (phân biệt chữ hoa/thường, phân biệt các dấu ngăn/cách)
    C++ Code:
    1. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    2. #include <vector>
    3. #include <algorithm>
    4. #include <iostream>
    5. #include <cstdio>
    6. #include <ctime>
    7. #include "unicode/unistr.h"
    8. #include "unicode/utypes.h"
    9. #include "unicode/locid.h"
    10. #include "unicode/coll.h"
    11. #include "unicode/tblcoll.h"
    12. #include "unicode/coleitr.h"
    13. #include "unicode/sortkey.h"
    14. #include "unicode/utext.h"
    15. #include "unicode/brkiter.h"
    16.  
    17. #include <windows.h>    // Uncomment this line if trouble
    18.  
    19. using namespace icu;
    20. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    21.  
    22. struct Entry {
    23.    std::string Key;
    24.    std::string Val;
    25. };
    26. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    27.  
    28. void scan(Entry& y)
    29. {
    30.    // scan one line to an Entry
    31.    getline(std::cin,y.Key,':');
    32.    getline(std::cin,y.Val);
    33. }
    34. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    35.  
    36. void print(const Entry& x)
    37. {
    38.    // print an Entry to one line
    39.    std::cout << x.Key << ':' << x.Val << '\n';
    40.    //printf_s("%s:%s\n",x.Key.c_str(),x.Val.c_str());
    41. }
    42. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    43.  
    44. UBool collateWithLocaleUTF8(const Locale& locale, UErrorCode& status)
    45. {
    46.    UnicodeString dispName;
    47.    UCollationResult result; //    = UCOL_EQUAL;
    48.    Collator *myCollator = 0, *myCollcase = 0;
    49.    UErrorCode error = U_ZERO_ERROR;
    50.    if (U_FAILURE(status))
    51.    {
    52.       return false;
    53.    }
    54.    myCollator = Collator::createInstance(locale, status);
    55.    if (U_FAILURE(status))
    56.    {
    57.       locale.getDisplayName(dispName);
    58.       /*Report the error with display name... */
    59.       fprintf(stderr,
    60.       "%s: Failed to create the collator for : \"%s\"\n", dispName);
    61.       return false;
    62.    }
    63.    myCollator->setStrength(Collator::SECONDARY);
    64.    myCollator->setAttribute(UCOL_ALTERNATE_HANDLING,UCOL_SHIFTED,status);
    65.  
    66.    myCollcase = Collator::createInstance(locale, status);
    67.    myCollcase->setStrength(Collator::QUATERNARY);
    68.    myCollcase->setAttribute(UCOL_ALTERNATE_HANDLING,UCOL_SHIFTED,status);
    69.  
    70.    std::vector<Entry> td;
    71.    for(;;){
    72.       Entry e;
    73.       scan(e);
    74.       if(e.Key.size() == 0) break;
    75.       td.push_back(e);
    76.    }
    77.    std::random_shuffle(td.begin(),td.end());
    78.  
    79.    std::sort(td.begin(), td.end(),
    80.       [myCollator,myCollcase,&error,&status]
    81.             (const Entry& s, const Entry& t){
    82.          char const *s0 = &s.Key[0], *s1 = s0;
    83.          char const *t0 = &t.Key[0], *t1 = t0;
    84.          char const * const DONE = nullptr;
    85.          for(;; s0=s1, t0=t1){
    86.             for(;; s1++,s0=s1){
    87.                if(*s1==0) {
    88.                   s1 = DONE;
    89.                   break;
    90.                }
    91.                else if(uint8_t(*s1) >= 64){
    92.                   do{ s1++; }while(uint8_t(*s1) >= 64);
    93.                   break;
    94.                }
    95.             }
    96.             for(;; t1++,t0=t1){
    97.                if(*t1==0) {
    98.                   t1 = DONE;
    99.                   break;
    100.                }
    101.                else if(uint8_t(*t1) >= 64) {
    102.                   do{ t1++; }while(uint8_t(*t1) >= 64);
    103.                   break;
    104.                }
    105.             }
    106.             if(s1 != DONE && t1 != DONE){
    107.                StringPiece ws(s0, s1-s0);
    108.                StringPiece wt(t0, t1-t0);
    109.                int cr = myCollator->compareUTF8(ws, wt, error);
    110.                if(cr > 0) return false; // GREATER
    111.                if(cr < 0) return true;  // LESS
    112.             }
    113.             else if(s1 != DONE && t1 == DONE) return false; // GREATER
    114.             else if(s1 == DONE && t1 != DONE) return true;  // LESS
    115.             else /* s1 == DONE && t1 == DONE */ {
    116.                return myCollcase->compareUTF8(s.Key, t.Key, error) < 0;
    117.             }
    118.          }
    119.       }
    120.    );
    121.  
    122.    for(auto e:td){ print(e); }
    123.  
    124.    delete myCollator;
    125.    delete myCollcase;
    126.    return true;
    127. }
    128. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/
    129.  
    130. int main()
    131. {
    132.    SetConsoleOutputCP(CP_UTF8); // Uncomment this line if trouble
    133.    UErrorCode status = U_ZERO_ERROR;
    134.    if (collateWithLocaleUTF8(Locale("vi","VN"), status) != true)
    135.    {
    136.         fprintf(stderr,
    137.         "\nCollate with locale in C++ failed.\n");
    138.    }
    139.    status = U_ZERO_ERROR;
    140.    return 0;
    141. }
    142. /*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*//*\\*/

    Nó khá chính xác. Đối với các dấu ngăn/cách thông thường, nó xử lý "ngon lành":
    Code:
    ...
    nhất nhất : one and all, all, everything, everything without exception, each and every one
    nhất ... nhì : first (do sth), then (do sth else)
    nhất phẩm : highest rank (of mandarins)
    ...
    nhưng đối với các dấu ngăn/cách phi ASCII, nó có thể gây ra sai biệt nhỏ so với kết quả chuẩn đã nêu ở bài #18:
    Code:
    ...
    nhỡ : (1) of medium size, medium-sized; (2) to miss (train, meal, etc.)
    nhỡ … thì sao : what if
    nhỡ dịp : miss the opportunity, lose the chance
    nhỡ hẹn : to fail to keep an appointment, miss a date or an appointment
    nhỡ nhời : make a slip of the tongue
    nhỡ tàu : to miss the boat (literally and figuratively)
    nhỡ thì : (of women) too old to get married
    nhỡ thời : miss a chance
    ...
    Cụm từ nhỡ … thì sao được xếp đứng trên nhỡ thì và bất cứ cụm từ nào có dạng nhỡ WX, với W là một chữ không trống và X là một xâu, bởi vì dấu ba chấm (…), vốn có điểm mã >= 64, đã không được bỏ qua và xâu con "" đã được tách như một chữ, để rồi được đem so sánh với xâu con W. Bất kể Collator *myCollator được cấu hình ra sao (có hay không bỏ qua các dấu ngăn/cách, kể cả dấu ba chấm) phép so sánh myCollator->compareUTF8() vẫn luôn cho kết quả "" < W.
    Đã được chỉnh sửa lần cuối bởi Ada : 12-09-2021 lúc 10:20 AM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn