Jan 5, 2014

0!=1

สัญลักษณ์คณิตศาสตร์ที่น่าฉงนปนตกใจมากที่สุดคงหนีไม่พ้น factorial (!) และสิ่งที่น่าตกใจยิ่งกว่าคือคำตอบที่ว่า 0!=1 เมื่อถามต่อว่าทำไมก็จะได้รับคำตอบว่ามันเป็นนิยาม ซึ่งจริงๆ ก็ถูกต้องแล้วแหละ แต่หลายคนคงอยากให้มันมีความหมายที่ลึกซึ้งกว่านั้น

เมื่อลองพิจณานิยามพื้นฐานสุดของ factorial การจะได้มาซึ่ง n! นั้นคือการหาผลคูณจาก 1 ขึ้นไปเรื่อยๆ จนถึง n การหา 1! ได้จึงไม่แปลก เพราะมันคือผลคูณจาก 1 ไปจนถึง 1 (ซึ่งก็คือไม่ต้องคูณเลยซักตัว) แต่ถ้าคิดตามนิยามนี้ 0! ก็ไม่มีตัวตนเพราะว่า 1 มีค่ามากกว่า 0 (แล้วเราจะคูณขึ้นไปเรื่อยๆ ได้ยังไง!?) ตอนนี้ถ้าเราอยากให้ 0! มีค่าก็ต้องหาวิธีคำนวณ/นิยายามที่มีเหตุผลขึ้นมาเพื่อให้คนอื่นยอมรับค่านั้นๆ ... ซึ่งแน่นอนว่ามันไม่ใช่การเปลี่ยนนิยามข้างต้นเป็น n! คือผลคูณจาก 0 ขึ้นไปเรื่อยๆ จนถึง n แน่ :P



ความพยายามแรกที่จะหาคำตอบนี้ ก็คือการกล่าวถึง factorial ในรูปของ n!=n(n-1)! จะเห็นว่าสูตรนี้เป็น recursive การจะหาผลลัพท์ได้นั้น ต้องมองย้อนกลับไป 1 ขั้นเสมอ ในทำนองเดียวกัน ถ้าเรารู้ผลลัพท์ของขั้นตอนปัจจุบัน เราอาจจะสามารถย้อนกลับไปคำนวณผลลัพท์สำหรับขั้นตอนก่อนหน้าได้ ดังนั้น
5! = 120 = 5 * 4!
4! =  24 = 4 * 3!
3! =   6 = 3 * 2!
2! =   2 = 2 * 1!
1! =   1 = 1 * 0!
เลขตัวเดียวที่คูณหนึ่งแล้วยังได้หนึ่งก็คือหนึ่ง ดังนั้น 0!=1 อย่างไม่ต้องสงสัย...

ใครตอบมาอย่างนี้บอกได้เลยว่าเป็นนักคณิตศาสตร์เกรด C เพราะถ้าเราลองทำต่อไปจะได้ว่า
0! = 1 = 0 * (-1)!
แน่นอนว่าสมการนี้หาคำตอบไม่ได้ (เลขใดๆ คูณศูนย์ย่อมได้ศูนย์) แม้ว่าสมการข้างต้นจะทำงานได้ดีไม่มีผิดถ้าหาก n เป็นเลขจำนวนเต็มบวก แต่ก็ทำให้เกิดข้อสงสัยว่า ในเมื่อ 0! ไม่สามารถคำนวนมาได้จากฝั่ง 0!=0*(-1)! แล้วเราจะแน่ใจได้หรือ ว่าสมการข้างต้นนี้สามารถใช้ได้ตั้งแต่ n=1 จริงๆ (เพราะตอนนี้เรามั่นใจแค่ว่ามันใช้ได้ตั้งแต่ n=2)



ความพยายามต่อมาคือ gamma function ที่ให้สมการอินทิเกรตยากๆ มาอันนึง (ซึ่งแก้ออกได้ด้วยการทำอินทิเกรตแยกส่วน แล้วลดรูปกลับมาให้อยู่ในรูปเดิม) สมการนี้เมื่อหาค่าทุกจุดแล้วนำมาพล็อตกราฟ จะได้ภาพการลากเส้นโค้งเชื่อมจุดต่างๆ ของ factorial นั่นเอง กราฟนี้บอกเราว่า 0!=1 และ factorial ของจำนวนเต็มลบใดๆ หาค่าไม่ได้ ซึ่งไม่ขัดแย้งกับความพยายามก่อนหน้า


แม้ว่าความพยายามนี้จะเลิศเลอเพอร์เฟคแค่ไหนก็ตาม (ทั้งสมการยากๆ ที่ทำให้ดูเป็นมือโปร ทั้งการขยายขอบเขต factorial ให้ไปอยู่บนจำนวนจริงได้) แต่มันก็เกิดขึ้นหลังจากที่เรานิยามได้แล้วว่า 0!=1 ครับ



ความพยายามสุดท้าย คือการกลับไปยังต้นกำเนิดของการที่เราอยากนิยาม factorial มาใช้เพื่อความสะดวกในการเขียนและคำนวณ นั่นก็คือเรื่องของโอกาสและความน่าจะเป็น เช่นตัวอย่างที่ว่า ถ้ามีนักเรียน 5 คน จะจัดให้ยืนเรียงต่อแถวกันได้ทั้งหมดกี่แบบ เราก็จะตอบได้ว่า คนแรกเลือกที่ยืนได้ 5 ที่ คูณกับคนที่สองเลือกยืนได้ 4 ที่ (เพราะคนแรกจองไปที่นึงแล้ว) คูณกับคนต่อมามีที่ยืนแค่ 3 ที่ (สองคนแรกจองไปสองที่) ... เมื่อคิดอย่างนี้ต่อไปเรื่อยๆ เราจะเห็นได้ว่ามันย้อนกลับไปหานิยามตั้งต้นของ factorial นั่นเอง

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

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

ความพยายามนี้แม้แลดูเป็นปรัชญาอันสูงส่งเข้าใจยาก (ที่พยายามบอกเป็นนัยเชิงปฏิทรรศน์ว่า ความว่างเปล่าแท้จริงแล้วก็ยังไม่ว่าง) มันนับว่าเป็นการอธิบายที่ถูกต้องตรงจุดที่สุดในบรรดาความพยายามอธิบายที่ผ่านๆ มาเลยทีเดียว



ส่วนเรื่องจริงนั้นก็อย่างที่บอกแต่แรกสุดแล้ว ว่า 0!=1 มันเป็นนิยามนั่นแหละ

เหตุผลก็ไม่พิศดารอะไรเลย แค่การนิยามแบบนี้แล้วจะทำให้เขียนสมการง่ายขึ้นเท่านั้น ลองคิดดูว่าถ้าเราไม่นิยามแบบนี้จะเกิดอะไรขึ้น
  • สัมประสิทธิ์ทวินาม หรือที่หลายๆ คนคุ้นเคยในรูปของสามเหลี่ยมปาสคาลมากกว่า มันมีสูตรว่า
    (x+y)^n = sigma C(n,k) x^(n-k)y^k for k from 0 to n
    
    ซึ่งถ้าไม่ได้นิยาม factorial ตามข้างต้น ก็ต้องเปลี่ยนสูตรนี้ใหม่เป็น
    (x+y)^n = x^k + (sigma C(n,k) x^(n-k)y^k for k from 1 to n-1) + y^k
    
  • นิยาม permutation กับ combination ต้องกลับไปเขียนข้อยกเว้นไว้ที่
    • C(n,0) (เลือกของ 0 ชิ้นจากของ n ชิ้นได้กี่วิธี) ซึ่งก็ยังเข้าใจได้เพราะการไม่เลือกของซักชิ้นเลยอาจเป็นข้อถกเถียง
    • C(n,n) (เลือกของ n ชิ้นจากของ n ชิ้นได้กี่วิธี) อันนี้เริ่มแปลกๆ แล้ว ทำไมเลือกของทุกชิ้นต้องมีข้อยกเว้นด้วยหละ?
  • การหาค่า e จากการกระจายเทย์เลอร์ จากเดิมที่เราสามารถสรุปสั้นๆ อย่างสวยงามได้ว่ามันคือ
    e = sigma 1/k! for k from 0 to infinity
    
    ก็จะกลายเป็นสมการหน้าเกลียดๆ ที่ไม่สามารถอธิบายได้ว่าเลข 1 ตัวแรกนั้นโผล่มาจากไหน/โผล่มาทำไม เช่นนี้
    e = 1 + (sigma 1/k! for k from 1 to infinity)
    
เพราะคณิตศาสตร์คือความสวยงามครับ :)

Jan 1, 2014

ปี 2013 ของฉัน

ปีที่แล้วไม่เป็นชิ้นเป็นอันที่สุด โปรเจกต์ทำเล่นหลายอันปิดไม่ลง แล้วระหว่างทำก็ดันคิดโปรเจกต์ใหม่ๆ ออกอยู่ร่ำไป โฮฮฮฮ

  • ทำ Debian Package สำเร็จจนได้ หลังจากงมมานาน ฝากไว้ที่ ppa:neizod/ppa ทำขำๆ ไม่รู้จะได้กลับไปดูแลหรือเปล่า :\
  • เรียนต่อมอเกษตร ตั้งใจมาฝากตัวเป็นลูกศิษย์อาจารย์ @jittat สอบเข้าไม่ยากเท่าไหร่ สอบจบต้องรอดูกันต่อไป 55+
  • เป็นวิทยากรสอน Python ท้าทายดี ขนาดแค่กลุ่มเล็กที่เหมือนจะดูแลได้ทั่วถึง ยังรู้สึกว่าทำได้ไม่ค่อยดีเท่าไหร่เลย
  • ซื้อ ThinkPad เอง เก็บตังค์จากข้อข้างบนนั่นแหละ
  • ลดน้ำหนักได้ 7 Kg ถือว่าน้อยถ้าเทียบทั้งปี แต่ก็ถือเป็นการเริ่มต้นที่ดี (มั้ง) กับคนที่เคยมีแต่น้ำหนักขึ้นมาโดยตลอด Orz
  • เขียนบล๊อกเดือนละครั้ง แม้เฉลี่ยแล้วจะเกิน แต่ก็ถือว่าไม่สำเร็จเพราะบางเดือนก็หายไปดื้อๆ ขี้เกียจตัวเดียวเลย TwT

achievement ไม่ค่อยแปลกเท่าไหร่ กินพิซซ่าข้ามปี 555+

Nov 10, 2013

Division ใน Relational Algebra

การหาร (DIVISION) ใน relational algebra อธิบายง่ายๆ คือการเอาตารางมา join กันแล้วนับว่าแต่ละ pk ของตารางตัวตั้ง มี row เท่ากับตารางตัวหารหรือไม่ ถ้าครบก็ตัด column ที่ปรากฏในตารางตัวหารทิ้งแล้วยุบรวม row ซ้ำด้วย pk เช่นเรามีข้อมูล
table: students
------+------+-------
 name | subj | grade 
------+------+-------
 bar  | code | A
 foo  | code | C+
 foo  | trek | A
 qux  | code | B+
 qux  | math | A
 qux  | trek | A
------+------+-------

table: colleges
-----------+------
  campus   | subj 
-----------+------
 leetademy | code
 leetademy | trek
 hack-cool | code
 hack-cool | math
-----------+------
คำสั่ง SQL ของการหารก็จะมีหน้าตาประมาณ
SELECT *
FROM   students
DIVISION (
    SELECT subj
    FROM   colleges
    WHERE  campus = 'leetademy'
) AS leetademy_courses
ซึ่งให้ผลลัพท์ออกมาคือ
------
 name 
------
 foo
 qux
------



ปัญหาของการหารคือ DBA แทบทั้งหมดไม่มีคำสั่งนี้ให้ใช้ (ต่างจากการลบ (MINUS) ที่ยังมีบ้าง แม้บางเจ้าจะเปลี่ยนชื่อเป็น EXCEPT ก็ตาม) เพราะมันเป็นท่าที่เปลืองทรัพยากรมาก แถมไม่ได้ใช้กันบ่อยๆ เสียด้วย ...ซึ่งจะว่าไปแล้วมันก็ดูไม่ใช่ปัญหาเลยนี่หน่า

อย่างไรก็ตามปัญหาจริงๆ คือมันเป็นท่าที่อาจารย์ database ส่วนใหญ่ชอบเอาไปออกสอบ ทำให้เราต้องหาท่าอ้อมๆ ที่ให้ผลลัพท์แบบเดียวกันมาใช้แทน

การจะเข้าใจมันได้ ถ้าคิดแบบ programming ก็แค่ดูว่า set ที่เป็นตัวตั้งหารนั้นเป็น superset ของ set ที่ถูกหารหรือเปล่า
students_set = defaultdict(set)
for name, subj, grade in students:
    students_set[name].add(subj)

colleges_set = defaultdict(set)
for campus, subj in colleges:
    colleges_set[campus].add(subj)

output = set()
for name, subj_set in students_set.items():
    if subj_set >= colleges_set['leetademy']:
        output.add(name)
print(output)
แต่การทำงานบน database เรามักเขียนพวกนี้ทั้งหมดให้เป็น one-liner เพื่อที่จะรับประกันว่าระหว่างการ query จะไม่เกิด race condition กับ query อื่นๆ สิ่งที่ต้องทำก่อนอื่นใดคือพยายามกำจัดการสร้าง set ของข้อมูลล่วงหน้าซะ

ในที่นี้ ขั้นแรกเราจะกำจัดการสร้าง colleges_set โดยวน loop ใน colleges ทุกตัวดูว่าแต่ละ subj มีใน students_set หรือเปล่า
def learnt_all(name):
    for campus, subj in colleges:
        if campus == 'leetademy':
            if subj not in students_set[name]:
                return False
    return True

output = set()
for name, _, _ in students:
    if learnt_all(name):
        output.add(name)
print(output)
ขั้นต่อมาก็กำจัด students_set โดย loop เข้า students ดูว่านักเรียนที่ชื่อ name ได้เรียนวิชา subj แล้วหรือยัง
def learnt(name, subj):
    for student_name, student_subj, _ in students:
        if student_name == name and student_subj == subj:
            return False
    return True

def learnt_all(name):
    for campus, subj in colleges:
        if campus == 'leetademy':
            if learnt(name, subj):
                return False
    return True
    
output = set()
for name, _, _ in students:
    if learnt_all(name):
        output.add(name)
print(output)
เนื่องจากในฟังก์ชัน learnt กับ learnt_all จะคืนค่า False ทันทีที่เข้าเงื่อนไขครบ แต่จะคืนค่า True หลังจากจบ loop เท่านั้น เราสามารถเขียนตรงนี้ใหม่ด้วย generator แล้ว wrap ด้านนอกด้วย not any(...) เช่นนี้
def inverse_learnt(name, subj):
    for student_name, student_subj, _ in students:
        if student_name == name and student_subj == subj:
            yield True

def inverse_learnt_all(name):
    for campus, subj in colleges:
        if campus == 'leetademy':
            if not any(inverse_learnt(name, subj)):
                yield True

output = set()
for name, _, _ in students:
    if not any(inverse_learnt_all(name)):
        output.add(name)
print(output)
พอทุกอย่างเป็น single-statement และ generator คราวนี้ก็ง่ายที่จะทำ one-liner แล้ว ... จับมาเขียนสวยๆ ได้แบบนี้
print({
    name
    for name, _, _ in students
    if not any(
        True
        for campus, subj in colleges
        if campus == 'leetademy'
        and not any(
            True
            for student_name, student_subj, _ in students
            if student_name == name
            and student_subj == subj
        )
    )
})
เห็น code นี้แล้วคงเดาได้ไม่ยากว่า พอเอาเขียนเป็น SQL คงหนีไม่พ้น
SELECT DISTINCT name
FROM   students AS s
WHERE  NOT EXISTS (
    SELECT *
    FROM   colleges AS c
    WHERE  campus = 'leetademy'
    AND    NOT EXISTS (
        SELECT *
        FROM   students AS ss
        WHERE  ss.name = s.name
        AND    ss.subj = c.subj
    )
)

อ้างอิง


Oct 29, 2013

ทำไมเชียงใหม่ถึงเจริญกว่าลำปาง

สัปดาห์ก่อนนั่งย่างเนื้อกับ @pruet @TonsTweetings แล้วเกิดคำถามขึ้นมาว่า ทำไมเมืองเชียงใหม่ถึงได้เจริญกว่าลำปางมาก ทั้งๆ ที่ลำปางนั้นอยู่ในจุดยุทธศาสตร์ที่ดีกว่า

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

    ยอมรับโดยสัตย์จริงว่า เคยคิดว่าเชียงใหม่เป็นอย่างนี้ได้ เพราะเจ้าดารารัศมี (เมื่อร้อยกว่าปีก่อน) เลยนะเนี่ย

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

    ประวัติศาสตร์ที่มีผลกระทบจริงๆ ต่อปัจจุบันคือช่วงไม่ถึงร้อยปีที่ผ่านมา และเราควรเอาใจใส่กับเรื่องราวเหล่านี้ให้มากขึ้น อย่าเพิ่งไปมองถึงวิธีการว่าเราควรเรียนแบบท่องจำหรือวิเคราะห์เลย แค่หลักสูตรก็มีปัญหาแล้ว (ทั้งๆ ที่การแก้ไขหลักสูตรมันมีแรงต้านน้อยกว่าด้วย)

  2. อึ้งอย่างที่สองคือเหตุผลที่ทำให้เชียงใหม่เจริญจนถึงจุดนี้ได้ เพราะตอนนั้นลำปางถูกรัฐบาลส่วนกลางวางไว้ให้เป็นจุดยุทธศาสตร์ภาคเหนือ เลยได้เป็นคนเลือกก่อนว่าจะเอาความเจริญจากสถานีโทรทัศน์หรือมหาวิทยาลัยไปตั้งในจังหวัดตัวเอง แล้วลำปางเลือกสถานีโทรทัศน์ช่อง 8 (2503) ทำให้เชียงใหม่ได้มหาวิทยาลัย (2507) มาแทน

    ผลพวงจากการที่เชียงใหม่ได้มหาวิทยาลัยมาแบบส้มหล่น ทำให้ความเจริญต่างๆ ค่อยๆ คืบคลานตามมาทีหลัง ไม่ว่าจะเป็นสนามบินนานาชาติ (2513) มหาวิทยาลัยพายัพ (2517) เซ็นทรัลกาดสวนแก้ว (2535) โรบินสันแอร์พอร์ต (2539) แบงก์ชาติ (2540) ... และท้ายที่สุด สถานีโทรทัศน์ช่อง 11 (2543)

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

    นั่งนึกไปนึกมาก็อดคิดไม่ได้ว่า ถ้าตอนนั้นเชียงใหม่ได้เป็นคนเลือกก่อนบ้างหละ? จะเป็นฝ่ายเลือกมหาวิทยาลัย (อย่าลืมว่าตอนนั้นเชียงใหม่ก็มีมหาวิทยาลัยแม่โจ้ (2477) อยู่ก่อนแล้ว) หรือเลือกสถานีโทรทัศน์แบบที่ลำปางทำนะ

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

edit: แก้ไขเป็นมหาวิทยาลัยพะเยาตามความเห็นของ @pruet

Oct 27, 2013

ทำ Debian Package ขึ้น Launchpad PPA

การทำ software package นั้นก็เปรียบเสมือนพ่อค้าคนกลาง หลักการทั่วๆ ไปคือ
  1. สืบเสาะว่าในตลาด (package source) ตอนนี้ สินค้าชนิดไหนที่ลูกค้า (user) ต้องการแต่ยังไม่มี (หรือมีแต่ก็ยังไม่ดีพอ)
  2. ตระเวณหาผู้ผลิตสินค้า (upstream) ชนิดนั้นๆ เจรจาต่อรองราคาซื้อสินค้า (app) มาตุนไว้
  3. ปรับแต่งสินค้า (config) ให้เหมาะกับลูกค้า แล้วบรรจุหีบห่อ (packaging) ส่งขายหน้าร้านของตัวเอง (PPA)
หน้าที่ของคนออก software package จึงไม่ใช่การเขียน code สำหรับ software ตัวนั้นๆ ซักเท่าไหร่ แต่เป็นการ config และทดสอบ dependency ให้ทำงานได้บนระบบนั้นซะมากกว่า

อย่างไรก็ตาม บางครั้ง software ที่ต้องการทำ package ก็ไม่มี upstream เพราะอาจจะผูกติดกับ platform นั้นๆ (native package) อย่างเช่น theme หรือโปรแกรมเฉพาะสำหรับ platform

บังเอิญว่าอยากทำ theme package พอดี แต่ tutorial ที่เจอดันสอนแต่แบบที่ต้องพึ่ง upstream (และเน้นหนักด้าน config) แถมยังเอา bzr มาปนซะเยอะ (แน่นอนว่าผมพยายามใช้แต่ git ถ้าเป็นไปได้) สุดท้ายเลยไล่โหลด code ชาวบ้านมาดูถึงทำเป็น

วิธีการทำ native package สำหรับ Debian โดยคร่าวๆ ก็คือ
mkdir proj-package/proj-0.9.0
cd proj-package/proj-0.9.0
dh_make --native
หมายเหตุว่าตอนนี้จำเป็นที่จะต้องห้อยเลข version ไว้ที่ชื่อ directory ด้วย ส่วน proj-package จะมีหรือไม่มีก็ได้ แต่พอเราทำ package แล้วไฟล์ต่างๆ จะมาอยู่ทำระดับเดียวกันกับ proj-0.9.0 ดังนั้นถ้าไม่อยากให้ไฟล์อื่นๆ ปนกันมั่วก็สร้างไว้ด้วย

พอสั่งคำสั่งสุดท้ายเสร็จก็ตอบคำถามอีก 1-2 ข้อ จะมี directory ชื่อ debian โผล่มา ถึงตอนนี้ถ้าอยากแก้ proj-0.9.0 เอา version ออกให้เหลือแค่ proj หรือกระทั่งเปลี่ยนเป็นชื่อใดๆ ก็ทำได้เลย

เรียบร้อยแล้วเข้าไปแก้ไฟล์ต่างๆ ใน debian ดังนี้
  • ลบทุกไฟล์ที่ลงท้ายด้วย .ex, .EX
  • ลบไฟล์ที่เกี่ยวกับ README และ directory source
  • แก้ไฟล์ copyright เพิ่มชื่อเจ้าของผลงานการทำ package ลงไป
  • แก้ไฟล์ control เพิ่มรายละเอียด อันนี้ยากหน่อย แนะนำให้ดูตัวอย่าง package ที่คล้ายๆ กันแล้วลอกมา

แก้เสร็จแล้วลอง build package ดู โดยกลับไปอยู่ที่ระดับเดียวกับ directory debian แล้วสั่ง
debuild -us -uc
จะได้ไฟล์ proj_0.9.0.deb (Debian installer ที่สามารถ double click เพื่อติดตั้งได้) ที่ระดับ directory proj-0.9.0 (กลับออกไป 1 ระดับ)

แต่ว่า package ตอนนี้มันยังเป็น package เปล่าๆ ไม่มีอะไร ถ้าต้องการระบุว่า package นี้จะติดตั้งไฟล์ไปที่ใดบ้าง ก็เอาไฟล์นั้นๆ ไปไว้ในระดับเดียวกันกับ debian เช่น
proj-0.9.0
├── bin
│   ├── goodbye.sh      # install => /usr/bin/goodbye.sh
│   └── hello.sh        # install => /usr/bin/hello.sh
├── conf
│   └── greeting.conf   # install => /etc/greeting.conf
└── debian
แล้วก็เขียนไฟล์ debian/install เช่นนี้
bin/* /usr/bin/
conf/* /etc/
หมายเหตุว่าไฟล์พวกนี้จะไปทับไฟล์ของ package อื่นๆ ไม่ได้ (ต้องเพิ่ม Conflict/Replace ใน debian/control -- จะยังไม่พูดถึงตอนนี้) อันที่จริง มันไม่ควรจะไปทับไฟล์ใดเลย เพราะตอน remove ไฟล์พวกนี้จะหายไปอัตโนมัติ ไม่ใช่ revert กลับไปเป็นของเดิม

ส่วน action ต่างๆ ที่จะทำตอนก่อนหรือหลังการ install / remove (เช่น install เสร็จแล้วก็ auto config เล็กน้อย) ก็เขียนเป็น shell script ใส่ preinst, postinst, prerm, postrm ไว้ใน directory debian นั่นแหละ

เมื่อ test build ที่เครื่อง local จนมั่นใจแล้ว ก็แก้ไฟล์ debian/changelog เปลี่ยน unstable เป็น codename ของระบบที่ใช้อยู่ (ดูได้ที่ /etc/lsb-release) และแก้ข้อมูลอื่นๆ ตามสมควร

เรียบร้อยแล้วกลับมาอยู่ที่ระดับเดียวกับ proj-0.9.0 แล้วสั่ง
tar -zcvf proj_0.9.0.orig.tar.gz proj-0.9.0/

cd proj-0.9.0
debuild -S -k$PGPKey

cd ..
dput ppa:user/repo proj_0.9.0_source.changes
เพื่ออัพ package นี้ขึ้น PPA ที่ Launcpad ต่อไป (รอมัน build ประมาณครึ่งชั่วโมง)

ทีนี้ ถ้าเพื่อนเราที่ใช้ Debian/Ubuntu รุ่นที่เรา build package ตัวนี้ไว้ อยากจะ install package นี้ ก็บอกเพื่อนว่าทำแค่
add-apt-repository ppa:user/repo
apt-get update
apt-get install proj
เท่ปะล่า :P

Oct 15, 2013

Taskbar 2 ข้าง

ผมชอบแนวคิดของ Unity พอสมควร คือในเมื่อพื้นที่แนวตั้งมันเหลือน้อย แล้วทำไมเราไม่ย้ายพวก taskbar ต่างๆ ไปไว้ด้านข้างซะหละ? (รักษาพื้นที่แนวตั้งให้ได้มากที่สุด) แต่ที่รับไม่ค่อยได้คือมันเล่นบังคับให้อยู่แค่ด้านซ้ายอย่างเดียวเลย (เผด็จการ) แถมขนาดที่ใหญ่เวอร์ของมันก็ทำให้หน้าจอดูไม่สมดุลอีก

ทางออกง่ายๆ คือทำให้ taskbar มีทั้ง 2 ข้างซะ ข้างนึงอาจจะเป็น taskbar (โปรแกรมที่เรียกบ่อย / โปรแกรมที่เปิดอยู่) แล้วอีกข้างเป็น status bar (วันที่, เวลา, ภาษา, ไวไฟ ฯลฯ) ก็ดูดีไม่ใช่น้อย

ผมลอง XFCE แล้วพบว่า build-in taskbar ก็พอใช้ได้ (ลูกเล่นน้อยกว่า Unity) แต่ที่รับไม่ค่อยได้คือ status bar มันกากๆ ยังไงไม่รู้ ปัญหาที่เจอคือถ้าวาด icon พวกไวไฟ, แบตฯ ใหญ่ๆ แล้วภาพแตก แต่ถ้าวาดรูปเล็กไปก็ไม่มีพื้นที่พอให้แสดงเวลาอีก

เล่นไปเล่นมาก็เพิ่งนึกได้ว่า ไอ้ status bar เนี่ยไม่จำเป็นต้องเอาไว้ด้านข้างก็ได้ ยังไงซะถ้าเปิด app เต็มจอ ก็โดน menu bar แย่งพื้นที่อยู่ดี ซึ่งตรงนี้ Unity มันเอา status bar ไปรวมกับ menu bar ให้อยู่แล้ว ปล่อยไว้อย่างนั้นแหละ

ตอนนี้เลยเล่นท่าง่าย ซ่อน taskbar ของ Unity ไว้ซะ แล้วใช้ Docky ทั้งสองข้างแทน ได้ผลออกมาดังนี้


ใช้ไปใช้มาแล้วพบว่ายังติดปัญหาอยู่เล็กน้อย เช่น
  1. ก็ยังตั้งค่าให้กด icon เพื่อเรียก Unity Dash ไม่ได้ ต้องกดปุ่ม super อย่างเดียว (ปัญหาเดิมเหมือนตอนซ่อน taskbar แล้วใช้ Docky ข้างล่าง)
  2. ถ้าใช้โปรแกรมแบบเต็มหน้าจอ จะเลื่อนเมาส์ไปลาก scrollbar ต้องใช้สมาธิเพิ่มขึ้นมากๆ ไม่งั้นจะไปโดน taskbar ด้านขวามือแทน (แล้วมันก็จะขยายมาบังอีก) ซึ่งแก้ได้ด้วยการไม่เปิดโปรแกรมเต็มหน้าจอบ่อยๆ + ใช้ keyboard แทนในหลายๆ กรณี (home, ctrl+down, mouse wheel)
  3. ตัวหนังสือ default ของ Ubuntu มันไซส์ใหญ่ไปหน่อย เปิด 2 terminal ข้างๆ กันแล้วไม่ได้จอละ 80 ตัวอักษรแล้ว
  4. ใช้ Touchpad / TrackPoint จิ้มเรียกโปรแกรมจะไม่ค่อยรู้สึกแปลก (เพราะมันใช้ได้ทั้ง 2 มือ) แต่ถ้าใช้เมาส์มือขวาจะรู้สึกว่ามันจิ้ม taskbar ด้านซ้ายยากมาก ... อันนี้คงคิดไปเอง :P

แต่โดยรวมก็ถือว่า happy กว่าเก่าเยอะ

Oct 1, 2013

ฝากหน้าเว็บขำๆ กับ Google App Engine

ช่วงนี้เล่น JavaScript / CoffeeScript / CSS บ่อย พอโปรแกรมเริ่มซับซ้อนขึ้น ครั้นจะแสดงตัวอย่างด้วย code เพียวๆ ให้นั่งนึกตามคงไม่ไหว เลยต้องหาที่ฝากไฟล์แบบแสดงเป็นหน้าเว็บซักหน่อย จะได้ demo เจ้าตัว mock up นี้อย่างสะดวกราบรื่น ไม่ต้องเปิดเครื่องตัวเองโชว์หรือให้คนอื่นเข้ามาทาง ip address

ทางออกที่สวยงามที่สุดสำหรับผมคือ Google App Engine ครับ เพราะนอกจากจะได้ URL ที่สั้นและสะอาดแล้ว ยังสามารถพัฒนา controller เบื้องหลังต่ออย่างเนียนๆ ในอนาคตได้อีก

วิธีการก็ไม่ยุ่งยากครับ โหลด App Engine SDK (ผมใช้แบบ command line) มาพร้อมสร้าง application ใหม่ เรียบร้อยแล้วไปยัง directory ที่เก็บ file เว็บของเรา (.html, .css, .js, .jpg ฯลฯ) สร้าง directory ใหม่ชื่อว่า static แล้วย้าย file ทุกอันไปไว้ใน directory ใหม่นี้ ถ้าทำถูกต้องโครงสร้าง directory จะออกมาประมาณนี้ครับ

.
└── static
    ├── beautiful.css
    ├── img/
    ├── index.html
    ├── jquery.min.js
    └── logic.js

หลังจากนั้น สร้างไฟล์ app.yaml แล้วเขียนข้อมูลดังนี้

application: application-name
version: 0-alpha
api_version: 1

runtime: php
threadsafe: true

handlers:
- url: /
  static_files: static/index.html
  upload: static/

- url: /(.*)
  static_files: static/\1
  upload: static/(.*)

อย่าลืมเปลี่ยนบรรทัดแรกให้เป็นชื่อ application ตามที่สมัครไว้กับ App Engine ด้วยนะครับ

ถึงตอนนี้ ถ้าใช้ command line เพียงสั่ง appcfg.py update . ก็เรียบร้อย ส่วน GUI ให้สั่ง import application เพื่อเลือก directory นี้เข้ามา แล้วกด deploy ครับ



อย่างไรก็ตาม วิธีนี้ฝากได้แต่ไฟล์ static เท่านั้น ถ้าเป็นไฟล์ script ที่ทำงานฝั่ง server (เช่น .php) มันจะไม่แปลเป็น .html ให้ครับ ระวังว่าจะเผลออัพไฟล์ source code ที่มีข้อมูลที่ไม่อยากให้ใครเห็นนะครับ

ถ้าอยากให้ server แปล .php ก็เอาไฟล์ .php ทั้งหลายออกจาก directory static มาไว้ที่ directory หลัก แล้วเพิ่ม handlers นี้ไว้ก่อนอันสุดท้ายครับ

- url: /(.*)\.php
  script: \1.php

ทั้งนี้ database ของ App Engine นั้นไม่ใช่ MySQL นะครับ ถ้าจะติดต่อ database ด้วยอาจต้องศึกษาเพิ่มนิดหน่อยแล้วหละ ;)

ก็น่าจะเป็นทางเลือกที่น่าสนใจไม่น้อย ตั้งแต่คนที่แค่มองหา host ขำๆ ไว้ฝากหน้าเว็บส่งงาน mock up ไม่กี่ครั้ง ไปจนถึงเหล่าผู้พัฒนา prototype ทั้งหลายที่อาจต้องขยาย project ในอนาคตเลย

Sep 27, 2013

ไอคอนสกิลโปรแกรมเมอร์

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

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

และนี่คือร่างที่สองบนกระดาษ


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

ตอนแรกก็กะว่าจะสแกนรูปไปใช้เลย ติดที่ว่าตอนใช้งานจะตั้งใจให้มันมีทั้งโหมดขาวดำและภาพสี เลยลองลากๆ เมาส์ใน Gimp / Inkscape และพบว่าไม่เวิร์ก ไม้ตายสุดท้ายคือกลับสู่ปากกา โชคดีว่ายืม iPad มาได้เลยจิ้มวาดใน MyScript Memo ครับ



ถ้าเห็นว่าสวยก็เอาไปใช้กันได้เลยจ้า ให้เครดิตพร้อมหลังไมค์มาบอกแค่นี้ก็พอแล้ว ;)

Sep 19, 2013

ริจะฟังเพลงคลาสสิก อย่ายึดติดว่าต้องจินตนาการเห็นต้นไม้ใบหญ้า

ยาวไปไม่(ต้อง)อ่าน: จินตนาการระหว่างฟังเพลงคลาสสิกแล้วสนุกก็ทำไปเถอะ

แตกมาจากบทวิเคราะห์ Moonlight Sonata นะครับ เพื่อไม่ให้มีหลายเรื่องปนกัน

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

หลายครั้งผมก็พยายามฟังดนตรีเพื่อดนตรีนะ (พวกทฤษฎีดนตรี วิเคราะห์คอร์ด หาโมทีฟเปลี่ยนรูป อะไรทำนองนั้น) แต่ก็อดไม่ได้ที่จะนึกภาพตามว่าเสียงบรรเลงแบบนี้ มันควรจะเกิดขึ้นกับสถานการณ์แบบไหน

การรับรู้ของมนุษย์ที่แข็งแรงที่สุดยังไงก็หนีไม่พ้นการมองอยู่ดี ดังนั้นการไปบังคับว่าฟังแล้วห้ามจินตนาการภาพตามเนี่ยแหละที่แปลก แปลกมากด้วย

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

ดังนั้นคนที่ฟังแล้วไม่เห็นภาพอะไรก็ไม่ต้องกังวล ส่วนคนที่สั่งห้ามจินตนาการนั้นก็ดูท่าว่าจะกลัวจนเกินเหตุ

จินตนาการกันดีกว่า ใครจะมาเข้าใจผิดก็ช่างเขาเถอะ

Sep 10, 2013

CoffeeScript มหัศจรรย์: ลูกศรเปลี่ยน Scope

JavaScript เป็นภาษา functional ที่คนมักเข้าใจผิดว่ามันเป็น OOP (ด้วยตัว syntax ที่หน้าตาคล้าย C/Java เสียเหลือเกิน) ด้วยความเข้าใจผิดและไม่มี syntax ประกาศ class โดยตรง ทำให้ framework หลายๆ เจ้าพยายาม define syntax นี้กันเอง ตัวอย่างเช่นใน CoffeeScript

class Dog
    constructor: (@name, @owner) ->
        me = $('<button>').addClass('dog').append(@name).click(@bark)
        $('#animals').append(me)
    
    bark: ->
        alert(@name + ': Bark bark. I love you, ' + @owner + '. Bark!')

bone = new Dog('Bonnie', 'Alice')

ดูดีและเหมือนภาษา OOP ทั่วไปเลยทีเดียว นอกจากนี้เมื่อทดสอบบน console จะเห็นว่า bone.bark() แสดงผลลัพท์ที่ถูกต้อง ไม่น่ามีปัญหาอะไร

อย่างไรก็ตาม ด้วยความเป็น dynamic scope ทำให้ฟังก์ชัน bark ไปใช้ scope จาก jQuery แทน ถ้ากดปุ่มจะเห็นว่าคราวนี้มี undefined ในข้อความ (เพราะ me ที่เป็น jQuery object ไม่มี attribute owner)

วิธีแก้แบบ JavaScript จะคล้ายๆ Python คือสร้าง self ที่ชี้กลับมาหาตัวเราจริงๆ แล้วให้ this เป็นแบบ dynamic ตามปรกติ

class Dog
    constructor: (@name, @owner) ->
        self = this
        me = $('<button>').addClass('dog').append(@name).click -> self.bark()
        $('#animals').append(me)

    bark: ->
        alert(@name + ': Bark bark. I love you, ' + @owner + '. Bark!')

ส่วน CoffeeScript มีท่าง่ายกว่านั้น แค่ใช้ fat arrow ก็พอ

class Dog
    constructor: (@name, @owner) ->
        me = $('<button>').addClass('dog').append(@name).click(@bark)
        $('#animals').append(me)

    bark: =>
        alert(@name + ': Bark bark. I love you, ' + @owner + '. Bark!')

ว่าแล้วก็แอบเห็นด้วยกับทวีตนี้ :P