Archive

Archive for the ‘programming’ Category

ลองเล่น Go

November 22nd, 2009

ไม่ได้เขียนอะไรใหม่ๆ ที่นี่มาเกือบครึ่งปี เพราะงานยุ่ง แล้วก็ยังเสพติด twitter และ facebook งอมแงมอีกต่างหาก วันนี้มีเวลาว่าง (จริงๆ ก็ไม่ว่างหรอก แต่ขี้เกียจทำงานที่ควรจะทำ) เลยเอา Go Programming Language ที่พัฒนาโดย Google มาลองเล่นดู เนื่องจากผมใช้ Mac OS X ซึ่งเป็นแพลตฟอร์มที่สนับสนุนอยู่แล้ว ก็เลยติดตั้งไม่ยาก แค่มี XCode อยู่ แล้วลง mercurial เพิ่ม ก็สามารถโหลดซอร์สโค้ดคอมไพเลอร์ของ Go มาคอมไพล์เองได้เลย ใช้เวลาคอมไพล์ตัวคอมไพเลอร์สั้นมาก แป๊บเดียวเสร็จ ประทับใจพอสมควร

จากนั้นก็เลยลองเขียนโปรแกรมแบบมั่วๆ (เพราะยังงงกับ syntax อยู่) เพื่อแก้ปัญหา N-Queens แบบง่ายๆ (ใช้ backtracking search ด้วย recursive ดื้อๆ เอานี่แหละ) โดยใช้ Go:

package main
 
import "fmt"
 
var N int = 10
 
func nqueens(board []int, filled int) int {
	var attack bool = false;
	if filled == N-1 {
		for i:=0; i<N; i++ {
			fmt.Printf("%d\t", board[i])
		}
		fmt.Printf("\n");
		return 1
	}
	filled += 1;
	for i:=0; i<N; i++ {
		board[filled] = i;
		attack = false;
		for j:=0; j<filled; j++ {
			dy := board[j]-board[filled];
			dx := j - filled;
			if (board[j] == board[filled]) || 
			(dy/dx == 1 || dy/dx == -1) && (dy%dx == 0) {
				attack = true;
				break
			}
		}		
		if !attack {
			nqueens(board, filled)
		}	
	}
	return 0
}
 
func main() {
	var board []int = make([]int, N);
	nqueens(board, -1);
}

ด้วยความอยากรู้ ก็เลยลองเอาโปรแกรมนี้เอามาแปลงเป็นภาษา C แบบตรงๆ บรรทัดต่อบรรทัด

#include<stdio.h>
#include<stdlib.h>
 
const int N=10;
 
int nqueens(int *board, int filled) {
	int i,j,attack,dy,dx;
	if (filled == N-1) {
		for(i=0; i<N; i++)
			printf("%d\t", board[i]);
		printf("\n");
		return 1;
	}
	filled++;
	for(i=0; i<N; i++) {
		board[filled] = i;
		attack = 0;
		for(j=0; j<filled; j++) {
			dy = board[j]-board[filled];
			dx = j - filled;
			if ((board[j] == board[filled]) ||
			((dy/dx == 1 || dy/dx == -1) && (dy%dx == 0))){
				attack = 1;
				break;
			}
		}
		if (!attack)
			nqueens(board, filled);
	}
	return 0;
}
 
main() {
	int *board = (int *)malloc(sizeof(int)*N);
	nqueens(board, -1);
}

เสร็จแล้วก็ลองเขียน Python อีกโปรแกรมหนึ่ง

def nqueens(board, filled):
	global N
	if filled == N-1:
		for i in board:
			print "%d\t" % board[i],
		print
		return True
	filled+=1
	for i in xrange(0, N):
		board[filled] = i
		attack = False
		for j in xrange(0, filled):
			dy = board[j]-board[filled]
			dx = j - filled
			if board[j] == board[filled] or ((dy/dx == 1 or dy/dx == -1) and dy%dx==0):
				attack = True
				break
		if not attack:
			nqueens(board, filled)
	return 0
N=10
board = [0]*N
nqueens(board, -1)

ทดลองรัน (ด้วย N=10) แล้วจับเวลาง่ายๆ ด้วย time ก็จะได้ผลเป็น

Go C Python
real 0m0.165s 0m0.041s 0m1.048s
user 0m0.116s 0m0.029s 0m1.028s
sys 0m0.040s 0m0.002s 0m0.015s

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

programming , , ,

Mail Merge ด้วย Python

April 22nd, 2009

หลังจากถูกถล่มด้วยงานต่างๆ ทำให้ไม่ได้มาเขียนสองเดือนกว่าๆ พอดีช่วงนี้ช่วยจัดงาน PAKDD2009 อยู่ และมีงานที่เกี่ยวข้องที่ต้องใช้สคริปต์ เลยเอามาจดไว้ที่นี่หน่อยกันลืม

เรื่องของเรื่องก็คือผมต้องการส่งอีเมลไปยังคนหลายๆ คน โดยมีเนื้อหาที่แตกต่างกันเล็กน้อย ผมเลยเริ่มจากเปิด OpenOffice.org Spreadsheet ขึ้นมาเขียนข้อมูลต่างๆ ที่ต้องการแปะลงไปในจดหมาย แล้วเก็บเป็นไฟล์ CSV (Comma-Separated Value) ที่เป็นสำหรับเก็บข้อมูลแบบง่ายที่สุด แต่ละบรรทัดแทนข้อมูลแต่ละเรคอร์ด และใช้เครื่องหมาย “,” คั่นระหว่างข้อมูลแต่ละฟิลด์ จากนั้นก็เขียนสคริปต์ Python เพื่ออ่านข้อมูลจากไฟล์ CSV ที่ทำไว้ เอาไปแปะในเทมเพลตที่เตรียมไว้ โดย Python จะมีคลาสสำหรับอ่านเขียนไฟล์ CSV เป็นคลาสมาตรฐานอยู่แล้ว ก็เลยสามารถทำงานได้สะดวกมาก

วิธีใช้คลาส csv เพื่ออ่านข้อมูล จะทำอย่างนี้

import csv
data = csv.reader(open('myfile.csv'), delimiter=',', quotechar='"')
for row in data:
	print row[0]

csv.reader เป็นคลาสสำหรับจัดการอ่านข้อมูลแบบ CSV รับอาร์กิวเมนต์เป็นไฟล์ เครื่องหมายที่ใช้คั่นระหว่างฟิลด์ และเครื่องหมายที่ใช้เป็น quote ครอบข้อมูลแต่ละฟิลด์ เมื่อสร้างออพเจคต์ของ csv.reader ขึ้นมาแล้ว ก็สามารถวนรอบเพื่ออ่านไฟล์ขึ้นมาทีละเรคอร์ดได้เลย โดยข้อมูลที่อ่านขึ้นมาจะเก็บไว้ในรูปอาเรย์ อย่างกรณีนี้ row[0] ก็คือข้อมูลในฟิลด์แรกสุด

หลังจากนี้เราก็ต้องเตรียมเทมเพลต ซึ่งก็คือกำหนดตัวแปรสตริงเท่านั้นเอง เช่น

template = """
Dear {r[0]},
 
I would like to send you the following information:
 
{r[1]}
 
Best Regards,
Cholwich
"""

จะเห็นว่าสตริงนี้มีสัญลักษณ์พิเศษ ระบุข้อมูลที่เราต้องการจะเอาไปแทรก {r[0]} หมายถึงสมาชิกตัวแรกของตัวแปร r ที่ส่งมาเป็นพารามิเตอร์ อันนี้เป็นฟีเจอร์ String Formatting ของ Python อยู่แล้ว เมื่อกำหนดเทมเพลตเสร็จ สั่งให้ Python แทรกข้อมูลโดย

output = template.format(r=row)

จะได้ผลลัพธ์ที่ผสานข้อมูลเข้าไปแล้ว เป็นสตริงเก็บไว้ที่ตัวแปรชื่อ output จากนั้นผมก็สามารถเอาไปส่งเมล์ หรือเอาไปเขียนลงไฟล์เก็บไว้ได้ พอเอาทั้งหมดมารวมกันก็จะได้

import csv
 
template = """
Dear {r[0]},
 
I would like to send you the following information:
 
{r[1]}
 
Best Regards,
Cholwich.
"""
data = csv.reader(open('myfile.csv'), delimiter=',', quotechar='"')
 
for row in data:
	output = template.format(r=row)
	f = open(row[2], 'w')
	f.write(output)
	f.close()

สุดท้ายก็จะได้สคริปต์ง่ายๆ สำหรับทำ mail merge ที่ได้ output เป็นไฟล์แยกกัน โดยระบุชื่อไฟล์ไว้ที่ row[2]

อ้างอิง:

  1. PEP 3103: Advanced String Formatting
  2. Python csv — CSV File Reading and Writing

programming ,

Student Randomizer

December 2nd, 2008

ด้วยความที่อยากให้นักศึกษามีส่วนร่วมในการเรียนมากขึ้น เทอมนี้เลยพยายามเรียกนักศึกษาให้ช่วยตอบคำถาม หรือออกมาทำอะไรเล่นหน้าห้อง อย่างน้อยจะได้หลับกันน้อยลง หรือพยายามทำแบบฝึกหัดที่ให้ในห้องเรียนบ้าง แต่จะให้เลือกชื่อก็จำชื่อนักศึกษาทั้งหมดไม่ได้ แถมอาจจะเรียกบางคนซ้ำ หรือไม่ได้เรียกบางคนเลย เมื่อวานพอมีเวลาว่างอยู่บ้าง เลยนั่งเขียนโปรแกรม Java เล็กๆ ขึ้นมาหนึ่งตัว เอาไว้สำหรับสุ่มชื่อนักศึกษาในชั้นเรียน ตอนแรกก็กะจะทำเป็นโปรแกรมง่ายๆ คือแค่เปิดไฟล์อ่านชื่อนักศึกษามา แล้วก็แค่สุ่ม แต่ถ้าทำอย่างนั้นก็มีโอกาสที่บางคนจะโดนซ้ำ หรือสุ่มไม่ทั่วถึง

เพื่อเผื่อแผ่ให้ทุกคนถูกเรียกกันอย่างทั่วถึง เลยปรับการสุ่มนิดหน่อย โดยเก็บจำนวนครั้งที่นักศึกษาแต่ละคนถูกเรียกไว้ ถ้าโดนเรียกแล้วหนึ่งครั้ง ก็จะกำหนดโอกาสที่จะถูกสุ่มขึ้นมาอีกให้เป็น 1/2 ของเพื่อนที่ยังไม่เคยโดนเรียก ถ้าโดนสองครั้งก็จะลดไปอีกให้เหลือ 1/4 ของเพื่อนๆ คือให้เป็น 1/(2^n) เมื่อ n เป็นจำนวนครั้งที่ถูกเรียก เสร็จแล้วก็มานึกอีกว่าบางคนอาจจะโดนเรียกแล้วไม่อยู่ อย่ากระนั้นเลยจะต้องเก็บข้อมูลไว้หน่อยว่าคนนี้โดดเรียน เลยทำปุ่มให้กดได้ด้วยว่าโดดไปแล้วกี่ครั้ง (ไม่รู้เก็บไว้ทำไมเหมือนกัน)

สุดท้ายได้โปรแกรมออกมาหน้าตาแบบนี้

ถ้าใครสนใจลองโหลดไปเล่นได้ที่นี่ วิธีใช้ก็คือเตรียม text file ใส่ชื่อนักศึกษาบรรทัดละคน เขียน ID ก่อน แล้วค่อยตามด้วยชื่อ คั่นด้วย TAB อาจจะใส่จำนวนครั้งที่เรียกนักศึกษาไปแล้วด้วยก็ได้ คั่นด้วย TAB เหมือนกัน

ส่วนโค้ด ถ้าใครอยากได้ก็ขอมาล่ะกันครับ มันค่อนข้างเละเทะ ไม่อยากเผย อายครับ :D ถ้ามีเวลาบ้าอีก ก็อาจจะเพิ่มฟีเจอร์อื่นๆ ไปด้วย

programming, teaching ,

Screen, NoHUP, SIGHUP

August 7th, 2008

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

$ firefox &

แต่เราจะพบปัญหาเวลาที่ต้องการให้โปรแกรมที่สั่งให้ทำงานแบบแบ็คกราวน์นั้นทำงานต่อไปแม้ว่าเราจะออกจากระบบไปแล้ว เนื่องจากเวลาเราออกจากระบบ (logout) จะมีการส่งสัญญาณ SIGHUP (hangup) ไปยังโปรเซสทั้งหมดของเราที่กำลังทำงานอยู่ ซึ่งพฤติกรรมโดยปกติของโปรเซสเมื่อได้รับสัญญาณก็คือ หยุดการทำงานแบบไม่ปกติ (abnormal termination) จึงทำให้โปรแกรมที่เราหมายมั่นปั้นมือว่าจะให้ทำงานไปตลอดคืนนี้ หยุดทำงานไปโดยไม่รู้ตัว (กว่าจะรู้อีกทีก็อาจจะเช้าแล้ว ต้องเสียเวลารออีก)

วิธีการแก้ปัญหานี้มีหลายวิธี วิธีที่ยุ่งยากหน่อยก็คือแก้โปรแกรมโดยเรียกใช้ system call เพื่อดัก SIGHUP ไว้ โปรแกรมของเราจะได้ไม่ทำตามพฤติกรรมปกติซึ่งก็ดูจะยุ่งยากไปหน่อย และไม่สามารถใช้กับโปรแกรมที่ไม่เปิดเผยโค้ดได้ อีกวิธีหนึ่งก็คือการใช้โปรแกรม nohup ซึ่งจะทำตัวเป็นเหมือนเชลล์ห่อโปรแกรมของเราไว้อีกชั้นหนึ่ง โดยจะดักสัญญาณ SIGHUP ไว้ ทำให้โปรแกรมทำงานต่อไปหลังจากออกจากระบบ เช่น

$ nohup myprogram &

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

screen เป็นโปรแกรมบนยูนิกซ์อีกโปรแกรมหนึ่งที่ช่วยอำนวยความสะดวกในการรันโปรแกรมแบบนี้

$ screen myprogram

screen จะทำงานคล้าย nohup เพียงแต่ว่าเมื่อเริ่มทำงาน screen จะทำให้ดูเหมือนว่าโปรแกรมของเราทำงานเป็นแบบฟอร์กราวน์ตามปกติทั่วไป เราสามารถสั่งให้ทำงานเบื้องหลังได้ โดยกด CTRL-A CTRL-D ต่อกัน โปรแกรมก็จะไม่เชื่อมต่อกับเทอร์มินัล แต่จะทำงานในแบบแบ็คกราวน์ แต่เราสามารถกำหนดให้ screen เรียกโปรแกรมของเราที่ทำงานอยู่ให้กลับมาอยู่เบื้องหน้าอีกครั้งได้ แม้ว่าเราจะออกจากระบบไป และกลับมาใหม่แล้วก็ตาม

$ screen -r

ในกรณีที่เรียกใช้ screen พร้อมกันหลายๆ โปรเซส ก็อาจจะต้องระบุรหัสโปรเซสที่ต้องการเรียกกลับมาบนเทอร์มินัล โดยอาจจะใช้ออปชัน -ls เพื่อแสดงโปรเซสของ screen ทั้งหมดก็ได้ นอกจากนี้ screen ยังมีออปชันให้เกิดสิ่งที่เป็นผลลัพธ์ทั้งหมดลงไฟล์ไว้ให้ด้วยก็ได้ ทำให้สะดวกในการเรียกดูผลลัพธ์ภายหลัง ผมเองก็ต้องพึ่งโปรแกรม screen เยอะเลยทีเดียวกว่าจะเรียนจบมาได้

รายละเอียดเพิ่มเติม: http://www.gnu.org/software/screen/

programming, unix , , , , ,

Ruby กับ Prolog

April 10th, 2008

ช่วงนี้ได้อ่านหนังสือ “The Ruby Programming Language” ทำให้มีโอกาสได้รู้เทคนิค หรือฟีเจอร์ของภาษา Ruby หลายอย่าง แม้ว่าจะใช้ Ruby มาตั้งแต่สมัยอยู่ญี่ปุ่น แต่ยังไม่เคยมีโอกาสอ่านหนังสือจริงๆ จังๆ ส่วนใหญ่ก็ใช้ไปแบบงูๆ ปลาๆ เหตุผลเดียวที่ใช้ก็คือรู้สึกว่า syntax ค่อนข้างคงที่ไม่กำกวม แถมยังเป็น OOP ทำให้ง่ายต่อการทำงานบางอย่าง วันนี้เพิ่งอ่านเจอว่าเราสามารถเขียน method ที่รับอาร์กิวเมนต์จำนวนไม่คงที่ได้ โดยใช้วิธีการกำหนดพารามิเตอร์เพิ่มหนึ่งตัว นอกเหนือจากพารามิเตอร์ปกติ และให้ใส่เครื่องหมาย * ไว้ข้างหน้าพารามิเตอร์ตัวนั้น เช่น

def min(first, *rest) 
  min=first
  rest.each {|x| min=x if x<min}
  return min
end

method นี้จะหาค่าต่ำที่สุดของอาร์กิวเมนต์ทั้งหมดที่รับมา เช่น

>> min(4,2,1,5)
=> 1

โดย first เป็นพารามิเตอร์หลัก หมายความว่าจะต้องรับข้อมูลมาอย่างน้อยหนึ่งตัว ส่วน rest เป็นพารามิเตอร์สำหรับค่าอื่นๆ ที่เหลือ ในที่นี้เมื่ออ้างถึง rest จะเป็นอะเรย์เก็บค่าต่างๆ ที่ได้รับมา ดังนั้นจึงสามารถใช้ rest.each ในการอ้างถึงสมาชิกแต่ละตัว แล้วเปรียบเทียบหาค่าต่ำที่สุดได้

ลองดูฟีเจอร์นี้ของ Ruby แล้ว ก็รู้สึกว่าไม่ต่างจากรูปแบบลิสต์ [head|tail] ของ Prolog คือลิสต์ถูกแบ่งเป็นสองส่วน ได้แก่ ส่วนหัว กับส่วนที่เหลือ (ตรงนี้จะต่างจากภาษา C ที่ใช้วิธีกำหนดทั้งหมดไว้ในอะเรย์ แล้วระบุจำนวนอาร์กิวเมนต์มาให้) เลยอยากจะลองเขียน method reverse สำหรับสลับลำดับข้อมูลในลิสต์เรียงกลับจากลำดับเดิม โดยใช้วิธี recursive แบบ Prolog ดู

reverse([],[]).
reverse([H|T], R) :- reverse(T,TR), append(T,[H],R).
def reverse(first, *rest) 
  if rest.empty?
    return [first] 
  else 
    return reverse(*rest) << first
  end
end

ลองเทียบกันดูในแง่ความหมาย จะเห็นว่่าเหมือนกันเป๊ะๆ แถมยังใช้เครื่องหมายคล้ายๆ กันอีก จุดที่แตกต่างที่สุดก็เห็นจะอยู่ที่ลักษณะของ reverse ใน Ruby เป็นฟังก์ชันมีการส่งค่ากลับ ส่วน reverse ของ Prolog เป็น predicate แสดงความสัมพันธ์ระหว่าง list สองอัน

เป็นอันสิ้นสุดการทดลองของวันนี้แต่เพียงเท่านี้

programming , ,