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


Aug 26, 2013

Google Maps Linux เล่น 3D ได้แล้ว \(;w ;)/

เรื่องกราฟฟิกและเกม Linux ถือว่าเป็นลูกเมียน้อยมาตลอด จนกระทั่งต้นปีที่ผ่านมา Steam for Linux ได้ถือกำเนิด บวกกับระบบมือถือยอดนิยมอย่าง Android ที่ผู้ใช้ไม่น้อยซื้อไปเพื่อเล่นเกม สถานการณ์ก็เริ่มดูดีขึ้นมาบ้าง

ตอนนี้ก็มีอีกบริการหนึ่งที่รองรับการใช้กราฟฟิกบน Linux แล้ว ซึ่งก็คือ Google Maps ตัวใหม่นั่นเอง (ไม่แน่ใจว่ารองรับตั้งแต่เมื่อไหร่ เพราะจำได้ว่าช่วงแรกๆ ที่ปล่อยพรีวิว เวลาพยายามดูให้เป็น 3D มันจะฟ้องหา Google Earth Plugin ซึ่งไม่รองรับบน Linux) อันนี้ดูผ่าน Chromium รุ่นล่าสุด เข้าใจว่าพี่แกคงปลุกปล้ำกับ WebGL จนสำเร็จ


ซูมออกไกลสุด เห็นหมู่ดาวและแสงไฟในเมืองด้วย


โลกโค้งๆ กับปุยเมฆบนท้องฟ้า (ไม่แน่ใจว่าภาพเมฆเป็นแบบ live หรือเปล่า)


ซูมภูเขาเห็นเป็นภูเขาแล้ว


ส่วนตึกต้องเป็นเมืองที่รองรับ ตอนนี้ยังมีไม่มากเท่าไหร่ ส่วนใหญ่ก็เมกากับแถบยุโรป



ซูมใกล้สุด รายละเอียดไม่ได้ชัดเวอร์ขนาด backpack เที่ยวผ่านหน้าจอได้แฮะ (แต่ก็ดีกว่าไม่มีหละน่า)

Aug 24, 2013

DatePicker ตัวใหม่ของ Android?

ภายใต้มาตรฐานเดียวกัน (ISO 8601) การสื่อสารเรื่องวันที่ไม่ควรเป็นปัญหาระหว่างวัฒนธรรม และการป้อนข้อมูลผ่านคีย์บอร์ดโดยยึดหลักการนี้ก็ไม่ใช่เรื่องยากแต่อย่างใด

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

อีกทั้งการป้อนข้อมูลผ่านคีย์บอร์ดยังไม่รองรับความผิดพลาดของข้อมูล ไม่ว่าทั้งการพิมพ์ผิด-ตกหล่น (syntactic error) หรือใส่วันที่เกินกว่าที่เดือนนั้นจะมีได้ (semantics error)

การบังคับป้อนข้อมูลด้วยวิธีการเฉพาะสำหรับข้อมูลนั้นๆ จึงเป็นสิ่งที่ควรทำเสมอ ดังตัวอย่างนี้



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



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


โชคดีที่ Android ยกเครื่องศัลยกรรมหน้าตาใหม่หมด (ช่วง Jelly Bean) ทำให้ DatePicker ตัวนี้ดูดีขึ้นมากภายใต้หน้าตาเดิมๆ (มีผลกับการปรับตัวของผู้ใช้ส่วนใหญ่) ด้วยการเลือกวันโดยใช้การเลื่อนขึ้น/ลงแทน แถมยังสามารถเลื่อนแบบมีแรงเฉื่อยเพื่อกระโดดข้ามชุดข้อมูลได้อีกด้วย


และแบบใหม่ล่าสุด เจอใน Google Keep (อาจจะกลายเป็น official ถัดไป) ออกแบบมาอย่างสวยงาม แม้จะหน้าตาจะเปลี่ยนไปบ้าง แต่ก็ยังคงความง่ายในการใช้งานอยู่

บล๊อกตอนนี้ไม่มีอะไร เห็นของสวยเลยมาอวยเฉยๆ :P

Aug 5, 2013

ทำไมผมเกลียด JavaScript

ลองเปิด JavaScript Console ของ browser แล้วป้อนคำสั่งเหล่านี้ลงไปดูครับ ...

console> [] + []
""
console> [] + {}
"[object Object]"
console> {} + []
0
console> {} + {}
NaN

จาก @garybernhardt

ก่อนหน้านี้เคยคิดว่าการเปรียบ == ประหลาดพอแล้วนะ (ยังดีที่มี === ช่วยชีวิต) แต่พอเจอแบบนี้ก็ทำเอาเสียหลักเลยทีเดียว

  1. ด้วยเหตุผลที่ JavaScript ทำงานกับ HTML ซึ่งเป็น markup language ที่เน้นการแสดงผลด้วย string เป็นหลัก การเอา array (หรือ type อื่นใดที่ไม่ใช่ตัวเลข) บวกกับอะไรซักอย่าง จะได้ผลลัพท์เป็น string ทันที (คล้ายๆ กับที่ C ที่มองทุกอย่างเป็น pointer arithmetic) พอเอา array ว่างๆ มาบวกกันเลยได้ string ว่าง
  2. ในทำนองเดียวกัน เมื่อเปลี่ยนเอา array ไปบวกกับ {} (ถือว่าเป็น object ใน JavaScript) ก็ทำให้ได้ผลลัพท์เป็น string ที่บอกว่านี่คือ object นั่นเอง
  3. อย่างไรก็ตาม {} มีความหมายได้อีกอย่างคือ block (ที่ไม่ยอมทำหน้าที่เป็น variable scope ไปด้วยเหมือนภาษา C) ทำให้บรรทัดนี้มีค่าเทียบเท่ากับการเขียนเพียง +[] เครื่องหมายบวกเลยทำหน้าที่ทางคณิตศาสตร์อย่างเต็มที่ และมันเปลี่ยน array ว่างให้กลายเป็นค่า 0
  4. ส่วน object อื่นใดที่ไม่ใช่ array, string ว่าง จะมีค่าเป็น not a number เมื่อเอามาดำเนินการทางคณิตศาสตร์ครับ

เฮ้อ จะเขียน JavaScript แต่ละทีมันเหนื่อย (ใจ) จริงๆ

Aug 4, 2013

Puma คู่แรก

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

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

ถึงกระนั้น Puma ก็ยังเป็นรองเท้าที่ผมใฝ่ฝันมาโดยตลอด อันที่จริงแล้ว วันที่ซื้อ Converse คู่แรกนั้น ผมแวะไปยังโชว์รูม Puma ก่อนด้วยซ้ำ (เรียกว่าเป็น love at first sight เลยก็ไม่ผิด) ความเรียบง่ายลงตัวสไตล์ minimalism อันเกิดจากสีสันเพียงน้อยนิดที่ใช้ พร้อมลวดลายพื้นๆ คาดเดาได้ไม่หวือหวา เด้ดสุดคือตำแหน่งร้อยเชือกเยื้องข้างอันเป็นเอกลักษณ์ องค์ประกอบทั้งหมดรวมกันคงไม่ไกลจากคำว่า perfect เท่าไหร่ อย่างไรก็ตามราคาของมันก็นับว่าสูงไม่ใช่เล่นสำหรับเด็กมหา'ลัย (เทียบเท่าค่าขนมครึ่งเดือน) ก็ทำให้ผมต้องตัดใจจากมันเสียทุกคราเมื่อถึงเวลาปลดระวางรองเท้าคู่เก่า

โชคดีที่เจอช่วงลดราคา (โดยตั้งใจ -- ใครหละจะปล่อยโอกาส) เลยได้ฤกษ์พาเจ้าแมวล้ำยุคคู่แรกคู่นี้ออกไปผจญโลกด้วยกัน


Puma Future Cat M2 - ว่าแต่เห็นที่ไหนๆ เค้าก็ถ่ายมุมนี้กันแฮะ

และหวังว่านี่จะไม่ใช่คู่สุดท้ายจาก Puma ;)

Jul 25, 2013

ยินดียิ่งนัก LaTeX พิมพ์ไทยได้แล้ว

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

ประสบการณ์ใช้ LaTeX ครั้งแรกเกิดขึ้นเมื่อ 5 ปีก่อนในวิชา Mathematics Software ซึ่งนับได้ว่าไม่น่าประทับใจเท่าที่ควร (และทำให้เกิดโครงการ EzMath ตามมา) และยังเลวร้ายขึ้นไปอีกเมื่อผมย้ายมาใช้ Linux เต็มตัวเมื่อสองปีก่อน เพื่อพบว่าการทำให้ LaTeX พิมพ์งานภาษาไทยได้นั้นยุ่งยากมาก จนสุดท้ายต้องหนีไป XeLaTeX หรือกลับไปใช้ MS Word ที่มหาวิทยาลัยแทน

อย่างไรก็ตาม สถานการณ์ตอนนี้เริ่มดูดีขึ้น หลังจาก @theppitak สามารถส่ง ThaiLaTeX กลับเข้าไปยังต้นน้ำได้สำเร็จ ต่อไปนี้ถ้าต้องการพิมพ์ไทยก็แค่ลงโปรแกรม

$ apt-get install texlive-full

แล้วก็เริ่มพิมพ์งานได้เลย ไม่ต้อง config / download อะไรเพิ่มเติมอีกต่อไป

\documentclass{article}
\usepackage[english,thai]{babel}
\usepackage[utf8x]{inputenc}
\begin{document}
สวัสดีอีกครั้ง $ e^{\pi i} + 1 = 0 $
\end{document}

ขอบคุณอีกครั้งที่ช่วยผลักดันวงการซอฟท์แวร์ไทยครับ

Jul 22, 2013

BarCamp เชียงใหม่ ครั้งที่ 6

วันเสาร์ปลายเดือนที่แล้วไปงาน BarCamp เชียงใหม่มา ตามธรรมเนียมที่ดีก็ควร blog เก็บไว้หน่อย (หลังจากอู้มา 2 ปีเต็ม)

งานครั้งนี้จัดที่วิศวะ มช. โดยได้โต้โผใหญ่อย่างอาจารย์ @pruet คอยจัดแจงทุกอย่างให้ ก็ขอขอบคุณอีกครั้ง ณ ที่นี้ด้วยครับ -/\-

อันนี้ session ส่วนตัวกับ @kengggg ว่าด้วย FireFox OS ซึ่งนับว่าเป็น session เช้าเพียง session เดียวที่ผมเข้าฟัง เนื่องจากความล้าจากการเดินทางข้ามคืน + ปั่นสไลด์ไม่เสร็จดี ก็ต้องกราบขอโทษทีม @thainetizen ที่ไม่ได้เข้าฟังด้วย


หลังจากหายเหนื่อยก็เดินสำรวจโน่นนี่นั่น และพบว่าตั้งชื่อห้องได้ geek มากๆ


คู่หูดูโอ้ @shr กับ @ckzon มาพูดว่าทำไมถึงไม่ควร aim ว่าจะทำงานเป็น tester ถ้าไม่อยากเขียน code แต่แรก


จบด้วยอาจารย์ @pruet สุดหล่อ ที่จะพาคุณออกเดินทางผ่านปราการด่านสุดท้ายด้วยความเร็วมากกว่าแสง


สำหรับผมเองพูดเรื่อง Tetris ก็แปลกใจอยู่ที่ได้รับความนิยมอย่างล้นหลามจาก geek ฝรั่ง ไม่แน่ใจว่าเป็นเพราะ session ของผมบรรยายเป็นภาษาอังกฤษ (จริงๆ บอกว่าเป็น 2 ภาษาแต่ไม่มีคนไทยมาฟัง) เพียง session เดียวตอนนั้นหรือเปล่า :P



งานนี้สนุกมากครับ เสียดายว่าผมเองไม่ได้ไป after party ต่อเพราะเวลาอยู่เชียงใหม่มีจำกัด อย่างไรก็ตามผมจะพยายามกลับมาพูดที่เชียงใหม่ให้ได้ทุกปีครับ :)

May 18, 2013

วงที่ได้ดีจากการร้องคำเดิมซ้ำๆ

หลายเดือนก่อนเห็นทวีตนี้ผ่านตา



มานั่งนับๆ ดู เอ่อ มันก็จริงนะ 555+



จี้ จี้ จี้ จี้ เบบี เบบี เบี



โอะ โอะ โอ โอ๊ะ



รัน รั๊น รัน รั๊น รัน



แท๊กซี่ แท๊กซี่ แท๊กซี่ จุชชี่ จุชชี่ จุชชี่



ทร๊อบเบิล ทร๊อบเบิล ทร๊อบเบิล ชุชุ๊ชู ฮุฮุ๊ฮูท



เอคโค่ เอคโค่ เอคโค่



มาแระวา ~ มาแระวา ~ มาแระวา ~ มาแระวา



วั๊นทูทรี ~ วั๊นทูทรี ~ วั๊นทูทรี



บู๊ม บูม บู่ม



ดุ ดู ดุ่ ดุ ดู ดู้ว์ คิสสิงยูว์เบบี้



เกิร์ลบริงเดอะบอยส์เซาท์ เกิร์ลบริงเดอะบอยส์เซาท์ เกิร์ลบริงเดอะบอยส์เซาท์ เกิร์ลบริงเดอะบอยส์เซาท์



ไอก๊อทอะบอยโมจิน ไอก๊อทอะบอยชาคัง ไอก๊อทอะบอยแฮนซัมบอย





จริงๆ แล้วการร้องซ้ำคำ/ท่อนเดิมนี่ไม่ได้ผิดแปลกอะไรหรอก ในทฤษฎีดนตรีเราอาจเรียกว่ามันคือโมทีฟ ซึ่งเป็นส่วนที่โดดเด่นในเพลง เราจะใช้โมทีฟซ้ำๆ/ดัดแปลงมันด้วยวิธีต่างๆ เพื่อให้ส่วนนี้ติดหูคนฟัง ตัวอย่างที่ชัดเจนโน้ตง่ายๆ 4 ตัวใน Beethoven Symphony No.5 นั่นเอง

May 12, 2013

Code Jam 2013 รอบ 1B และ 1C

สั้นๆ ครับ ปีนี้ขยันน้อย สมองไม่ค่อยแล่น รอบ 1B และ 1C เลยไม่ได้ส่งซักข้อ Orz

แต่ก็ได้ไอ้นี่มาจากข้อ Pogo (ข้อ B จากรอบ 1C) แม้จะสวยดีแต่ก็ไม่ได้ช่วยตอบคำถามข้อนี้แต่อย่างใด 55+



update: ตอนแรกคิดอออกมาได้ตารางข้างบน แล้วก็เพิ่งมานึกออกว่า algo ผิดเพราะลืมไปว่าการเดินตกจุดเดิมซ้ำด้วย move ที่ไม่เท่ากันนั้นมี่ค่าต่างกันสำหรับการเดินรอบต่อไป เลยแก้ออกมาจนได้รูปด้านล่าง....... กลายเป็นว่าง่ายเลยข้อนี้ T T (เอา 38 คะแนนของตรูคืนมาาาา)


May 11, 2013

เซ็นทรัลลำปาง

เมื่อวานมีโอกาสได้แวะไปลำปางแบบด่วนๆ (ไปเย็นกลับค่ำ) เลยแวะสำรวจเล็กน้อย และพบว่ามันมาทรงเดียวกับเซ็นทรัลพิษณุโลกเลย เพียงแต่สั้นกว่า (มาก)



  • อย่างไรก็ดี เซ็นทรัลลำปางก็มีข้อดี (?) ที่เหนือกว่าเซ็นทรัลพิษณุโลกอย่างมาก คือตำแหน่งที่ตั้งนั้นอยู่ใจกลางเมืองพอดี แถมยังติดสถานศึกษาอย่างโรงเรียนลำปางกัลยาณีและบุญวาทย์วิทยาลัยแถวๆ นั้นอีก ก็น่าจะดึงคนได้เยอะดีพอสมควร
  • แต่คนกลับไม่เยอะอย่างที่คิด ขนาดเป็นวันศุกร์ตอนเย็น ที่จอดรถยังโล่งๆ คนน้อยกว่าเซ็นทรัลกาดสวนแก้วเชียงใหม่อีก (อาจเป็นเพราะสัปดาห์นี้หยุดยาว 3 วันก็ได้)
  • ที่งงคือลานจอดรถด้านหลังก็ค่อนข้างใหญ่ แต่ทำไมถึงมาจอดถนนใหญ่หน้าห้างกันเยอะแยะ หรือนี่เป็นเรื่องปรกติของคนลำปางกันไปแล้ว
  • ว่าด้วยเรื่องลักษณะอาคาร อย่างที่บอกไปแล้วว่ามันคือเซ็นทรัลพิษณุโลก ก็คือมีส่วนที่เป็นห้าง 3 ชั้น พร้อมชั้นที่คล้ายๆ จะเป็นชั้นใต้ดินไว้จอดรถอีก 1 ชั้น
  • โรงหนังที่นี่เป็น SF แฮะ สงสัยจะได้แวะเที่ยวบ่อยๆ ซะแล้ว :P
  • มี McDonald, KFC, Yamazaki, Swensens, Shabushi, Sukishi, Santafe, Jeffer แต่ไม่มี Sizzler
  • Starbucks ไม่มีคนเลย
  • แต่ iStudio คนแวะเข้าออกตลอด
  • ของน้อยมาก หา Lego ไม่ค่อยได้เลย เพลงคลาสสิกใน B2S ยิ่งไม่ต้องพูดถึง

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

เสียดายที่เชียงใหม่แย่งชิงคนไปได้หมด ซึ่งน่าจะเกิดขึ้นเพราะการตั้งมหาวิทยาลัยเชียงใหม่เมื่อกว่า 40 ปีก่อน ทำให้คนในวัยกำลังเริ่มทำงานเลือกที่จะตั้งหลักปักฐานใกล้กับมหาวิทยาลัยที่จบการศึกษา ด้วยเหตุผลทั้ง connection กับมหาวิทยาลัย, แรงงานจากรุ่นน้องที่ศึกษาอยู่, ความหนาแน่นของผู้คน ฯลฯ

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

Apr 27, 2013

Code Jam 2013 รอบ 1A

รอบนี้ตอนแรกว่าจะปล่อยผ่านเพราะอุตสาห์มากทม.ทั้งที อยากไปร่วมฟาร์มเสา 8 กับเหล่า agent ทั้งหลายด้วย

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

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

เราเริ่มจากสังเกตว่าวงกลมแรกจะใช้สีระบายไป 2r + 1 วงกลมถัดๆ มาใช้ 2r + 1 + 4 และ 2r + 1 + 8 ดังนั้นจะตั้งเป็นสมการได้ว่า

used_color(n) = (2r+1) + (2r+1 + 4) + (2r+1 + 8) + ... + (2r+1 + 4(n-1))
              = summation (2r+1 + 4k) for k in [0 .. n-1]
              = n(2r+1) + 4 * summation k for k in [0 .. n-1]
              = n(2r+1) + 4n(n-1)/2
              = n(2r+1) + 2n(n-1)

แต่เนื่องจากสิ่งที่เรารู้คือ t = used_color(n) และต้องการคำนวณกลับเพื่อหา n ดังนั้น

0 = n(2r+1) + 2n(n-1) - t
  = 2n^2 - (2r-1)n - t

ก็จะได้ว่า

a = 2
b = 2r - 1
c = -t
n = (-b ± sqrt(b^2 - 4ac)) / 2a

ถึงตอนนี้ก็แค่แก้สมการข้างบน ได้คำตอบมา 2 อันก็เลือกเอาคำตอบที่มากกว่า 0 แล้วปัดเศษทิ้งครับ



ข้อสองคิดวิธี optimize ไม่ออก เลยเขียน recursive ไป (loop ใหญ่สุดของแบบเล็กคือ 60 ล้าน ก็ยัง brute-force ออกในเวลาที่รับได้) แต่เสียดายที่คิดผิด debug ไม่ทัน

ส่วนข้อสามแนวคิดคือหาตัวคูณร่วมน้อยของเลขทั้ง set ที่เค้าให้ แล้วก็แยกตัวประกอบมันออกมาเป็น list นึง ทีนี้ถ้า list มันใหญ่กว่าขนาดของการ์ดทั้งหมดก็ merge ตัวเลขใน list นี้ด้วยการคูณจนกว่ามันจะมีขนาดเท่ากับการ์ด เท่านี้เอง (อันนี้ออกแค่ข้อเล็ก)

อันดับระหว่างแข่งคือ 18xx พอจบรอบตรวจคะแนนจริงก็เด้งกลับมาที่ 1377 เพราะข้อที่ส่งไปถูกหมด (แต่ก็ยังไม่เพียงพอสำหรับผ่านเข้ารอบต่อไปอยู่ดี) ... ไม่เป็นไรรอบหน้าเอาใหม่ เนอะ ^^)v

Apr 14, 2013

Code Jam 2013 รอบคัดเลือก


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

ปีนี้ความตั้งใจแรกคือจะพยายามเขียนส่งให้ได้หลายๆ ภาษา คือฝึก ML, Lisp, Bash อะไรพวกนี้จนคิดว่าน่าจะคล่องพอเอามาใช้แก้โจทย์ปัญหาจริงๆ ได้แล้ว น่าเสียดายที่รับ input ไม่เป็น เลยกลับมาตายรังด้วย Python เหมือนเดิม (แต่ก็มี Haskell โผล่มาครึ่งข้อ) ไม่แน่ใจว่ารอบต่อๆ ไปจะเขียน C++ ทันหรือเปล่า เพราะโดนจำกัดเวลาเหลือแค่ 2.5 ชั่วโมงแล้ว

ข้อแรกโจทย์ extended เกม OX ให้ใหญ่ขึ้นเป็นตาราง 4x4 แถมยังอนุญาตให้มี wildcard (เป็นได้ทั้ง X และ O) อย่างมาก 1 ที่บนกระดาน ก็ถามว่าเกมตอนนี้ใครชนะ หรือว่าเสมอ หรือว่ายังเล่นไม่จบ

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

1 2 3      7 4 1
4 5 6  =>  8 5 2
7 8 9      9 6 3

จะสามารถ reuse code ส่วนที่เช็คแนวนอนมาเช็คแนวตั้งได้ด้วย เช่นเดียวกับ code เช็คแนวทะแยง การ implement แค่ส่วนนี้คงมีวิธีเจ๋งๆ ให้เลือกใช้เยอะอยู่ ส่วนผมที่มาจากสาย functional เลือกใช้ transpose . reversed ครับ

def transpose(grid):
    return [''.join(line) for line in zip(*grid)]

def chk_hori(grid):
    for line in grid:
        if all(c in 'XT' for c in line):
            return 'X won'
        if all(c in 'OT' for c in line):
            return 'O won'

def chk_diag(grid):
    if all(grid[i][i] in 'XT' for i in range(len(grid))):
        return 'X won'
    if all(grid[i][i] in 'OT' for i in range(len(grid))):
        return 'O won'

def chk_won(grid):
    for _ in range(2):
        for chk in [chk_hori, chk_diag]:
            answer = chk(grid)
            if answer:
                return answer
        grid = transpose(reversed(grid))

def chk_full(grid):
    return not any('.' in line for line in grid)

for case in range(int(input())):
    grid = [input() for _ in range(4)]
    input()
    answer = chk_won(grid)
    if not answer:
        answer = 'Draw' if chk_full(grid) else 'Game has not completed'
    print('Case #{}: {}'.format(case+1, answer))



ข้อถัดมาถามว่าสามารถใช้เครื่องตัดหญ้าอัตโนมัติ (ที่ดันวิ่งตรงไปข้างหน้าได้อย่างเดียว) ตัดหญ้าให้แต่ละช่องมีความสูงตาม pattern ที่วางไว้ได้หรือเปล่า

พบว่า algorithm ง่ายกว่าที่คิด คือดูว่าแต่ละจุดบนสนามหญ้านั้น ต้องมีความสูงเท่ากับความสูงหญ้าที่สูงที่สุดในแนวนอนหรือแนวตั้งเท่านั้น เพราะถ้าเกิดมีจุดที่สูงกว่าทั้งแนวนอนและแนวตั้งพร้อมกัน จะทำให้ไม่สามารถตัดหญ้าในช่องนั้นให้ต่ำกว่าค่าสูงสุดได้

def transpose(lawn):
    return [list(line) for line in zip(*lawn)]
    
def beautiful_garden(lawn, n, m):
    tlawn = transpose(lawn)
    def chk(y, x):
        return lawn[y][x] in [max(tlawn[x]), max(lawn[y])]
    return all(all(chk(y, x) for x in range(m)) for y in range(n))
            
for case in range(int(input())):
    n, m = [int(i) for i in input().split()]
    lawn = [[int(i) for i in input().split()] for _ in range(n)]
    answer = 'YES' if beautiful_garden(lawn, n, m) else 'NO'
    print('Case #{}: {}'.format(case+1, answer))



ข้อที่ 3 ให้ว่ามีเลข palindrome ที่รากที่สองของมันก็ยังเป็น palindrome ด้วยทั้งหมดกี่ตัวในช่วงที่กำหนด ซึ่งมีความรู้สึกว่าถ้าเล่นกับ palindrome แล้ว Haskell จะสวยงามมาก แต่ก็ไปตายที่ test กลางเลยกลับไปใช้ Python แทน

import Data.List.Split (splitOn)

boolAsNum b = if b then 1 else 0

isSquare x = root^2 == x
    where root = sqrt x

isPalindrome x = y == reverse y
    where y = show x

countFair x y
    | x > y     = 0
    | otherwise = (boolAsNum $ all isPalindrome [x,x^2]) + countFair (x+1) y

eachLoop nosLoop = do
    raw <- getLine
    let rawSqrtNum = [sqrt $ read x | x <- splitOn " " raw]
        [start, stop] = [f x | (f,x) <- zip [ceiling, floor] rawSqrtNum]
    putStrLn $ "Case #" ++ (show nosLoop) ++ ": " ++ (show $ countFair start stop)

main = do
    allLoop <- getLine
    sequence_ [eachLoop n | n <- [1..read allLoop]]

ส่วนนี่คือ Python ที่ optimize ไปนิดหน่อยสำหรับความยากปานกลาง โดยส่วนที่วนสร้างเลข palindrome ตัวถัดไปยังไม่ได้ optimize สำหรับโจทย์นี้โดยเฉพาะเลย (ว่าแล้วก็ต้องเขียนเก็บเข้า lib ตัวเองซะแล้ว)

odd = lambda x: x % 2 == 1
square = lambda n: int(n ** 0.5) ** 2 == n
palindrome = lambda n: str(n) == str(n)[::-1]

def int_sqrt(n):
    if n == 0: 
        return 0
    a, b = divmod(n.bit_length(), 2)
    x = 2 ** (a + b)
    while True:
        y = x + n // x 
        y //= 2
        if y >= x: 
            return x
        x = y

def next_palindrome(n):
    s = str(n)
    size = len(s)

    fst = s[:size//2+1] if odd(size) else s[:size//2]
    lst = s[-size//2:]

    size_old_fst = len(fst)
    if fst <= lst[::-1]:
        fst = str(int(fst) + 1)

    lst = fst[:-1] if len(fst) > size_old_fst else fst
    return int(fst[:-1] + lst[::-1]) if odd(size) else int(fst + lst[::-1])

def palindrome_range(start, stop):
    while start < stop:
        if palindrome(start):
            yield start
        start = next_palindrome(start)

for case in range(int(input())):
    start, stop = [int(n) for n in input().split()]

    start = int_sqrt(start) + (0 if square(start) else 1)
    stop = int_sqrt(stop)

    answer = sum(palindrome(pal**2) for pal in palindrome_range(start, stop+1))
    print('Case #{}: {}'.format(case+1, answer))



ส่วนข้อ 4 ไม่ได้ทำ เพราะสังเกตเห็น pattern ของข้อ 3 เลยกะจะแก้ความยากระดับโหดสุด (input ใหญ่ได้ถึง 10^100) แต่ลอง optimize ดูแล้วทำทันแค่ 10^80 เท่านั้น ก็เลยต้องตัดใจไปตามระเบียบ แต่แต้มแค่นี้ก็เพียงพอสำหรับรอบ 1 แล้วครับ ;)

Apr 8, 2013

TCDC เชียงใหม่

tl; dr มันคือห้องสมุดฮายโซวสำหรับ designer นั่นเองครับ



วิธีการเดินทางก็ไม่ยาก ถ้ามาจากแจ่งหมูกะทะ (แจ่งศรีภูมิ) ให้ไปทางริมแม่น้ำปิง (ทางที่จะไปเจดีย์ขาว) พอถึงอีกไฟแดงนึงก็เลี้ยวซ้ายเลย TCDC อยู่ซ้ายมือครับ

ที่น่าทึ่งก็คือใน Google Street View ยังไม่มีอาคารนี้เลย นับว่าสร้างได้เร็วมากๆ (แต่มองจาก satellite mode เห็นหลังคาแล้วนะ)



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

ส่วนนี่เป็น model งานออกแบบอาคาร TCDC แบบอื่นๆ ที่ไม่สามารถนำมาสร้างจริงได้ แต่ก็สวยใช่เล่น



ด้านพื้นที่ใช้สอย ชั้นล่างสุดเป็นส่วนแสดงนิทรรศการ ไปคราวนี้เจองาน "เล่าเรื่อง เมืองใหม่ Chiang Mai Revisited" โดยเน้นเขตเศรฐกิจอย่างย่านนิมมานเหมินทร์ที่เปลี่ยนไปมากในช่วงไม่กี่ปีที่ผ่านมานี้ครับ



ส่วนชั้นสองเป็นห้องสมุด designer ที่ไม่ได้มีแต่หนังสือศิลปะอย่างเดียว แต่ยังจัดบริเวณที่เป็น material lib ไว้ด้วย (ตัวอย่างของ material แบบต่างๆ ที่นิยมใช้ในงานออกแบบเช่น เนื้อผ้า กระเบื้อง กระดาษ กระจก -- ห้ามถ่ายรูปแล้ว) ซึ่งส่วนตัวผมว่ามันยังน้อยไปหน่อย จริงๆ แล้วเอาหนังสือออกให้ไปหมดเพื่อแสดงแต่ material เลยก็น่าจะได้นะ?

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

Apr 7, 2013

Bit Flag

ในภาษาสมัยใหม่ เราคงคุ้นเคยกับการเขียนฟังก์ชั่น (และเรียกใช้) แบบนี้

def foo(n, m, a=False, b=False, c=False):
    if a:
        n += 42
    if b:
        n *= 7
    if c:
        n **= 2
    return n % m

...

foo(5, 99)
foo(5, 99, a=True)
foo(5, 99, c=True)
foo(5, 99, c=True, b=True, a=True)

โชคร้ายที่ระบบ keyword argument ไม่ได้เป็นแบบนี้ทุกภาษา อย่างเช่นใน PHP ถ้าจะเปิดตัว flag c เพียงตัวเดียว ก็ยังคงต้องบอกว่า a, b ถูกปิดอยู่ด้วย (ต้องบอกสถานะของ flag ทุกตัวที่อยู่ก่อนหน้า c)

ทางออกง่ายๆ แต่ทำให้ syntax ดูรุงรังหน่อยคือการให้ argument ส่วนที่เป็น flag ใช้ datatype แบบ hash table เช่นนี้

function foo($n, $m, $options=array()) {
    if (array_key_exists('a', $options) && $options['a'])
        $n += 42;
    if (array_key_exists('b', $options) && $options['b'])
        $n *= 7;
    if (array_key_exists('c', $options) && $options['c'])
        $n *= $n;
    return $n % $m;
}   

...

foo(5, 99);
foo(5, 99, array('a' => true));
foo(5, 99, array('c' => true));
foo(5, 99, array('c' => true, 'b' => true, 'a' => true));

ทางออกที่คลาสสิก (แต่ปวดหัว) กว่าคือการใช้ bit flag แบบนี้

const int A = 0x01;
const int B = 0x02;
const int C = 0x04;

int foo(int n, int m, int options) {
    if (options & A)
        n += 42;
    if (options & B)
        n *= 7;
    if (options & C)
        n *= n;
    return n % m;
}

...

foo(5, 99, 0);
foo(5, 99, A);
foo(5, 99, C);
foo(5, 99, C|B|A);

หลายคนอาจบอกว่าการใช้ bit flag นั้นมีข้อดีเหนือกว่า keyword argument ตรงที่สามารถ store สถานะของ flag เก็บไว้ใน data ตัวหนึ่ง (เพื่อที่จะนำไปใช้ซ้ำในที่อื่น) ซึ่งข้อได้เปรียบนี้ไม่เป็นความจริงเลยสำหรับภาษาที่แตก array, hash table ลงไปเป็น argument ได้

args = [5, 99]
flags = {'b': True, 'c': True}

foo(*args, **flags) == foo(5, 99, b=True, c=True)

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

Mar 12, 2013

42

เนื่องจากเมื่อวานเป็นวันคล้ายเกิดของ Douglas Adams ซึ่งถ้าเขามีชีวิตอยู่ก็จะอายุขึ้นปีที่ 61 แล้ว Google เลยทำ Doodle รำลึกถึงนักเขียนนิยายชื่อก้องคนนี้ให้ได้เล่นกันครับ


รายละเอียดเล็กน้อยๆ ที่เก็บได้จากยานหัวใจทอง ยานอวกาศหลักของเรื่องที่สามารถเดินทางทะลุข้ามมิติจักรวาลได้ด้วยหลักการของความไม่น่าจะเป็น

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

และนี่คือเรื่องราวต่างๆ ที่สามารถอ่านได้จากหนังสือคู่มือนักโบก

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

Mar 2, 2013

Swap ตัวแปร

โปรแกรมที่เล็กและง่ายรองจาก hello world ที่โปรแกรมเมอร์หลายคนต้องผ่านตามาบ้าง คือโปรแกรม (ฟังก์ชัน) สำหรับสลับค่าตัวแปรนั่นเอง

แต่ก่อนจะลงลึกที่รายละเอียด มาดู simple yet best practice ใน Pyhton กันก่อน :P

a, b = b, a



อธิบายก่อนว่าการเข้าถึง data ในคอมพิวเตอร์ จะคล้ายๆ กับคนที่มีแขนข้างเดียว คือหยิบของได้ทีละ 1 อย่าง ไม่สามารถหยิบของพร้อมกัน 2 อย่างแล้ววางสลับที่กันทันทีได้เหมือนในชีวิตจริง

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

จะอธิบายให้คอมพิวเตอร์เข้าใจได้ ก็ต้องบอกด้วยว่าที่ว่างนั้นคือตรงไหน (พูดลอยๆ ไม่ได้ เดี๋ยวกลับไปหาไม่เจอ) เช่นนี้

int t;

t = a;
a = b;
b = t;

ปัญหาของการย้ายแบบนี้ คือมันเป็นการมองในเชิง object โลกมนุษย์ ซึ่งไม่ได้ใกล้เคียงกับกิจกรรมที่เกิดขึ้นใน memory เลย มันไม่ใช่การย้ายของด้วยซ้ำ แต่เป็นการทำสำเนาสิ่งของแล้วย้ายที่สลับไปมาต่างหาก

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

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

a = a + b;
b = a - b;
a = a - b;

แบบนี้ก็เกือบดีแล้ว แต่ยังมีข้อควรระวังคือ overflow เนื่องจากมันเป็นวิธีทางคณิตศาสตร์ แถมโปรแกรมแบบนี้ก็ยังไม่ค่อยสวยงามเท่าไหร่ด้วย

ถ้าจะมองให้เป็นไปในทางคอมพิวเตอร์จริงๆ ต้องเปลี่ยนการดำเนินการ +, - ไปใช้ xor แทน เช่นนี้

a ^= b;
b ^= a;
a ^= b;

ซึ่งสามารถลดรูปได้อีกขั้นจนเหลือเพียงบรรทัดเดียวว่า

a ^= b ^= a ^= b;

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

process [1, 2]:
  goto address [a, b]
  read into process memory
  sync with process [2, 1]
  goto address [b, a]
  write from process memory



ส่วนภาษาเชิง functional ที่ไม่ยอมให้มี mutable อย่างการสลับเปลี่ยนค่าตัวแปร ก็ยังสามารถทำท่านี้ได้โดยใช้เทคนิคตั้งชื่อตัวแปรสลับที่กันใน environment ที่ต่างกัน (ภาษาเค้าเรียกว่า Monad) ต่อไปนี้คือตัวอย่างใน Haskell

do (a,b) <- return (b,a)
   someMagic a b
   ...

หรือแบบนี้ใน Lisp (สังเกตการใช้คำสั่ง let เพื่อสลับตัวแปร)

(let ([a b]
      [b a])
  (magic-happens a b ...))



สุดท้ายนี้กลับไปดู Python ที่เรียบง่ายอีกรอบ แท้จริงแล้วมันคือการสั่ง

container = b, a
a, b = container

แปลได้ว่า แรกสุดมันจะเก็บค่าตัวแปร b และ a ตามลำดับลง container ชนิดหนึ่ง (ใน Python มันคือ tuple) แล้วหลังจากนั้นจึงดึงตัวแปรออกจาก container นี้มาให้ตัวแปร a และ b ตามลำดับ แนวคิดแบบนี้ค่อนข้าง general มาก และสามารถนำไปใช้กับกรณีที่มีตัวแปรให้สลับเยอะกว่านี้ได้ด้วย เช่น

a, b, c, d = d, -b, -c, a

Feb 25, 2013

ลำดับ Farey และ วงกลม Ford

ปีที่แล้วเล่น Project Euler ไปได้หลายข้อ แล้วก็สะดุดตากับข้อที่น่าสนใจ เลยเอามาขยายผลต่อจนได้เป็นเรื่องนี้ขึ้นมา

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

อย่างไรก็ดี ถ้านิยามอย่างนี้เราจะมีปัญหาว่าลำดับ Farey มันดันเป็นลำดับอนันต์ซะหนิ ดังนั้นเลยเพิ่มข้อจำกัดไปว่า ลำดับ Farey อันดับ n จะบรรจุเศษส่วนอย่างต่ำ ที่ตัวส่วนมีค่าน้อยกว่าเท่ากับ n เท่านั้น

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

ส่วนวงกลม Ford ก็เป็นวงกลมที่นำเอาลำดับ Farey ไปวาดแสดงผลได้อย่างสวยงาม ;)

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

ปล. blog ตอนนี้ เขียนเป็นพิเศษให้ @flurrywong @FordAntiTrust ฮะ <3

วาเลนไทน์

วาเลนไทน์ที่ผ่านมาไปสอบสัมภาษณ์ป.โท วิศวะคอม มก. (อาจารย์ที่สอบสัมภาษณ์เค้าก็เข้าใจเลือกวันเนาะ)

ตกใจนิดหน่อยที่สอบเข้าได้ที่ 1 (ได้ 69%) ทั้งที่ไม่ได้อ่านหนังสือสอบเลย อาจารย์บอกว่าได้คะแนนคณิตศาสตร์เยอะดี สมกับที่เรียนคณิตศาสตร์มา 555+

  • คณิตศาสตร์: 18/25 => 21% จาก 30% เห็นตอนแรกบอกจะมี 50 ข้อ เปิดเจอแค่ 25 ข้อค่อยโล่งใจหน่อย
  • ภาษาอังกฤษ: 32/50 => 19% จาก 30% เสียดายที่ทำไม่ทัน ทิ้งดิ่งเยอะมาก
  • ความรู้คอมฯ: 3x/50 => 29% จาก 40% คำถามเชิงลึกกระจายหลายหัวข้อมาก มาอ่านเอาสัปดาห์เดียวไม่น่าได้

แต่เรื่องสอบอ่ะ มันไม่ได้ดีใจเท่าของรางวัลที่ได้ในวันรุ่งขึ้นหรอก <3


Jan 2, 2013

ทำ Optional Argument แบบ LaTeX

command ใน LaTeX นั้นมีวิธีเรียกใช้ที่ต่างจากภาษาโดยทั่วไปอยู่บ้าง ลองดู
\sqrt{x}
\sqrt[3]{x}
อันด้านบนจะวาด √x (รากที่สองของ x -- โดยปรกติ รากที่สองไม่จำเป็นต้องมีเลข 2 กำกับ) ส่วนอันล่างจะวาด ∛x

รูปร่างของ syntax ที่น่าสนใจคือ ส่วนที่เป็น optional argument จะถูกเรียกขึ้นมาก่อน main argument แถมยังใช้วงเล็บแยกกันอีก นี่ทำให้การ currying เป็นไปได้อย่างง่ายดาย
\newcommand{\cbrt}{\sqrt[3]}
...
\cbrt{x}
จริงๆ แล้วทาง math ก็มีฟังก์ชันในแนวคิดนี้อยู่เยอะ อย่างเช่น σx(n) ซึ่งเขียนแบบนี้แล้วเข้าใจง่ายกว่า σ(n, x) และยังไม่สับสนอีกว่า argument ตัวไหนที่ควรเป็น n หรือ x กันแน่

แล้วถ้าอยากได้แบบนี้ใน Python บ้าง? ง่ายนิดเดียวเพราะใช้เทคนิคเดียวกับ Infinite List นั่นเอง
@singleton
class sigma:
    def __call__(self, n, x=1):
        if n == 1:
            return 1
        return product( sum((k**x)**i for i in range(v+1))
                            for k, v in Counter(factor(n)).items() )
    def __getitem__(self, x):
        def partial_sigma(n):
            return self(n, x)
        return partial_sigma
คราวนี้จะเขียน
6 == sigma[1](6) - 6
หรือกระทั่ง
tau = sigma[0]
...
[tau(i) for i in range(return 1, 10)]
ก็ย่อมได้

Jan 1, 2013

ปี 2012 ของฉัน

1 ปีนี่ผ่านไปเร็วจริงแฮะ สงสัยว่าเป็นปีที่ไม่ค่อยผาดโผนอะไรมากด้วย สรุปออกมาได้แค่

  • เว็บ Tutor0x ตอนนี้ทะลุ 200 like แล้ว (วันสุดท้ายก่อนปีใหม่พอดี)
  • ไป BarCamp เยอะมาก ทั้ง เชียงราย (ม.ค.), เชียงใหม่ (ม.ย.), บางเขน (ก.ย.), กรุงเทพ (ต.ค.)
  • เล่น Project Euler โจทย์มันโหดดี แถมยังช่วยให้คิดหัวข้อสัมมนาออกง่ายๆ เลยด้วย
  • Linux ทั้งตัว เพราะโดนกาต่ายยักษ์แกล้ง TwT
  • Python Magic เล่นแล้วรู้สึกเป็นหมอผีเพิ่มขึ้นมาเยอะเลย 555+ (ตัวอย่าง: Infinite List, Callable Num)

ส่วน achievement คืนข้ามปีคือดูหนังโรงฮะ (Wreck-It Ralph)