Native Unicode sub-string support

ผมมองว่าสำหรับ Natural language processing ภาคปฏิบัติ การหา sub-string จาก Unicode string นี่มันสำคัญมากเลย

เอาเข้าจริงแทบทุกภาษา (programming language) ก็น่าจะใช้ Unicode ได้หมด ขึ้นอยู่กับว่าออกแรงมากออกแรงน้อยเท่านั้นเอง ถ้าทำง่าย ๆ ก็ดี

ในบทความไม่วิชาการนี้อยากจะลองใช้ substring กับ native string ของแต่ละภาษาเลยว่าจะทำงานได้อย่างที่ต้องการไหม อย่างเช่นว่า “abc”[0:1] ก็ควรจะได้ตัว a ออกมา แต่พอเป็น latin alphabet พวก a b c มันง่ายไป ภาษาส่วนมากก็ใช้ได้หมดถ้าเป็น “กขค”[0:1] ยากขึ้นมาหน่อย แต่เดี๋ยวนี้ก็เรียกว่าง่ายแล้ว

ผมก็เลยมาลองกับตัวอักษรนี่แทน 𨭎 เป็นกลุ่ม non-bmp หรือรหัส Unicode มีค่ามากกว่า 0xFFFF นั่นเอง

  1. ภาษาแรกเลย Python 3 นอกจากจะเขียนง่ายแล้วก็จัดการเรื่องนี้ได้เลยทำงานถูกต้องทุกประการ
    print("𨭎"[0:1])
    
  2. Ruby ก็ใช้ได้เนียน ๆ
    puts "𨭎"[0..1]
    
  3. JavaScript เจ๊ง … จริง JS นี่ใช้ภาษาไทยก็ได้ แต่เจอ non-bmp เจ๊ง
    console.log('𨭎'.substring(0,1));
    
  4. Lua เจ๊งชนิดที่ว่าภาษาไทยก็ไม่ได้

    print(string.sub("𨭎", 1, 1));

  5. PHP เจ๊งชนิดที่ว่าภาษาไทยก็ไม่ได้
    echo substr("𨭎",0,1);
    

ลองกลุ่ม duck type มาเยอะแล้ว ลองพวก static type บ้าง

  1. Rust น้องใหม่มาแรง เจ๊ง ภาษาไทยก็ไม่รอดเหมือนกัน แต่ดีหน่อยที่ว่าโปรแกรมหยุดทำงาน แล้วมี error message บอกว่า ไม่หั่นที่ byte แรกไม่ได้

    println!("{}", &("𨭎"[0..1]));

  2. Java เจ้าตลาด เจ๊ง
    System.out.println("𨭎".substring(0,1));
    
  3. Go ขี้เกียจเขียนครับ เคยลองไปหลายครั้งแล้วเจ๊งกระทั่งกับภาษาไทย แต่มีท่าแก้คือแปลงเป็น rune ก่อน
  4. C# บน Mono บน Fedora 21 เจ๊งเหมือน Java

    Console.WriteLine("𨭎".Substring(0,1));

  5. Free Pascal เจ๊ง
    Writeln(Copy('𨭎',1,1));
    
  6. Ada ใช้ Gnat บน Fedora 21 เจ๊ง

    procedure Main is
    Str : constant String := "𨭎";
    begin
    Put_Line (Str (1..1));
    end Main;

    แทบจะเรียกได้ว่า static type นี่แทบไม่มีอะไรใช้ได้เลย

  7. OCaml ขี้เกียจลองดูจาก spec แล้วควรจะเจ๊งแบบ Go
  8. Haskell ใช้ได้ เป็นภาษาแบบ static type ภาษาเดียวที่ไม่ต้องออกแรงมา

    putStrLn (take 1 "𨭎")

พิจารณามา 13 ภาษา ภาษาหา Unicode sub-string ได้สะดวก ๆ ก็คือ Python 3 ส่วน Python 2.7.x ไม่ได้นะ, Ruby 2 หรือ 1.9 ก็ได้มั้ง และถ้าต้องการ static type ก็มี Haskell ตัวเดียว ที่เป็น pure functional ด้วยอาจจะปรับตัวยากหน่อย

ภาษาอื่น ๆ ก็มีท่าแก้ เช่น PHP ใช้ mb_strsub และกำหนด internal encoding เป็น UTF-8; Rust ก็มีหลายท่าแต่ API มันยังไม่ stable หลัก ๆ คือแปลงเป็น Chars ก่อน; Go ก็แปลงเป็น []rune

ความคิดเห็นของผมก็คือถ้าไม่เน้นว่าโปรแกรมต้องทำงานเร็วมาก ก็ใช้ Python 3 ครับ แต่ถ้าต้องการเร็วจัด ๆ อันนี้คิดหนักแล้ว 😛

ขอบคุณ @plutonix (aka น้าธนา) ที่เอาเรื่อง codepoint กับ ES6 ให้อ่าน ผมถีงสำเนียกได้ว่า ภาษาที่ internal encoding เป็น UTF-16 มันมีกรณีที่เจ๊งแบบนี้

ใช้ Miktex

ผมลง basic-miktex-2.9.3972 และ texmaker บน Windows 7 สิ่งเคยใช้ได้บน Fedora มันก็เจ๊ง ปัญหาก็เกิดจากขาด package บางอย่างไปเท่านั้นเอง สิ่งที่ต้องทำคือเรียก mpm ขึ้นมาลง thailatex ลง unicode และอะไรอื่นๆ ที่มันต้องการก็เสร็จแล้ว

ผมใช้ \usepackage[utf8x]{inputenc} และ \usepackage[thai,english]{babel} ด้วย มันอาจจะมีท่าดีกว่านี้แต่ก็ขี้เกียจแก้

เคยลองใช้ Word 2007 ก็ยังงงๆ อยู่ ก็เลยใช้ Latex ไปก่อนแล้วกัน

กรณีเจ๊งของ PHP และ regular expression เวลาเจอภาษไทย + UTF-8

ถ้ามี code แบบนี้ใช้ charset เป็น UTF-8

<?php
	print_r(preg_split("/\\s/", "ประเภท"));
?>

แบบนี้มันไม่น่าจะ split ได้เพราะว่า ไม่มี space ใน “ประเภท” เลยแต่มัน split ได้ซะงั้น

Array
(
    [0] => เธ›เธฃเธฐเน€เธ
    [1] => เธ—
)

แถวๆ “ภ” มันคงแปลงไปเป็นอะไรแล้ว code ไปเป็น space มั้ง

แต่ไม่ว่าจะเพราะว่าอะไร มีท่าแก้ง่ายๆ แบบนี้ใส่ u เข้าไป หายครับ

<?php
	print_r(preg_split("/\\s/u", "ประเภท"));
?>

ผมใช้ php 5.3.5 ที่มากับ xampp 1.7.4 และทดลองบน Windows 7 ครับ

Python + MySQL + UTF-8

import MySQLdb as db
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
conn = db.connect(host="localhost", user="root",
                  passwd="", db="kulex",
                  use_unicode=True, charset="UTF8")
c = conn.cursor()
c.execute("SELECT * FROM `Classifier`")
while True:
    row = c.fetchone()
    if row == None:
        break
    for col in row:
        print col
c.close()
conn.close()

CakePHP 1.2 + ภาษาไทย + UTF-8 + MySQL

default encoding ของ MySQL เท่าที่ผมใช้ไม่ได้เป็น UTF-8. เวลาใช้ CakePHP ก็เลยต้องแก้ configuration นิดหน่อยเพื่อทำให้ ใช้ UTF-8 และ MySQL ได้เนียนๆ. ใน CakePHP รุ่นก่อนๆ หน้านี้บางทีก็ต้องไปแก้ AppModel ที่ไม่ค่อยเท่เท่าไหร่ เพราะน่าจะต้องมาแก้อีกเวลา port ไปใช้ database ตัวอื่นที่ไม่ใช่ MySQL.

ใน Cake 1.2.x สามารถตั้งค่า encoding/charset แบบรวมศูนย์ได้ใน app/config/database.php เลย. ตามตัวอย่างแบบด้านล่าง

class DATABASE_CONFIG {

    var $default = array(
        'driver' =&gt; 'mysql',
        'persistent' =&gt; false,
        'host' =&gt; 'localhost',
        'login' =&gt; 'your_username',
        'password' =&gt; 'your_password',
        'database' =&gt; 'my2',
        'prefix' =&gt; '',
        'encoding' =&gt; 'UTF8' #ดูบรรทัดนี้เป็นสำคัญ
    );

    var $test = array(
        'driver' =&gt; 'mysql',
        'persistent' =&gt; false,
        'host' =&gt; 'localhost',
        'login' =&gt; 'user',
        'password' =&gt; 'password',
        'database' =&gt; 'test_database_name',
        'prefix' =&gt; '',
    );
}
?&gt;

เพื่อม ‘encoding’ => ‘UTF8’ เข้าไปก็ทำให้ใช้ภาษาไทยและ UTF-8 ได้เนียนๆ แล้ว.

พอมาเปิดใน phpmyadmin ที่ตั้งค่าให้ใช้ UTF-8 และภาษาไทย

ก็แสดงผลออกมาได้ถูกต้อง.

สรุปว่าถ้าอยากใช้ UTF-8 กับ MySQL ใน CakePHP 1.2.x ก็เข้าไปตั้งค่าได้ใน app/config/database.php

อ้างอิง