การจัดเรียงภาษาไทยใน Python

เป็นปัญหาเดิม ๆ ที่โปรแกรมเมอร์ไทยรู้จักกันดี นั่นคือ การจัดเรียงภาษาไทย เพราะภาษาไทยเป็นภาษาที่ซับซ้อนมาก สระวางได้ตั้งสี่ตำแหน่ง แถมมีวรรณยุกต์ การันต์ ไม้ไต่คู้ (เขียนถูกบ้างหรือเปล่าเนี่ย) และอื่น ๆ อีกมากมาย การจัดเรียงโดยใช้วิธีมาตรฐานเลยมักไม่ได้ผล ก็เลยต้องเขียนวิธีจัดเรียงกันใหม่ ซึ่งในภาษา C พี่เทพ ได้ทำไว้แล้วใน Thai Sorting Algorithms นอกจากนี้ใน Narisa มีคนทำเป็นภาษา PHP ไว้แล้วด้วย ผมคิดว่าเราน่าจะลองทำในภาษาอื่นกันบ้าง (ภาษาอื่น ๆ สนใจเชิญได้ครับ)

ผมขอเริ่มด้วย python ก่อนครับ

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import re
 
def normalize(str_in):
	regex = re.compile('\xe0\xb9[\x80\x81\x82\x83\x84\x88\x89\x8a\x8b][\w\W][\w\W][\w\W]')
	pattern = regex.sub('%s', str_in)
	return pattern%tuple([i[3:]+i[:3] for i in regex.findall(str_in)])
 
class thstr(str):
	def __le__(self,s):
		return normalize(self.__str__()) <= normalize(s)
	def __lt__(self,s):
		return normalize(self.__str__()) <  normalize(s)
	def __ge__(self,s):
		return normalize(self.__str__()) >= normalize(s)
	def __gt__(self,s):
		return normalize(self.__str__()) >  normalize(s)
 
test = ['บา', 'บ่น', 'บ่า','บ้า','บ้าน','โบว์','ปลา','ปี','แปล']
f = open('out.txt','w')
for i in sorted(test):
	f.write(i+',')
f.write('\n')
for i in sorted(map(thstr,test)):
	f.write(i+',')
f.close()

ผมใช้วิธี Operation Overriding พวก Operation เปรียบเทียบต่าง ๆ ทำให้เรียกใช้ sort หรือ sorted ได้ทันที แต่ก่อนจะเปรียบเทียบต้อง normalize ก่อน ซึ่งไอ้ normalize นี่แหละยาก ในโปรแกรมที่ผมเขียนครอบคลุมเฉพาะกรณีที่มี วรรณยุกต์ และสระ เ,แ,โ,ไ,ใ เท่านั้น ยังขาดพวกอักษรนำ (เช่น "หมา" ต้องไปเรียงกับ "ม้า" ไม่ใช่ "หา") white space แบบต่าง ๆ และอื่น ๆ อีกมากมาย สนใจร่วมสนุกกันได้ครับ รับรองไม่มีของรางวัล :P

sugree's picture

โอ้ เจ๋ง โชคดีที่ไม่ยุ่งกับภาษาไทย

ไม่เจ๋งหรอกครับ เพราะจริง ๆ ก็เอาแนวคิดมาจากพี่เทพ อีกทั้งยังการ normalize ยังไม่ดีพอ คุณ sugree ไม่สนใจร่วมสนุกหน่อยเหรอครับ แต่ผมว่าเรื่องนี้ขาประจำอย่างคุณ วีร์ น่าจะมีประสบการณ์มากที่สุด

sugree's picture

ผมยอมรับว่าไม่เข้าใจภาษาไทยดีพอครับ ยังสับสนกับการเรียงใน unicode กับ ascii ด้วย

ผมเห็นใน perl มี module ช่วยอยู่ตัว

http://search.cpan.org/src/SBURKE/Sort-ArbBiLex-4.01/README

แต่ขี้เกียจลอง perl ก็เลยไปลอง idea ใน java แทน (ดูแล้วน่าจะคล้ายๆกัน)

ปกติใน java มันมี class ที่ช่วยเรื่องพวกนี้อยู่ ชื่อ Collator
ถ้าเขียนแบบไม่ต้องคิดอะไร ก็แบบนี้

final Collator thCollactor = Collator.getInstance(new Locale("th"));		
list = Arrays.asList(new String[] {"บน", "เบาะ", "แบะ","เบา", "ใบ", "บา", "บ่น", "บ่า","บ้า","บ้าน","โบว์","ปลา","ปี","แปล"});
Collections.sort(list, new Comparator<String>() {
	@Override
	public int compare(String o1, String o2) {
		return thCollactor.compare(o1, o2);
	}
});

อันข้างบน นั่นคือกฎการเรียงที่เขาเตรียมมาให้แล้ว
ถ้าเราจะกำหนดกฎเองก็ได้
ลองดูตัวอย่าง

// ตรงบรรทัดแรก จริงๆมันคือ =ไม้เอก;ไม้โท;ไม้ตรี.. (คั่นด้วย ;)
String simple = "=่ ;้ ;๊ ;็ " +
	"< ก < เก < แก < โก < ใก < ไก" +
	"< ข " +
	"< ค " +
	"< ง " +
	"< จ " +
	"< ฉ " +
	"< ช " +
	"< ด " +
	"< น" +
	"< บ < เบ < แบ < โบ < ใบ < ไบ" +
	"< ป < เป < แป < โป < ใป < ไป" +				 
	"< ย " +
	"< ร" +
	"< ล" +
	"< ว " +
	"< อ" +
	"< ะ < า ";
final RuleBasedCollator rb = new RuleBasedCollator(simple);
String list = Arrays.asList(new String[] {"บน", "เบาะ", "แบะ","เบา", "ใบ", "บา", "บ่น", "บ่า","บ้า","บ้าน","โบว์","ปลา","ปี","แปล"});Collections.sort(list, new Comparator<String>() {
	@Override
	public int compare(String o1, String o2) {
		return rb.compare(o1, o2);
	}
 
});
sugree's picture

โห แล้ว RuleBasedCollator นี่มันทำงานยังไงครับ

โอ... สุดยอดครับ เพิ่งรู้เหมือนกันว่า Java สามารถ override method แบบ on the fly ได้ด้วย แถมตัว Comparator ไม่ต้อง override method หลายตัวอีกต่างหาก

ผมมีคำถามเกี่ยวกับ RuleBasedCollator เหมือนกันครับ ว่าเราต้องกำหนด เ,แ,ไ,ใ,โ กับทุกพยัญชนะหรือเปล่า หรือว่าสามารถใช้ RegEx ในการกำหนดได้ด้วย

แล้วก็ค่าที่คืนจาก Comparator.compare() มีค่าอะไรบ้างครับ ในกรณี <, <=, >, >=, ==, !=

เรืองนี้พึ่งศึกษาเหมือนกันครับ
rule ข้างบนนี่นั่งมั่วๆอยู่นานเหมือนกัน (นี่เป็นสาเหตุที่ยังไม่กล้าอธิบายให้คุณสุกรีฟัง)

feature ที่ bow เห็น ใน java เรียกว่า annonymous class
http://www.unix.org.ua/orelly/java-ent/jnut/ch03_12.htm
http://www.developer.com/java/other/article.php/3300881

ดูแล้วก็คล้ายๆ closure, block ของพวก dynamic language เหมือนกัน
http://snakesgemscoffee.blogspot.com/2007/08/anonymous-classes-javas-syn...

เ, แ, ไ, ... เข้าใจว่าใช้ RegEx ไม่ได้นะ
ต้องขยันใส่ทุกตัวอักษร

ค่าที่คืนจาก compare มีค่าได้ดังนี้ -1, 0, 1
กรณี เท่ากัน return 0,
ถ้า left < left return -1
ถ้า left > right return 1

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น