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

5. March 2010

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

OOAD, .NET, Java, Native , , , , , ,

ปัญหาภาษาไทยกับ iTunes และ Windows

27. September 2009

เป็นปัญหาเดียวกับเรื่องภาษาไทยเป็นตัวยึกยือ มันเกิดจากข้อมูลใน ID3 Tag (เป็นส่วนของไฟล์ที่ไว้บอกข้อมูลเกี่ยวกับเพลง) มันเก็บภาษาไทยโดยใช้ Encoding แบบนึง (มักจะเป็น Windows-874 / TIS620 ซัมติง …)

MP3TagProblem

อ่านหาวิธีแก้ตามเว็บบอร์ดชุมชนคนไทยก็พบวิธีแก้ง่ายๆอย่างนึงคือต้องไปเปลี่ยน Regional Settings ให้เป็นไทยหลายๆอัน แล้วจะดิสเพลย์ได้ถูกต้อง o__O! แล้วค่อยใช้ iTunes convert ID3 ให้กลายเป็นเวอร์ชันใหม่ๆ (ที่จะเป็น unicode รองรับหลายภาษา)

การทำแบบนี้มันก็มีปัญหาอยู่บ้าง เพราะพอเปลี่ยน Regional แล้วก็จะทำให้ไม่รู้ว่าอัลบั้มไหน convert ID3 ไปเรียบร้อยแล้วรึยัง (เพราะมันแสดงได้ถูกต้องทั้งหมด) อย่างตอนนี้ผมลง OS ใหม่เป็น Windows 7 อัลบั้มเก่าๆเน่าไปหลายอันอยู่ … ก็เลยเขียนโปรแกรม convert ดีกว่า! (นั่น …. หาเรื่องเขียนบลอกอะดิ๊ๆๆๆๆ)

ประเด็นสำคัญ

  • Tool สำหรับแปลง ID3 ที่เป็น encoding หนึ่งไปอีกอันนึงก็มีอยู่แล้วบ้าง แต่ส่วนใหญ่มักจะเป็นบน Linux กับ Mac หาของ Windows ไม่เจอ (หรือผมอาจจะตาถั่วเอง)
  • ใช้ Lib สำหรับอ่าน ID3 คือ TabLig Sharp
  • ใช้ Reflection ในการวนรอบ property ทุกอันที่เป็น string และ string[] เป็นครั้งแรกที่เริ่มเห็นคุณค่าของ relfection!
  • พอมันมี Library ภายนอกก็เลยมี DLL หลายอัน รวมให้เป็น EXE อันเดียวโดยใช้ ILMerge จะทำให้ ship ง่ายกว่า (แต่ยังไม่คิดจะ ship รอให้มี demand ก่อน ฮ่าๆ)
  • อันตรายมาก: ยังไม่มีวิธีเช็คเลยว่าไฟล์ไหนถูกแปลงไปแล้วบ้าง เพราะถ้าแปลงซ้ำกันสองครั้งมันจะทำให้เนื้อหาไฟล์พังไปเลย >_< โดนไปหนึ่งดอกเรียบร้อย
  • ตอนนี้ใช้ iTunes โดยให้มัน Consolidate Library แล้ว รู้สึกทำให้ย้ายของง่ายกว่า
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace ID3ConvertToUnicode
{
class Program
{
public static string FixThaiCodePage(string str)
{
if (str == null) return null;
byte[] raw = Encoding.Default.GetBytes(str);
string res = Encoding.GetEncoding("windows-874").GetString(raw);
return res;
}

public static string[] FixThaiCodePage(string[] strings)
{
string[] res = new string[strings.Length];
for (int i = 0; i < strings.Length; i++)
{
res[i] = FixThaiCodePage(strings[i]);
}
return res;
}

static void Main(string[] args)
{
string[] files = Directory.GetFiles(".");
foreach (string file in files)
{
if (!file.EndsWith(".mp3"))
continue;

Console.WriteLine("Converting: " + file);
TagLib.File music = TagLib.File.Create(file);

foreach (PropertyInfo pinfo in music.Tag.GetType().GetProperties())
{
if (!pinfo.CanRead || !pinfo.CanWrite)
continue;

if (pinfo.PropertyType == typeof(string))
{
string s = pinfo.GetValue(music.Tag, null) as string;
s = FixThaiCodePage(s);
pinfo.SetValue(music.Tag, s, null);
}

if (pinfo.PropertyType == typeof(string[]))
{
string[] s = pinfo.GetValue(music.Tag, null) as string[];
s = FixThaiCodePage(s);
pinfo.SetValue(music.Tag, s, null);
}
}
music.Save();
Console.WriteLine();
}

}
}
}

ilmerge /target:winexe  /out:ID3Retag.exe 
ID3ConvertToUnicode.exe policy.2.0.taglib-sharp.dll taglib-sharp.dll

iTunesThaiFixed

ยอดเยี่ยม!

.NET , , ,

ควบคุมบราวเซอร์ด้วย WatiN

14. July 2009

วันนี้พยายามจะเขียนโปรแกรมให้ export ข้อมูลออกจาก web app ที่ทำงานตัวนึงอัตโนมัติ เขียนไปเขียนมา account โดนล็อค - -‘ แกว่งเท้าหาเสี้ยนชัดๆเลย

[Test]
public void SearchForWatiNOnGoogle()
{
using (IE ie = new IE("http://www.google.com"))
{
ie.TextField(Find.ByName("q")).TypeText("WatiN");
ie.Button(Find.ByName("btnG")).Click();

Assert.IsTrue(ie.ContainsText("WatiN"));
}
}

ใช้ง่ายเกินไปแล้ว!! สนใจเชิญที่ WatiN (สำหรับชาว .NET เท่านั้น – Web Application Testing in .NET - รุสึกจะมี WatiR ที่เป็นของ Ruby ด้วย)

มันเป็นเครื่องมือสำหรับให้พวก QA/Tester ใช้อ่ะครับ แต่คิดว่าถ้าจะเอาไปดัดแปลงทำ บอตโหวตคะแนน บอตลงทะเบียน หรืออะไรทำนองนี้ก็เอาไปทำได้ไม่ยากเลย

ข้อเสียคือใช้ได้กับ IE 6 7 8 เท่านั้น (Firefox อยู่ใน Roadmap) ถ้าอยากใช้กับ Firefox หรือ browser อื่นๆ ลองดู Selenium

ข้อเสียอีกอย่างคิดว่าคงเหมือนกับเครื่องมือทำนองนี้อื่นๆทั่วๆไป คือถ้าเมนูเป็น Flash เป็น Java Applet หรือเป็น ActiveX Object อะไรซักอย่าง มันจะทำให้เขียนโค้ดเข้าไป Automate ยาก (หรือทำไม่ได้) ขึ้นมาทันที

ปล. ต่อไปนี้จะพยายามเขียนบลอกให้สั้นๆ ได้ใจความมากขึ้น

.NET, Tools , ,

เลือกเครื่องมือให้ถูกงาน: C++ ปะทะ C#

2. May 2009

งานที่ผมต้องทำต่อไปใน day job มันค่อนจะเกี่ยวกับ C++/Unix เยอะหน่อย อยากมีประสบการณ์กับพวกมันมากกว่านี้ก็เลยตั้งใจลองเขียนโปรแกรมเล่นๆขึ้นมาดู

โปรแกรมที่ต้องการจะเขียนจะมีหน้าที่คือดึง Update ของคนๆหนึ่งมาจาก Twitter แสดงบนหน้าจอ (ใน Console) แล้วแยกเฉพาะส่วนที่เป็นลิงค์ใน Tweet อันนั้นมาแสดงคู่กันด้วย

นั่งทำตั้งแต่บ่ายแก่ๆ มาเรื่อยๆ เพิ่งมาเสร็จเมื่อกี้ o__o!

สรุปปัญหาที่เจอและสิ่งที่เรียนรู้ดังนี้

เลือกใช้ VIM เป็น Editor อันนี้ลองใช้มาซักระยะแล้ว รู้สึกชอบมากกว่า Emacs นะครับ ;P รู้สึกว่าตัว VIM มัน Simple ดี แล้วแทบจะมีอยู่ทุกที่ หัดทีนึงคุ้มเลยทีเดียว มันก็มีพวก script มาให้ลงเพิ่มได้เยอะแยะนะครับ เช่นตัวช่วย browse โค้ด หรือ theme หรือตัวช่วยทำ auto-complete ที่ใน VIM จะมี feature ชื่อ omni-completion ให้ด้วย (เป็น auto-com แบบ context-sensitive พูดง่ายๆ คือฉลาด) แต่ก็ยังไม่รู้สึก “เหมือนอยู่บ้าน” เท่า Visual Studio

Build โดยใช้ Makefile อันนี้เล่นเอาปวดหัว ตอนแรกคิดว่าพวก automake autoconf จะช่วยทำ Makefile ให้ได้ง่ายๆแบบไม่เปลืองแรง แต่จริงๆพวกนั้นก็ต้องเขียนไฟล์ขึ้นมาเพิ่มเหมือนกัน สุดท้ายเลยลงมือหัดเขียน Makefile เอง (และก็ได้แบบพิการๆแต่ทำงานได้ออกมา)

เรียนรู้ option ของ g++ ปกติแล้วใช้ Visual Studio ก็เอาไฟล์ยัดเข้าโปรเจค แล้วก็กด F5 ก็เอาโปรแกรมมารันเล่นได้แล้ว แต่พอต้องใช้คำสั่ง cmd ในการ compile – link เล่นเอาเหนื่อยเหมือนกัน สรุปว่าใช้ดังนี้

g++ –L path/to/lib1 –L path/to/lib2 –ldynamclib –I path/to/include –o execname source1.o source2.o staticlib.a

หรือถ้าจะสร้าง object ไฟล์อย่างเดียว

g++ –c main.cpp main.o

การใช้ Libraries ภายนอก C++ มันแทบไม่มีอะไรมาให้ใช้เลยนอกจาก STL (Standard Template Library) อยากได้อะไรต้องหาเองหมด ในทีนี้ต้องหา

  • Lib สำหรับการอ่าน XML เลือกใช้ TinyXML
  • Lib สำหรับการทำ HTTP Request เลือกใช้ libcurl
  • Lib สำหรับใช้ Regular Expression อันนี้จริงๆมัน overkill ไปหน่อย แต่อยากใช้ดู เอาส่วนหนึ่งของ Boost มาใช้

การเอาแต่ละอันมารวมกับโปรเจคก็เป็นเรื่องน่าปวดหัวมาก เนื่องจากด้อยประสบการณ์เรื่องพวกนี้เหลือเกิน กว่าจะรวมได้ก็แทบหัวแตก ปัญหาส่วนใหญ่เกิดตอน link

สรุปได้โปรแกรมออกมาสมใจ เสียเวลาไปมหาศาล ภูมิใจมาก T__T แทบจะเป็นโปรแกรม C++ โปรแกรมแรกบน Linux ที่ไม่ใช่ Hello World! (อ่อ ผมดึง update มาจากคุณ @tamir เพราะของตัวเองมีแต่อัพเดทภาษาไทย)

termtweet

อันนี้เป็น Makefile .. คนเขียนเป็นมาเห็นคงดูออกว่านุ้บมาก 55+

CXXFLAGS= -I ~/boost/include/boost-1_38 -I lib/curl/include -lrt

SRCS=main.cpp

OBJS=main.o

TINYOBJS=lib/tinyxml/tinystr.o lib/tinyxml/tinyxml.o lib/tinyxml/tinyxmlerror.o lib/tinyxml/tinyxmlparser.o

LIBS=~/boost/lib/libboost_regex-gcc43-mt-1_38.a /usr/local/lib/libcurl.a

all: termtweet

termtweet: $(OBJS) $(TINYOBJS)
g++ $(CXXFLAGS) -o $@ $(OBJS) $(TINYOBJS) $(LIBS)

tinyxml: $(TINYOBJS)

depend: $(SRCS)
makedepend $(SRCS)

clean:
-rm termtweet *.o *.bak *.swp

อันนี้ Source Code ในภาษา C++

#include <cstdio>
#include <iostream>
#include "lib/tinyxml/tinyxml.h"
#include <curl/curl.h>
#include <string>

#include <boost/regex.hpp>

using namespace std;

static int writer(char *data, size_t size, size_t nmemb,
string *buffer)
{
int result = 0;
if(buffer == NULL) return result;

buffer->append(data, size * nmemb);
result = size * nmemb;
return result;
}

void extract_link(const char *s){
string line(s);
boost::regex pat(".* (http://.*) .*");
boost::smatch matches;
if(boost::regex_match(line, matches, pat))
cout << "link: " << matches[1] << endl;
}

int main(void){
cout << "TermTweet 0.1" << endl;
cout << "Fetching items ..." << endl;

CURL *curl;
CURLcode res;
string buffer;

curl = curl_easy_init();
if(curl){
curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/user_timeline/6380022.rss");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writer);

res = curl_easy_perform(curl);

curl_easy_cleanup(curl);
}

if(res != CURLE_OK)
{
cout << "Can't get the RSS feed" << endl;
exit(-1);
}
cout << "Page load completed. Parsing XML .." << endl;

TiXmlElement *root;
TiXmlDocument doc;
doc.Parse(buffer.c_str());
root = doc.FirstChildElement("rss");
if(root != NULL){
TiXmlElement *channel;
channel = root->FirstChildElement("channel");
if(channel != NULL){
TiXmlElement *item;
item = channel->FirstChildElement("item");
while(item != NULL){
TiXmlElement *title;
title = item->FirstChildElement("title");
if(title != NULL){
cout << title->GetText() << endl;
extract_link(title->GetText());
}

item = item->NextSiblingElement("item");
cout << "----------------------------------" << endl;
}
}else{
cout << "Cannot find 'channel'" << endl;
exit(-1);
}
}else{
cout << "Not an RSS" << endl;
exit(-1);
}
cout << "Parse OK" << endl;
return 0;
}



ได้อะไรมาเยอะเหมือนกัน จากการหัดทำโปรเจคแสนถึกอันนี้ (แต่ก็เสียไปเยอะเช่นกัน T_T) ต่อไป … ไฮไลท์ … โปรแกรมคล้ายๆกันใน C# >.< เพิ่งเขียนเมื่อกี้ ไม่ต้องลง Lib เพิ่ม ไม่ต้องทำเบื้อกใดๆทั้งสิ้น ! (เชื่อว่า Java ก็คงใกล้ๆกัน)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;
using System.IO;
using System.Text.RegularExpressions;

namespace SharpTermTweet
{
class Program
{
static void Main(string[] args)
{
string address = "http://twitter.com/statuses/user_timeline/6380022.rss";
WebRequest req = WebRequest.Create(address);
WebResponse res = req.GetResponse();

StreamReader sr = new StreamReader(res.GetResponseStream());
string content = sr.ReadToEnd();

XmlDocument doc = new XmlDocument();
doc.LoadXml(content);
XmlNodeList list = doc.GetElementsByTagName("item");
foreach (XmlNode node in list)
{
Console.WriteLine(node["title"].InnerText);
ExtractLink(node["title"].InnerText);
Console.WriteLine("-----------------------------");
}

}

static void ExtractLink(string line)
{
Regex regex = new Regex(".* (http://[^\\s]*) .*");
Match match = regex.Match(line);
if(match.Groups.Count >= 2)
Console.WriteLine("link: {0}", match.Groups[1]);
}
}
}

สรุปว่า เลือกเครื่องมือให้ถูกงานครับ :)

ปล. เพิ่งจะรู้ว่า Put the Right man to the Right job เป็นสำนวนที่โคดไทยเลย ไม่เชื่อลองไปหาใน Google ดูสิ ส่วนประโยคต้นฉบับจริงๆน่าจะเป็น Right man for the job มากกว่าจากการวิจัยของผม ..

.NET, Native ,

วิธีการใช้ Subversion (SVN) – Subversion กับ Visual Studio

5. April 2009

โพสต์นี้ไม่ค่อยมีอะไรหรอกครับ แค่จะบอกว่า ถ้าจะใช้กับ Visual Studio มันมีบางโฟลเดอร์ที่ไม่ควรใส่เข้าไปในตัวซอฟแวร์ Version Control ด้วย อันได้แก่

    1. ไฟล์ .csproject.user และ ไฟล์ .suo ไฟล์พวกนี้มันเก็บ configuration ของแต่ละ user ครับ ดังนั้นไม่ต้องใส่เข้าไปด้วย เพราะมันจะเปลี่ยนทุกครั้งที่บันทึก project
    2. โฟลเดอร์ bin อันนี้เป็นผลจากการ build ไม่ต้องใส่เข้าไปเช่นกัน เพราะเดี๋ยวก็ต้อง build ใหม่อยู่ดี
    3. โฟลเดอร์ obj ไม่ต้องใส่เช่นกันครับ

After First Commit

บางคนอาจจะชอบเอาไฟล์ Input บางอย่างไปโปะไว้ใน bin/Debug หรือ bin/Release แบบนี้จะทำให้ไฟล์พวกนี้ไม่เข้าไปใน VC ด้วย ถ้าเป็นกรณีนี้ลองใช้วิธีเอาไฟล์ดังกล่าวใส่เข้าไปใน project แล้วตั้ง property ชื่อ “Copy to Output Directory” ให้เป็น Copy if newer แทน จะเป็น practice ที่ดีกว่า

จบแล้ว ง่ายจัง :)

.NET, Subversion Tutorials ,

ตัวอย่างโค้ด C# เปรียบเทียบกับโค้ดภาษาเชิง functional อื่นๆ

22. March 2009

วันก่อนอ่านเจอกระทู้คล้ายๆ Code Golf ที่ Stackoverflow จาก delicious ของ Jemmy ครับ เป็นกระทู้ในเว็บ Narisa.com

ลองพยายามแปลงโค้ดจากภาษา Scala ของพี่ข้าวโพดหวานให้เป็น C# ดู เพราะพอรู้มาว่า C# 3.5 มันทำอะไรแปลกๆได้มากขึ้น ได้ผลเปรียบเทียบดังนี้ครับ

แก้ไข: เพิ่งไปอ่านมาว่า Scala มันเป็นภาษาแบบ multi-paradigm ไปบอกว่าของเค้าเป็น functional อย่างเดียวเด๋วโดนว่า ฮ่าๆ

อันนี้เป็น Scala

def findPrimeFactors(number : Int) : List[Int] = {
val first_prime_or_none = (2 until Math.sqrt(number)).inclusive.find { i => number % i == 0 }
if (first_prime_or_none != None) {
return first_prime_or_none.get :: findPrimeFactors(number / first_prime_or_none.get)
}
number :: Nil
}

(2 until 100).inclusive.foreach {
number => println( "Prime factors of " + number + " is " +
findPrimeFactors(number).removeDuplicates.mkString("[", ", ", "]") )
}

อันนี้เป็น C#

static void Main(string[] args)
{
for (int number = 2; number <= 33; number++)
Console.WriteLine("Prime factors of {0} is [{1}]",
number, String.Join(", ", FindPrimeFactors(number)
.ToList().ConvertAll<string>(x => x.ToString())
.ToArray())
);
}

static HashSet<int> FindPrimeFactors(int number)
{
var firstPrimeOrNone = Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(number)))
.ToList().Find(x => number % x == 0);

if (firstPrimeOrNone != 0)
{
var ret = FindPrimeFactors(number / firstPrimeOrNone);
ret.Add(firstPrimeOrNone);
return ret;
}

return new HashSet<int>(new int[] { number }); ;
}

C# เวอร์ชันลอกการบ้านชาคริตมาแก้นิดแก้หน่อย

อันนี้ลองใช้ IEnumerable กับ yield ทำ state machine ง่ายๆครับ อ่านง่ายกว่าอันข้างบนแยะ

static void Main(string[] args)
{
for (int number = 2; number <= 33; number++)
Console.WriteLine("Prime factors of {0} is [{1}]",
number, String.Join(", ", FindPrimeFactors(number)
.Distinct().Select(x => x.ToString()).ToArray())
);
}

static IEnumerable<int> FindPrimeFactors(int number)
{
while (true)
{
var firstPrimeOrNone = Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(number)))
.ToList().Find(x => number % x == 0);

if (firstPrimeOrNone != 0)
{
yield return firstPrimeOrNone; number /= firstPrimeOrNone;
}
else
{
yield return number; break;
}
}
}


.NET ,

ทดลองใช้ ANTS Profiler

4. March 2009

เหมือนที่ชื่อบอกครับว่ามันคงเป็นผลิตภัณฑ์ในตระกูล Profiler นั่นแหละ

Profiler เป็นโปรแกรมที่ไว้วัดประสิทธิภาพในการทำงานของโปรแกรม โดยอาจจะวัดในแง่เวลาการทำงาน หรือหน่วยความจำที่ใช้ โดยวัดกันได้ถึงระดับ Class, Method เลยทีเดียว ตัวที่เอามาลองวันนี้เป็นของ Redgate ทดลองใช้แบบ Evaluation ได้ 14 วันครับ

โปรเจคที่เอามาลองเป็นโปรเจค C# ในวิชา Game Programming ซึ่งเป็นเกม 3D ที่ใช้ Graphics Engine ตัวหนึ่งชื่อ OGRE3D (หรือสั้นๆว่า OGRE : Object-Oriented Graphics Rendering Engine) ตัวเดียวกับที่ใช้ทำ Senior Project

ตอนแรกก็สงสัยเหมือนกันว่าแล้วเราใช้ Library เสริมในลักษณะนี้ แล้วตอนทำ Profile โปรแกรมมันจะไม่อืดน่าดูเหรอ (ยิ่งเป็น Application ประเภทเกมด้วย) ปรากฎว่าตอนลองทำ Profile ดูแทบไม่เห็นความแตกต่างเลยครับ ตัวโปรแกรมก็ใช้ง่ายมาก มีการทำ Profile ได้หลายแบบ ทำไปสองแบบคือ Profile Performance และ Profile Memory เอามาให้ดูเฉพาะ Profile Performance

ส่วนอันนี้เป็นการดูรายละเอียดแต่ละ method ดูได้ว่าเข้ามาจากทางไหนบ้าง กระโดดไปทำงานต่อที่ไหนบ้าง แต่ละส่วนใช้เวลาเท่าไหร่

สรุปว่าก็เป็น Tool ที่มีประโยชน์ครับ ไว้หาคอขวดของโปรแกรม คือส่วนที่ทำงานช้าที่สุด (ในเคมีเรียกว่าขั้นตอนกำหนดปฎิกริยาสินะ) และ Profile Memory ก็เอามาไว้ช่วยหา Memory Leak ได้ (ยังใช้ไม่เป็น - -‘a)

.NET, Tools

วิธีการใช้ regular expression เพื่อ parse ไฟล์ข้อความใน C#

25. February 2009

เขียนมาหลายครั้งแล้วไม่เคยจำซักที T_T เอามาลงไว้เตือนความจำซักหน่อย

หน้าตาของบรรทัดใน Text File มันคั่นด้วย Space หลายตัวติดกัน ใช้ string.Split(‘ ‘) อาจจะไม่เวิร์คเท่าไหร่ เพราะจะได้ empty string ติดมาด้วย

:
1.0000000e+000  2.4924448e+000  2.6646541e+000  ..
1.0000000e+000 -2.6678309e+000 -1.0957386e+000  ..
1.0000000e+000 -2.6132891e+000 -1.6032095e+000  ..
:

ส่วนอันนี้เป็นโค้ดสำหรับ parse ตัดมาเฉพาะใจความสำคัญ

                 string line;
                int count = 0;
                Regex regex = new Regex("[0-9e+-.]+");
                while (count < maxLines && (line = tr.ReadLine()) != null)
                {
                    int type = -1;
                    List<double> values = new List<double>();
                    MatchCollection matches = regex.Matches(line);
                    foreach (Match m in matches)
                    {
                        double d = double.Parse(m.Groups[0].Value);
                        if (type == -1) type = (int)d;
                        else values.Add(d);
                    }
                    count++;

                    Entry e = new Entry()
                    {
                        Type = type,
                        Data = values.ToArray()
                    };

                    list.Add(e);
                }

.NET ,

Windows XP กับ Nokia อะไรบูตเร็วกว่า

19. February 2009

เมื่อเร็วๆนี้ไป Subscribe บลอกของคุณ Tamir Khason เพิ่ม เจอได้ยังไงจำไม่ได้แล้ว (น่าจะหลงไปตาม StackOverflow.com) แต่ป๋าเค้าเขียนเฮฮาดี เค้าเขียนเรื่องเกี่ยวกับโปรเจคที่เค้าทำอยู่ครับ คือเป็นโปรแกรมที่ช่วยดูแลเรื่องพลังงานในรถยนต์ไฟฟ้าซึ่งเป็นโครงการของอิสราเอล

ที่น่าสนใจคือมันรัน Windows XP บนรถ! และใช้ Windows Presentation Foundation!

โอ้วววววววว …. ลองจินตนาการว่านี่เป็นข่าวใน Blognone คงมีพวก anti-microsoft ออกมาประนามมากมาย และคงมีคอนเม็นต์เรื่องจอฟ้าเฟ้อ จินตนาการเรื่องรถชน blah blah เต็มไปหมด ขนาดผมเองโอนเอียงไปทาง Microsoft ยังรู้สึกแอบตกใจเลย เพราะเคยมีประสบการณ์กับ WPF แอบรู้สึกว่าโปรแกรมที่ใช้ WPF มันไม่ค่อย responsive เท่าไหร่บนเครื่องเก่าๆที่ช้า ตอนนั้นใช้ Pentium M 1.7 GHz กับ 1.2 GB RAM และ Intel Integrated Graphics ก็รู้สึกหน่วงๆครับ

แต่พี่แกโชว์วีดิโอที่อัดไว้ เป็น Windows XP เวอร์ชัน tweak แล้วบูตแข่งกับ Nokia E71 ครับ ผลเป็นยังไงคงเดากันได้ง่ายๆ

จาก - What boots faster – Netbook, powered Windows XP or Nokia E71 mobile phone?

General, .NET , ,

การทำ Serialization และ Deserialization ใน C#

25. January 2009

เป็นหัวข้อน่ารู้อีกอย่างนึงครับ สำหรับเรื่องของ Serialization และ Deserialization

ตามนิยาม (ในวิกิพีเดีย) เค้าบอกไว้ว่า

Serilization คือกระบวนการแปลงวัตถุให้กลายเป็นสายข้อมูลในรูปแบบบิต ทำให้สามารถเก็บรักษาวัตถุดังกล่าวไว้บนสื่อเก็บข้อมูล (เช่น HDD) หรือจะเอาไปส่งผ่าน Network ก็ได้

Deserilization คือกระบวนการย้อนกลับของ Serialization คือการแปลงจากสายข้อมูลในรูปแบบบิตให้กลับมาเป็นวัตถุของเรานั่นเอง

จากรูป วัตถุ "ปลาดิบ" จะถูกแปลงเป็นข้อมูลในรูปแบบ Binary เพื่อเก็บไว้บนดิสค์ครับ และหากต้องการนำมาใช้ใหม่ก็แค่โหลดข้อมูลขึ้นมาทำการ Deserialzation

จริงๆแล้วขั้นตอนการเก็บรักษา "สถานะ" ของวัตถุตามที่เห็นในรูป มันก็สามารถทำมือได้เช่นกัน โดยอาจจะเขียนข้อมูลต่างๆใส่ Text File แล้วเขียนลอจิกที่ทำหน้าที่โหลดข้อมูลดังกล่าวขึ้นมาเอง แต่ตามความเข้าใจของผม วิธีการนี้ก็เป็นวิธีการหนึ่งในการเก็บรักษาสภาพของวัตถุที่สะดวกดีครับเขียนเพิ่มนิดหน่อยก็ทำ Serialization ได้เลย

วิธีการทำใน C# มีขั้นตอนดังต่อไปนี้ ยกตัวอย่างจากคลาสที่เขียนในวันนี้

  1. เพิ่ม Attribute Serializable เข้าไปที่หัว Class ก่อน
  2. ให้ Class implements ISerializable ต้องเขียน method GetObjectData เพิ่ม
  3. ทำ Deserialization Constructor ที่มีรูปแบบตามที่กำหนด
  4. เพิ่มลอจิกส่วนที่เป็นการทำ Serialize และ Deserialize

ดูโค้ดดีกว่า

ส่วนของ ToBinaryFile และ FromBinaryFile เป็นส่วนของข้อ 4 นะครับ

เวลาจะทำ Serialize ก็เรียก ToBinaryFile("filename.osl") และเวลาจะ Deserialize ก็เรียก Word.FromBinaryFile("filename.osl")

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace WordParser
{
    [Serializable()]
    class Word : ISerializable
    {
        public string Value { get; set; }
        public string Description { get; set; }
        public int Popularity { get; set; }

        public Word()
        {

        }
       
        public Word(SerializationInfo info, StreamingContext context)
        {
            Value = (string)info.GetValue("Value", typeof(string));
            Popularity = (int)info.GetValue("Popularity", typeof(int));
            Description = (string)info.GetValue("Description", typeof(string));
        }

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Value", Value);
            info.AddValue("Popularity", Popularity);
            info.AddValue("Description", Description);
        }

        #endregion

        public static Word FromBinaryFile(string file)
        {
            using (Stream stream = File.Open(file, FileMode.Open))
            {
                BinaryFormatter bformatter = new BinaryFormatter();
                Word ret = (Word)bformatter.Deserialize(stream);
                return ret;
            }

           
        }

        public void ToBinaryFile(string file)
        {
            using (Stream stream = File.Open(file, FileMode.Create))
            {
                BinaryFormatter bformatter = new BinaryFormatter();
                bformatter.Serialize(stream, this);
            }

        }

    }
}

.NET, OOAD , ,