ตัดคำไทยด้วย C#

WARNING: Highly technical blog ahead

กาลครั้งหนึ่งนานมาแล้ว … อาจารย์สมชายแห่ง CP เคยเอารัฐธรรมนูญฉบับเก่ากับฉบับใหม่มาเทียบความถี่ในการปรากฎของคำ ตอนนั้นผมอึ้งมากที่รู้ว่า Java มันตัดคำไทยได้ด้วย แบบ built-in มาไม่ต้องหา library อะไรมาเพิ่มเลย >_<  ถ้าผมเข้าใจไม่ผิด Java ใช้ ICU ของ IBM ที่เป็น open source แต่ตัวที่อยู่ใน class library ของ Java จะเป็นเวอร์ชันเก่ากว่าหน่อย

เจ้า ICU ที่ว่านี่นอกจากตัดคำได้ (Boundary Analysis) แล้วมันยังทำอย่างอื่นที่เกี่ยวกับกับ localization & internationalization ได้อีกมากมายก่ายกองครับ ลองเข้าไปดูกันเอง IBM ทำไว้สองชุดคือ ICU4C และ ICU4J สำหรับ C, C++ และ Java (ไม่มี .NET แป่วว) ถ้าใครทันสมัยใช้ Firefox ที่ยังตัดคำไทยไม่ค่อยจะคล่องอาจจะได้ยินชื่อนี้บ่อย เพราะมีคนไทยฮาร์ดคอร์มากมายเอาโค้ด Firefox มาแก้ใส่ ICU ให้ตัดคำแล้ว build ใหม่ … แค่ฟังก็อยากจะอ้วกออกมาเป็น pointer กับไฟล์ .h แล้วว =__=’

ด้วยความที่ 3 วันนี้ผมเมาจัดหรือยังไงไม่ทราบ เลยใช้เวลาว่างอันมีอยู่น้อยนิดไปพยายามทำ binding ให้เรียกใช้ ICU จาก C# (และ .NET) ได้ครับ! ก่อนทำก็หา best practice โดยการไปถามที่แหล่งประจำเล็กน้อย ได้คำตอบมาแค่อันเดียวน่าเศร้าใจ TvT

ผมเข้าใจว่าการทำ binding มันจะต่างจาก wrapper ตรงที่ เราต้องนำเสนอ interface เดียว (หรือคล้ายๆ) กับที่ underlying C++ classes มันใช้อยู่ให้กับผู้ใช้ ส่วนการทำ wrapper มันเหมือนกับว่าเราไปสร้างอะไรซักอย่างหุ้ม C++ classes เหล่านั้นไว้ และให้ผู้ใช้เรียกใช้งานผ่าน interface ของเรา (โดยไม่รู้ว่าข้างในมีอะไรอยู่บ้าง – Facade Pattern อ่านว่า “ฟา-ซ้าด”) ซึ่งการทำ wrapper นี่มันง่ายกว่าโขเลยน่ะ

หลังจากลองถูๆไถๆ ใช้พลังกับ C++/CLI เหมือนที่เคยใช้ในซีเนียร์โปรเจค ก็ออกมาเป็นรูปเป็นร่าง อยู่ที่ ICU4NET สามารถลองดาวน์โหลดไปใช้ได้ ถึงชื่อจะบอกว่า ICU4NET แต่จริงๆแล้วตอนนี้มันมี class ที่ใช้งานได้อยู่ class เดียวคือ BreakIterator =_=” ตั้งใจไว้ว่าจะจัดการกับ class ในกลุ่ม boundary analysis ให้หมด

เอาโค้ดตัวอย่างให้ดูเล็กน้อย อันแรกเป็นแบบดั้งเดิม ลักษณะเดียวกับที่เขียนใน ICU4J และ ICU4C

private List<string> WordBreak(string text)
{
var sb = new StringBuilder();
var col = new List<string>();

using (BreakIterator bi = BreakIterator.CreateWordInstance(Locale.GetUS()))
{
bi.SetText(text);
int start = bi.First(), end = bi.Next();
while (end != BreakIterator.DONE)
{
col.Add(text.Substring(start, end - start));
start = end; end = bi.Next();
}
}

return col;
}

ส่วนอันนี้ผมเขียน Extension Method เพิ่มให้มัน return เป็น IEnumerable ได้ พวกขา 3.5 จะได้เล่นซนแบบนี้ได้ :’)

using (BreakIterator bi = BreakIterator.CreateWordInstance(Locale.GetUS()))
{
bi.SetText(uxText.Text);

// requires ICU4NETExtension to use Enumerate extension method
MessageBox.Show(string.Join(Environment.NewLine, bi.Enumerate()
.GroupBy(w => w)
.OrderBy(x => x.Count())
.Reverse()
.Select(x => x.Key + " : " + x.Count())
.Take(10)
.ToArray()));
}

ตอนแรกตั้งใจว่า ส่วนที่น่าจะเอาไปเล่นได้หลักๆคงเป็นพวก ASP.NET Web App แหละ แต่นั่งคิดๆดูแล้วมันมีส่วนประกอบที่เป็น library native ของ ICU หลายอันอยู่ ถ้าเข้าใจไม่ผิดคงมีปัญหากับเรื่อง trust level ของ IIS พอสมควร …

ผลพลอยได้จากการผลาญเวลา (แบบไร้เหตุผล เอามันส์ล้วนๆ) ครั้งนี้คือ ได้ศึกษา C# เพิ่มอีกนิดหน่อย แล้วก็ลองใช้ C++/CLI อีกนิสสสนึง หลังๆมานี่อยู่ที่ทำงานได้ใช้แต่ C++ จนรู้สึกตัวเองตามโลก C# ไม่ทัน มาอ่านโค้ดของ @chakrit (ขา 3.5 ตัวพ่อ) ทีนี่นั่งงงอยู่หลายนาที –..-‘ เออออ … น้ำหลักลดด้วยนะ ^ ^

จากประสบการณ์ ด้วยหัวข้อยบลอกประมาณนี้ ผมเชื่อว่าคงมี นิสิต/นักศึกษา ที่กำลังปั่นงานของอาจารย์แล้ว search มาเจอ และอยากนำไปใช้ได้โดยเร็ว … ผมขอร้องว่าก่อนจะทิ้งคอมเม็นต์ไว้หรือเมล์มาถาม technical issue กับผม ช่วยศึกษาเรื่องพื้นฐาน C# กับเรื่องพวกวิธี reference รวมถึงอ่าน Readme ก่อน แล้วค่อยถามมานะครับ …

ผมตั้งเป้าไว้ว่าจะมีคนเอาไปใช้ประโยชน์ได้ 2 คนขึ้นไป ใครเอาไปใช้ทำอะไรบอกด้วยๆ :’)

ICU4NET – ICU Binding for .NET

WordBreak

34 thoughts on “ตัดคำไทยด้วย C#

  1. Zolo

    เยี่ยมไปเลยนาย เหมือนว่าเราจะต้องไปนั่งดู​ ICU เหมือนกันอะ ฮา ๆ

    Reply
  2. Zolo

    เยี่ยมไปเลย กำลังจะได้เล่น ICU เลยหละมั้ง ฮาๆ

    Reply
  3. Nuke

    ผมเพิ่งรู้เร็วๆนี้เองว่า Java มี BreakIterator ที่ตัดคำได้ที่เอามาจาก ICU ลองเล่นดูใน Java แล้วเหมือนว่ามันใช้ dict ด้วยรึป่าว (อันนี้ไม่รู้) เจอชื่อเฉพาะมันก็ตัดแปลกๆนะ

    Reply
  4. Bewilder

    เราอ่านแล้วไม่เข้าใจว่าทำไมต้องทำ Binding ทำไมถึงทำเป็น Wrapper ไม่ได้ อะ

    Reply
  5. m3rlinez

    @อู๋ IBM Thailand ได้ทำ ICU ด้วยเหรอวะ ดีๆๆ
    @โตโต้ ตั้งใจทำกว่าที่บ.อีกนะเนี่ย แอร๊ย ไม่ใช่
    @Nuke ใช่แล้วครับ ICU มันใช้ dict ตัดนั่นแหละ ขนาด dict ภาษาไทยประมาณ 500 KB
    @อรุช แน่น๊อนนน
    @รุต ถ้ามองในแง่ฟังก์ชัน จะ wrap หรือจะทำ binding มันก็ได้เหมือนกันนะ เพียงแต่ถ้าอยากตั้งชื่อโปรเจคว่า ICU4NET (ซึ่งเราอยาก) ตัว interface ข้างใน (classes, methods) มันควรจะเหมือนกับ ICU4C, ICU4J อ่ะ ไม่รู้ตอบตรงคำถามรึเปล่า ?

    Reply
  6. chakrit

    Cool 🙂

    Back where I used to work my friend created an internal database search engine implementation once using Lucene.NET and it can’t break thai words – -‘

    Now it can!

    amd thanks for link up on fu# 🙂

    Reply
  7. ob

    ลองเอาไปใช้แล้วใช้ไม่ได้ครับ มันบอก error ว่าไม่เจอ ICU4NET Version=1.0.3716.970 ผมเอา DLL ทั้งหมด ใน Package ที่ Download มาไปไว้ตำแหน่งที่เก็บ EXE แล้วครับ แต่ยังไม่ได้ หรือมีให้ Download ตัวที่เป็น DLL ของ IBM ได้ที่ไหนครับ หรือผมจะดึง Dictionary ที่คุณเอามาใช้ได้จากที่ไหน

    Reply
  8. m3rlinez

    คุณ ob ใช้งานโปรแกรมตัวอย่างที่ติดไปได้รึเปล่าครับ ?

    Reply
  9. ob

    ผมใช้ VS.NET C# 2005 พอลง 2008 ใช้ได้ครับ น่าจะเกี่ยวกับ Version .Net Framework รึเปล่าไม่แน่ใจ แล้วสามารถนำไปใช้กับ VB6 ได้รึเปล่าครับ

    Reply
  10. 333CS

    ขอบคุณมากค่ะ สำหรับบทความนี้ พอดีกำลังทำโปรเจคเรื่องนี้อยู่พอดี เกี่ยวกับการตรวจสอบคำสะกดบนเวบบอร์ด ได้ลองใช้โปรแกรมที่ให้ไปแล้ว
    ว่าจะใข้ vs 2008 เขียน เป็นไปได้ไหมคะ และควรจะทำอย่างไรบ้าง ช่วยชี้แนะหน่อยค่ะ

    Reply
  11. m3rlinez

    @333CS ใช้ได้แน่นอนครับ แต่ผมแนะนำว่าให้ทำเป็น desktop application (Windows Form หรือ Console app) จะเจอปัญหาน้อยกว่าเอาไปทำเป็น web application

    ใช้ class WebClient ให้ไปดึงข้อความมาจากเว็บบอร์ดเรื่อยๆก็ได้ครับ

    Reply
  12. ob

    ผมลองใช้แล้วมีบางประโยคที่ตัดผิดอยู่ครับ เดี๋ยวเอาให้ดู แล้วอยากทราบว่าเราสามารถเพิ่มคำศัพท์ที่ใช้เปรียบเทียบในการตัดคำได้หรือไม่ครับ

    Reply
  13. Noomkingkong

    ผมอยากเอาไปพัฒนาต่ออ่ะครับ

    อยากขอ ซอสโค้ด ต้องติดต่อคุรยยังไง ครับ บอกทีครัย

    นศ วิทยาการคอม ฯ อิอิ

    Reply
  14. ob

    ช่วยด้วยครับ พอดีผมเอา ICU4NET ไปใช้แล้ว Compile เรียบร้อยแล้ว ลอง Install เครื่องตัวเองได้ ไป Install เครื่องอื่นที่ลงโปรแกรม Visual Studio.Net 2008 เมื่อ Install แล้ว ก็ลงได้ แต่พอผมเอาไปลงเครื่องที่ ไม่มี Visual Studio.Net 2008 กลับ เกิด Error ดังนี้ ครับ รบกวนหน่อยนะครับ

    [เอา error message ออกไปแล้วนะครับ มันแอบยาว /m3rlinez]

    ผม Add DLL เข้าไปแล้ว แต่ยัง Error อยู่
    ขอบคุณครับ

    Reply
  15. ob

    ช่วยบอกด้วยครับว่าผมจะต้องลง Driver อะไรของ VS.NET2008 เพิ่มบ้าง โดยไม่ต้องลงโปรแกรม VS.NET 2008 ช่วยด้วยครับ

    Reply
  16. ob

    4/5/2553 15:22:31 #
    คุณ ob ใช้งานโปรแกรมตัวอย่างที่ติดไปได้รึเปล่าครับ ?
    ———————————————————–

    Error เหมือนกันครับ

    Reply
  17. m3rlinez

    อ่านคร่าวๆแล้วดูเหมือน copy DLLs ไปไม่ครบ ตอนเอาไป deploy ได้ copy DLL ตัวไหนไปบ้างครับ ?

    ปกติโปรแกรมตัวอย่างที่ดาวน์โหลดไป ถ้าเครื่องมี .NET Framework 3.5 ลงไว้แล้ว ควรจะรันได้เลยหลังแตก zip ถ้าไม่ได้ลอง copy log แบบเดียวกันจากโปรแกรมตัวอย่างมาให้ดูหน่อยละกันครับ

    Reply
  18. ob

    ลง framwork 3.5 แล้ว ผม Add refference และ copy dll ไป deploy ตามที่ได้ตาม Readme นะครับ

    ICU4NETExtension.dll
    ICU4NET.dll
    icudt42.dll
    icuin42.dll
    icuio42.dll
    icule42.dll
    iculx42.dll
    icutu41.dll
    icuuc42.dll

    ปัญหาอย่างที่บอกครับ ว่าต้องมี VS.NET 2008 ถึงจะได้ครับ ถ้าไม่มีก็ Error แบบนั้นครับ

    Reply
  19. ob

    Log ที่เกิด Error เมื่อ Run Sample File โดยที่ไม่ได้ลง VS.NET 2008 ครับ

    [เอา error message ออกไปแล้วนะครับ มันแอบยาว /m3rlinez]

    Reply
  20. m3rlinez

    @ob ขอบคุณสำหรับรายละเอียดเพิ่มเติมนะครับ ผมลองดูแล้วปรากฎว่าเป็นเหมือนกัน – -‘ สั้นๆคือมันเกิดจาก ICU4NET.dll ที่แจกจ่ายไปมัน build มาแบบ Debug แล้วมัน depends on debug version ของ VC++ Run-time อันนี้ตอนแรกก็หาไม่เจอเหมือนกันเพราะทุกเครื่องที่ใช้ดันลง VS2008 ไว้หมด

    เร็วๆนี้จะเอาเวอร์ชันที่ build แบบ Release มาให้ใหม่ คิดว่าหลังจากเปลี่ยนเป็นเวอร์ชันนี้แล้ว ตอนเอาไปลงที่เครื่องที่ไม่มี VS2008 ก็แค่ลง VC++ 2008 Run-time ไปด้วย (~2 MB) ก็จะรันได้ตามปกติแล้ว

    ผมทำ ticket ไว้ที่ http://code.google.com/p/icu4net/issues/detail?id=2 ไปติดตาม update ได้

    Reply
  21. ob

    ใช้ได้แล้วครับ เยี่ยมครับ ขอบคุณมากครับ
    Cheer

    Reply
  22. rangsarn

    ฉัน|มาร|อก|ราบ|พระสงฆ์
    ฉัน|มา|รอก|ราบ|พระสงฆ์
    ฉัน|มา|รอ|กราบ|พระสงฆ์

    เรือ|โคลง|เพราะ|โคลง|เรือ
    เรือ|โคลง|เพราะ|โค|ลง|เรือ
    เรือ|โค|ลง|เพราะ|โคลง|เรือ
    เรือ|โค|ลง|เพราะ|โค|ลง|เรือ

    คน|ตาก|ลม|นอน|ตาก|ลม
    คน|ตาก|ลม|นอน|ตา|กลม
    คน|ตา|กลม|นอน|ตาก|ลม
    คน|ตา|กลม|นอน|ตา|กลม
    คน|ตา|กล|มน|อน|ตาก|ลม
    คน|ตา|กล|มน|อน|ตา|กลม

    จะเกิดอะไรขึ้นเมื่อโปรแกรมแบ่งคำผิด ?

    Reply
  23. สุระพล

    ขอรบกวนหน่อยครับ ผมใช้ ICU4NET Run บน VS2008 Run ได้แต่ Run บน IIS มัน Error ครับ ผมใช้ IIS 6 ครับ

    Reply
  24. สุระพล

    รบกวนหน่อยครับ คือผมใช้ ICU4NET Run บน VS2008 มัน Run ได้ไม่มีปัญหา แต่พอ Run ผ่าน IIS 6 มัน Run ไม่ได้มัน ฟ้องแบบนี้ครับ มันเกิดจากอะไรครับ แล้ววิธีแก้ไขทำอย่างไรครับ

    Server Error in ‘/’ Application.
    ——————————————————————————–

    Could not load file or assembly ‘ICU4NET’ or one of its dependencies. An attempt was made to load a program with an incorrect format.
    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.BadImageFormatException: Could not load file or assembly ‘ICU4NET’ or one of its dependencies. An attempt was made to load a program with an incorrect format.

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Assembly Load Trace: The following information can be helpful to determine why the assembly ‘ICU4NET’ could not be loaded.

    WRN: Assembly binding logging is turned OFF.
    To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
    Note: There is some performance penalty associated with assembly bind failure logging.
    To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

    Stack Trace:

    [BadImageFormatException: Could not load file or assembly ‘ICU4NET’ or one of its dependencies. An attempt was made to load a program with an incorrect format.]
    System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) +0
    System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks) +567
    System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +192
    System.Reflection.Assembly.Load(String assemblyString) +35
    System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +123

    [ConfigurationErrorsException: Could not load file or assembly ‘ICU4NET’ or one of its dependencies. An attempt was made to load a program with an incorrect format.]
    System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +11479520
    System.Web.Configuration.CompilationSection.LoadAllAssembliesFromAppDomainBinDirectory() +484
    System.Web.Configuration.AssemblyInfo.get_AssemblyInternal() +79
    System.Web.Compilation.BuildManager.GetReferencedAssemblies(CompilationSection compConfig) +334
    System.Web.Compilation.BuildManager.CallPreStartInitMethods() +280
    System.Web.Hosting.HostingEnvironment.Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters, PolicyLevel policyLevel, Exception appDomainCreationException) +1087

    [HttpException (0x80004005): Could not load file or assembly ‘ICU4NET’ or one of its dependencies. An attempt was made to load a program with an incorrect format.]
    System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +11612256
    System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +141
    System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr) +11443374

    ——————————————————————————–
    Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.1

    Reply
  25. m3rlinez

    @สุระพล อันนี้ใช้ Windows Server เวอร์ชันอะไรอ่ะครับ แล้วเป็น 64-bit หรือ 32-bit? ตัว ICU4NET ตอนนี้มีเฉพาะ 32-bit อ่ะครับ อาจจะต้อง set IIS ตามเว็บนี้ (แต่ผมยังไม่เคยลองนะ) -> http://www.alexjamesbrown.com/development/could-not-load-file-or-assembly-chilkatdotnet2-or-one-of-its-dependencies-an-attempt-was-made-to-load-a-program-with-an-incorrect-format/

    Reply
  26. num

    โห.. geek มากเลยครับพี่ แต่แบบว่าอยากได้เวอร์ชั่น 64bit แล้วก็รันบน .NET4.5.1 ด้วยอ่ะครับ พี่แก๊นไม่มีเวลาอัพเดทโปรเจคซะหน่อยเหรอครับ

    Reply
  27. kittisak

    ผมใช้ IIS7 and Windows Server 2008 R2 64-bit จากนั้น set Application Pool –> Enable 32-bit Applications = true แล้ว แต่ก็ยัง Error อยู่ครับ

    Could not load file or assembly ‘ICU4NET.DLL’ or one of its dependencies.

    ถ้ามีทางอื่นยังไง รบกวนด้วยคับ
    ขอบคุณครับ

    Reply
  28. เอก

    สำหรับคนที่ใช้ กับ IIS แล้วมีปัญหานะครับ
    ให้ Add Reference ตามปรกติ
    แล้วจากนั้น Copy .DLL ทั้งหมด ไปไว้ที่ C:\windows\system32 หรือ (และ) C:\windows\sysWoW64

    ขอบคุณผู้พัฒนาด้วยครับ

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *