Dec 14, 2012

9/3(2+1) = ?

เวลามีคนถามว่า

9/3(2+1) = ?

จะตอบโดยไม่ลังเลเลยว่า 1 แถมถ้าใครมาบอกว่าต้องคิดหารทางด้านซ้ายมือ แล้วค่อยมาคูณแบบคอมพิวเตอร์นะ จะตอบกลับไปเลยว่ามัน syntax error โว้ย ภาษาคอมพิวเตอร์ที่ไหนเค้ายอมรับการเขียนเลขติดกันว่าเป็นการคูณเลขบ้าง?

อนึ่ง ถ้ามองว่าการเขียนเลขติดกัน (คั่นด้วยวงเล็บ) เป็นการคูณจริงๆ ตัวที่ติดกับวงเล็บก็ต้องมี precedence สูงกว่า (ทำงานก่อน) เครื่องหมายหารอยู่แล้ว นั่นหมายความว่าต้องคิดส่วน 3(2+1) ให้เสร็จก่อนเอาไปคำนวณต่ออยู่ดี

เจอบ่อยๆ ก็เริ่มขี้เกียจอธิบาย เลยเขียนโปรแกรมที่มันแปลสมการด้านบนนี้ได้ถูกต้องเลยละกัน -> จิ้มโลด

ตัวอย่างผลลัพท์จากโปรแกรม

>>> 9/3(2+1)
1.0
>>> a, b, c, d = 48, 2, 9, 3
>>> a/b(c+d)
2.0
>>> 10(9(8(7(6(5(4(3(2(1)))))))))
3628800
>>> (1+2j)(3-4j)
(11+2j)
>>> 1 + 1/phi
1.618033988749895
>>> phi(43)
42

อันที่จริงก็คิดว่าจะทำมาได้ซักพักละ แต่ด้วยความที่ขี้เกียจวางกฎ grammar ด้วย flex bison ทั้งที่หลายๆ กฎนั้นภาษาส่วนใหญ่ก็ทำไว้ให้แล้ว เลยดองมาเรื่อยๆ จนทำ Infinite List เสร็จ และไปเจอ Python's Magic Methods เลยได้ฤกษ์เริ่มจากตรงนั้น

แนวคิดก็ง่ายนิดเดียว แค่เพิ่มเมธอด __call__ เข้าไปให้ class ของตัวเลข (เป็น Monkey Patching เหมือนใน Ruby) โดย define มันกว่าถ้าเรียก a.__call__(b) แล้วจะมีค่าเท่ากับเอามันมาคูณกันซะ

ปัญหาที่ต้องไล่เก็บก็คือ
  1. Python ไม่อนุญาติให้แก้ไขพวก builtins -- ที่จริงปัญหานี้ก็เหมือนกับคราวที่ทำ Infinite List นั่นแหละ ซึ่งแก้ไม่ยาก แค่ inherit class นั้นๆ ออกมาแล้วไล่ define magic method ซะ
  2. ปัญหาจริงๆ คือ Python parser มันจะแปลตัวเลขไปเป็น class ตัวเลขใน builtins เท่านั้น ดังนั้นก็ต้องแปลง token เองโดยหาว่าตรงไหนคือเลข แล้วก็เอา class ตัวเลขที่ inherit มาแล้วครอบไว้
  3. งานนี้ได้โมดูล tokenize มาช่วยแปลตัวเลขให้ ไม่ต้องเขียน regex เอง เพราะมันจะมีท่าประกาศตัวเลขประหลาดๆ ทำให้ tokenize เองพลาดได้
  4. ที่เหลือก็เอา code ที่แก้ token เรียบร้อยไปทำงาน ตรงนี้ดึงโมดูล code ที่ทำทุกอย่างเตรียมไว้แล้วมาใช้ฟังก์ชันเดียวจบ
  5. แต่ตอนที่เอา tokenize มาใช้ร่วมกับ code จะมีปัญหาที่พิมพ์ได้ทีละบรรทัด เนื่องจาก tokenize มันปรับแต่งอะไรไม่ได้เลย ก็ต้องหลอกมันเอานิดหน่อย
  6. ครบถ้วนกระบวนความแล้วก็ clean up โดยจุดที่เอา code ออกไปได้เยอะมากๆ คือส่วน define magic method อันนี้ใช้ meta-programming บอกแค่ว่าจะเพิ่ม method ไหนบ้างก็พอ แล้วปล่อยให้มัน generate ตัวเองไปซะ
  7. อยู่ๆ ก็เขียน function decorator เป็นเฉยเลย งงตัวเอง 555+
อย่างไรก็ตาม แนวคิดของ callable number นี้ไม่ควรเอาไปใช้เขียนโปรแกรมในโลกจริง เพราะนักคณิตศาสตร์ชอบตังชื่อเดียว แต่ใช้ในคนละบริบท เช่น φ (phi) ที่อาจหมายถึง golden radio หรือ Euler's totient ก็ได้ ซึ่งแม้ว่าทั้ง 2 รูปของ φ นี้จะไม่ overlap กัน (เช่น 1+1/φ จะถูกมองว่าเป็นตัวเลข ในขณะที่ φ(43) คือการใช้ฟังก์ชัน) แต่ด้วยความที่ Python เป็น 1st class function ที่ยอมให้เราส่งผ่านฟังก์ชันเป็นตัวแปรของฟังก์ชันอื่นๆ ได้ ดังนั้นถ้าเราเจออะไรอย่าง δ(φ,ε) เราจะไม่สามารถแน่ใจได้เลยว่า φ ในบริบทนี้คืออะไรครับ

Dec 11, 2012

เซ็นทรัลพิษณุโลก

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

สรุปเลยว่ามันคือเซ็นทรัลลาดพร้าวแบบเล็กลงมาหน่อย หรือถ้าใครเคยไปเซ็นทรัลเชียงรายคงบอกว่าเป็นฝาแฝดแน่ๆ


  • ตำแหน่งที่ตั้งห้าง (เข้าใจว่า) ตั้งอยู่ชานเมือง (ไม่เหมือน Big C ที่รายล้อมไปด้วยหมู่บ้านร้านค้าต่างๆ)
  • แต่ก็ยังดีที่อยู่บนเส้นหลัก พิษณุโลก-ตาก (AH16) เข้าใจว่าคนสุโขทัยที่ไม่เหลือโรงหนังแล้วต้องมาเที่ยวที่นี่
  • ตัวอาคารเป็นทรงแท่งยาวๆ และมีแต่ลานจอดรถที่สับสนในชีวิตมาก เพราะลานจอดรถให้อารมณ์อยู่บนเกาะแยกออกมาจากตัวห้าง มีสะพานเข้า-ออกแค่ไม่กี่จุด
  • หน้าห้างเป็นลานโล่งสำหรับจัดกิจกรรมเหมือนที่เซ็นทรัลเวิร์ด แต่ก็ไม่ใหญ่เท่าไหร่เพราะแบ่งพื้นที่ไปจัดสวนหย่อม
  • มีชั้นหลัก 3 ชั้นโดยเริ่มที่ชั้น 1 (ไม่มีชั้นใต้ดิน) อันนี้เป็นปัญหาสำหรับคนที่กะเดินเที่ยวให้ครบรอบ เพราะมันจะต้องเดินซ้ำชั้นไป 1 ชั้นเต็มๆ
  • มีชั้น (กึ่ง) ใต้ดินเล็กๆ 1 ชั้น (ร้านเล็กๆ ~5 ร้าน) เปิดประตูออกไปเจอลานจอดรถ (กึ่ง) ใต้ดินขนาดเล็กสำหรับรถ VIP / จอด 30 นาที
  • ลานโล่งๆ ในห้างมีอยู่ 3 ลาน วันที่ไปมีลานนึงจัดงานคริสต์มาส ลานนึงเป็นเลหลังของ B2S ส่วนอีกลานไม่มีงาน
  • ห้องน้ำให้อารมณว่าเป็นอาคารแยกออกมาจากตัวห้าง ตกแต่งสวยไม่แพ้ T21 แค่ไม่มี theme หลากหลายเท่า
  • โรงหนังเป็น Major Cineplex อยู่ชั้น 4 สุดด้านตึก (ชั้นพิเศษสำหรับโรงหนัง) และมีหนังฉาย 5 โรง ราคาบัตรอยู่ที่ 140.-
  • หนังมีให้เลือกน้อยไปหน่อย หนังดังๆ ช่วงนี้อย่าง Cloud Atlas กลับไม่มีให้ดู แถมพนักงานยังปิดเครื่องฉายตอนขึ้นเครดิทหนัง - -"
  • ข้างๆ โรงหนังเป็น Fun Planet มี Jubeat อยู่ 4 ตู้ (ออฟไลน์ไปแล้ว) Pump It Up อีก 2 ตู้ ถือว่าฮายโซวกว่าเชียงใหม่เยอะ
  • ชั้น 3 มีแต่ร้านของกิน -- ไม่มี Sizzler, CoCoIchibanya (ยังดีมี Shabushi) สุดด้านตึกเป็น B2S
  • ชั้น 2 เป็นพวก IT และหนังสือ อันนี้จำไม่ได้ว่าสุดด้านตึกเป็นอะไร
  • ชั้น 1 เป็นเสื้อผ้า สุดด้านจะมี McDonald, KFC, Swensen's เปิดข้างๆ กัน ถัดไปอีกหน่อยก็ Tops
  • ส่วนร้านขายของจากทางห้าง (อยู่สุดด้านตึกอีกทางนึง) เป็น Robinson ก็พวกชุดเครื่องครัว เสื้อผ้า ชุดชั้นใน ของเล่นแพงๆ นั่นแหละ
  • คนไม่ค่อยเยอะเท่าที่คิด อาจเพราะไปตอนเย็นแล้ว หรือว่ายังตี Big C ไม่แตกก็ไม่รู้นะ

Nov 30, 2012

เปลี่ยนค่าตัวแปรใน Haskell

ใน ghci ถ้าเราลองทำอย่างนี้
let x = 10
let x = x + 1
กด Enter แล้วจะพบ prompt รอทำงานต่อ ถึงตอนนี้อาจคิดว่าไม่มีปัญหาหนิ แต่อย่าลืมว่า Haskell เป็น lazy language ดังนั้นต้องเรียก x ออกมาดูค่าด้วย

... แล้วเราก็จะพบกับความว่างเปล่า เฮ้ยทำไมแค่บวกเลขง่ายๆ มันถึงคิดช้าจังฟระ

นั่นเป็นเพราะว่ารูปด้านบนนี้เป็น recursive ตอนที่จะ eval ค่า x + 1 ด้านขวามือออกมา มันจะไปเรียกดู definition ของ x ซึ่งก็คือ x + 1 นั่นเอง (ลืมไปได้เลยว่าเราเคยบอกให้ x = 10)

อย่างไรก็ตาม ถ้าเราเปลี่ยนไปเขียนแบบนี้
let x = 10
let y = x + 1
let x = y
เวลาเรียก x เราจะได้คำตอบที่ถูกต้องคือ 11 แล้วครับ



แต่ถ้าทำท่าข้างบนนี้ มันก็คงบาปพอๆ กับ
word = "abracadabra"
for i in range(len(word)):
    print(word[i])
ใน Python นั่นแหละ :P



ดังนั้น Haskell จะเลี่ยงไปใช้ Monad แทน
x <- return 10
x <- return (x + 1)
ดูเผินๆ เหมือนว่าเราจะเปลี่ยนค่าตัวแปร x ได้ แต่ที่จริงแล้วสองบรรทัดข้างบนนี้จะเทียบเท่ากับ
return 10 >>= \x -> return (x + 1) >>= \x -> return x
นั่นหมายความว่า แรกสุดเราเอาค่า 10 ส่งให้ฟังก์ชั่นที่รับตัวแปร 1 ตัว โดยฟังก์ชั่นนี้อ่านชื่อตัวแปรที่รับมาว่า x แล้วจึงคำนวณ x + 1 เรียบร้อยแล้วก็ส่งต่อค่าที่คำนวณได้ไปให้ฟังก์ชั่นอีกตัว ที่รับตัวแปร 1 ตัวและอ่านชื่อตัวแปรนั้นว่า x เช่นกัน

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

Nov 21, 2012

สวัสดีโลก!

สืบเนื่องจากวันนี้เป็นวัน World Hello Day ที่แนะนำให้ออกไปทักทายกับคนอื่นๆ อย่างน้อย 10 คน ... พอดีเห็นชื่อกิจกรรมมันสวยดี เลยอยาก say hello บ้าง

เริ่มดัวยทักทายกับ C

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

จะเห็นว่า C เป็นภาษาที่มี boilerplate เยอะพอสมควร แต่ก็น่าจะแพ้ภาษาถัดมาอย่าง Java

public class IJustWannaSayHelloWhyINeedToNameThis {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

ส่วน C++ นั้นจะต่างออกไป ตรงที่มองข้อความที่จะพิมพ์ในรูปของ stream

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

ย้ายมาดูฝั่งภาษาขั้นสูง ซึ่งเป็นที่นิยมสำหรับคนทำ web อย่าง PHP บ้าง

<?='Hello, world!'?>

สั้นดี แต่ถ้าไม่รู้จักกันมาก่อนนี่คงงงไปพักใหญ่ เทียบไม่ได้กับ Python ที่เน้นเรื่อง readability สุดๆ

print 'Hello, world!'

ลองมาดูภาษาที่หลายคนคงมึนใน syntax อย่าง Common LISP

(write-line "Hello, world!")

นอกจากจะจัดวงเล็บประหลาดแล้ว ชื่อฟังก์ชันยังประหลาดกว่าชาวบ้านเค้าด้วย ... แต่นี่ก็คงไม่ประหลาดเท่าแนวคิดที่ว่า ตัวแปรไม่สามารถเปลี่ยนค่าได้ใน Haskell

main = do
    putStrLn "Hello, wolrd!"

อันที่จริง ภาษาขั้นสูงก็ควรจะช่วยโปรแกรมเมอร์ให้ทำงานได้ง่ายขึ้นอยู่แล้ว เพราะถ้าเจอแบบ Brainfuck เข้าไปคงมึน

++++++++++ [>+++++++>++++++++++>+++>+<<<<-]
    >++.>+.+++++++..+++.>++.
    <<+++++++++++++++.>.+++.------.--------.>+.>.

แต่ถ้า obfuscate แล้วสวยงามอย่างนี้ก็ยอมนะ (จาก Piet Program Gallery)


สวัสดีกันมาตั้ง 9 ครั้งแล้ว ครั้งที่เหลือก็ลองออกไปค้นหาสิ่งใหม่ๆ ดูบ้างนะฮะ

Nov 20, 2012

Topology 101: กลับด้านในของลูกบอลออกมา

ในวิชา topology มันมีบทพิสูจน์หนึ่งที่ไม่น่าเชื่อเอาซะมากๆ นั่นคือ เราสามารถกลับด้านเอาด้านในของลูกบอลออกมาด้านนอกได้ (Smale's paradox)



เอ่อ มันทำได้จริงแฮะ

Nov 5, 2012

คิดเองมั่งดิ

P: เราชอบเธอจริงๆ นะ ตอนนี้เราก็ยังชอบอยู่

A: รู้แล้ว

P: แล้ว...

A: คิดเองมั่งดิ >\\\<

P: ^___^

ว่าแต่ทำไมชีวิตจริงมันไม่เป็นงี้มั่งนะ

Oct 23, 2012

ถ้าฝนตกแล้วเก็บผ้า

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

ตัวอย่างเช่น "ถ้าฝนตก ผ้าที่ตากไว้จะเปียก" จะเห็นได้ว่า ฝนตกแล้วยังไงผ้าต้องเปียกแน่ๆ ไม่มีทางที่ฝนตกแต่ผ้าไม่เปียกได้เลย แต่เราจะไม่สามารถระบุได้เลยว่าหากฝนไม่ตกแล้วผ้าจะเปียกหรือไม่ (มันไม่เกี่ยวโยงกันเลย)

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

ดังนั้นถ้าจะหัวหมอหน่อย เวลามีคนบอกว่า "ถ้าฝนตก แล้วเก็บผ้า" เนี่ย เราสามารถไปเก็บผ้าได้ทันทีเลย ไม่ต้องรอให้ฝนตกนะ (เพราะสาเหตุมันไม่เกี่ยวโยงกัน ดังนั้นเมื่อฝนไม่ตก จะเก็บผ้าหรือไม่เก็บผ้าก็ได้)

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

นั่นก็คือเปลี่ยนประโยคนั้นเป็น "ฝนตกก็ต่อเมื่อเก็บผ้า" หรือเพื่อให้อ่านได้เข้าใจง่ายขึ้น "เก็บผ้าก็ต่อเมื่อฝนตก" แบบนี้แล้ว การจะเก็บผ้าต้องทำตอนที่ฝนตกเท่านั้น ถ้าฝนไม่ตกห้ามเก็บผ้าเด็ดขาดครับ

Oct 17, 2012

โปรแกรมที่เขียนมันช้า...

เวลาเขียน Python แล้วต้อง implement อะไรประมาณนี้
numbers = [random() for r in range(1000)]

for i in range(100):
    print(max(numbers)**i)
จะระลึกตัวเสมอว่า implement ตรงๆ แบบนี้แล้วมันช้านะ แต่ถ้า input ไม่ได้ใหญ่จนน่าเป็นห่วง ก็ไม่มีเหตุผลที่จะเขียนให้ยากขึ้น เพราะการแก้โดยเอา max(numbers) ไปไว้เป็นค่าคงที่นอก loop แม้จะทำให้โปรแกรมเร็วขึ้นก็ตาม แต่ก็จะเสีย readability ไปบางส่วน เวลากลับมาอ่านอาจจะสับสนใน logic ว่าส่วนนี้มีไว้ทำอะไรกันแน่ แถมถ้าภายหลังโปรแกรมถูกพัฒนาจนซับซ้อนมากๆ แล้ว ค่า max(numbers) อาจไม่เท่ากันทุกรอบที่วน loop ก็ได้ ทำให้เกิดเป็น bug ซ่อนเร้นอีก

ส่วนถ้าต้องการประสิทธิภาพที่ดีกว่าเดิมมากๆ การแก้ Python จนอ่านยากอาจไม่ใช่ความคิดที่เข้าท่าเท่าไหร่ ลองถอดโปรแกรมเดิมมาเขียนบน C++ แบบเป๊ะๆ ก็คงได้ประมาณนี้
int main(void) {
    float numbers[1000];
    fill_with_random(numbers);

    for (int i=0; i<100; i++) {
        printf("%.32f\n", pow(max(numbers), i));
    }
    return 0;
}
(ตัวเลขขนาด array / loop ควรเปลี่ยนไปยังค่าสูงกว่านี้ เพื่อให้เห็นได้ชัดว่าโปรแกรมทำงานได้ช้าลง)

ถึงตอนนี้จะเห็นว่า max(numbers) มีผลอย่างมากต่อประสิทธิภาพโปรแกรม การย้ายมันออกมาไว้นอก loop จะเป็นการกระทำที่มีเหตุผลขึ้นทันที

แต่ถ้าโปรแกรมเรามีอะไรแบบนี้เป็นสิบเป็นร้อยที่หละ การย้ายมือเองไม่สนุกแน่

ทางออกก็ง่ายๆ บอกให้ gcc มันทำแทนเราไปซะ
$ gcc -O2 exp-max.cpp
เพียงเท่านี้ก็จะได้ code ที่ยังคง logic + readability เดิมเอาไว้อยู่ พร้อมทั้งโปรแกรมที่เร็วกว่าเดิมเยอะ

แต่ถ้าโปรแกรมใหญ่มากๆ ก็ต้องรอ compile กันนานหน่อยนะ :P



Oct 7, 2012

Nessun Dorma



คือปรกติเพลงประกอบโฆษณาที่เป็นเพลงคลาสสิก เคยเจอแต่พวกเพลงบรรเลงแฮะ ไม่ค่อยเจอ aria อะไรอย่างนี้ซักเท่าไหร่ จดเก็บไว้หน่อยๆ :3

Sep 27, 2012

Where Do I Begin?



where do i begin? .... ตอบยากมาก เริ่มไม่ถูกเลยแฮะ พอรู้ตัวอีกทีก็หลงรักเสียงของปู่ไปแล้ว

หลับให้สบายฮะปู่ :'(

P.S. Love means never having to say you're sorry

Sep 22, 2012

Einleitung



ปรกติแล้ว เพลงโหมโรงในโลกของ classical music จะค่อนข้างยาว บางเพลงก็เป็น 10 นาทีเลย ซึ่งมันก็ไม่ได้ peak ตลอดเวลาทั้งเพลง มีส่วนที่ฟังสนุกๆ แค่ไม่เท่าไหร่

แต่ Einleitung ใน Also sprach Zarathustra ของ R. Strauss กลับไม่ใช่อย่างนั้น มันเป็นเพลงโหมโรงสั้นๆ แค่ 2 นาทีที่ทรงพลังอย่างมาก

ยิ่งเอาไปใช้คู่กับบทเปิดตัวกับอะไรก็ตามนี่ สุดยอดมาก >w<)b

Sep 18, 2012

1000^1000 กับ 1001^999 อะไรมากกว่ากัน?

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

จริงๆ ตอนก่อนก็แอบเกรียนไปหน่อย คือรู้แหละว่าตั้งใจจะให้พิสูจน์ทางคณิตศาสตร์ งั้นมาไถ่บาปกับคำถามที่ว่า 1000^1000 กับ 1001^999 (เวอร์ชั่นแก้คำผิดแล้ว) อะไรมากกว่ากันอีกรอบดีกว่า ;)



ก่อนอื่น สังเกตว่า
1000^1000 = 1000 * 1000^999
ที่เราต้องการคือ จัดรูปของ 1001^999 ให้กลายเป็น 1000^999 ให้ได้ ซึ่งก็ง่ายๆ โดนทำการกระจายทวินาม
1001^999 = (1000 + 1)^999
         = summation   C(999, i) * 1000^(999-i) * 1^i   for i from 0 to 999
         = summation   C(999, i) * 1000^(999-i)         for i from 0 to 999
         = 1000^999 + C(999, 1) * 1000^998 + C(999, 2) * 1000^997 + ... + 1
จะเห็นว่า พอกระจาย 1000^999 ออกมาแล้ว จะมี 1000 พจน์พอดี (index ไล่จาก 0 จนถึง 999) ซึ่งมันสามารถเอาไปจับคู่กับ 1000*1000^999 ได้ ดังนี้
pair(1000^1000, 1001^999) = [ (1000^999, 1000^999),
                              (1000^1 * 1000^998, C(999, 1) * 1000^998),
                              (1000^2 * 1000^997, C(999, 2) * 1000^997),
                              ...
                              (1000^i * 1000^(999-i), C(999, i) * 1000^(999-i)),
                              ...
                              (1000^999 * 1000^0, C(999, 999) * 1000^0) ]
จะเห็นว่า สิ่งที่ต้องการตรวจสอบคือ 1000^i กับ C(999, i) อะไรมากกว่ากัน

จาก
P(n, r) = n!/r!
C(n, r) = n!/(r!(n-r)!) = P(n, r)/(n-r)!
จะเห็นว่า P(n, r) มีค่ามากกว่าหรือเท่ากับ C(n, r) เสมอ

ถึงตรงนี้ก็เห็นชัดแล้วว่า
1000^0 = P(999, 0) = 999!/999! = 1
1000^1 > P(999, 1) = 999!/998! = 999
1000^2 > P(999, 2) = 999!/997! = 999 * 998
1000^3 > P(999, 3) = 999!/997! = 999 * 998 * 997
...
1000^n >= P(999, n) = 999!/(999-n)! = 999 * 998 * ... * (999-n)
ดังนั้น
1000^i >= P(999, i) >= C(999, i)   for all i in [0..999]
เพราะฉะนั้น
for i in [1..999]:
    summation 1000^i * 1000^(999-i) >= summation C(999, i) * 1000^(999-i)
    summation 1000^(i+999-i) >= summation C(999, i) * 1000^(999-i) * 1^i
    1000 * 1000^999 >= (1000 + 1)^999
    1000^1000 >= 1001^999
แต่เนื่องจาก มีบางพจน์ 1000^i ที่มีค่ามากกว่า C(999, i) ดังนั้น
1000^1000 > 1001^999
จบการพิสูจน์ครับ

Sep 17, 2012

ยังไหว...

วันก่อนเจอคำถามงี่เง่าว่า 1000^1000 กับ 101^999 อันไหนมากกว่ากัน? แล้วก็บอกว่าห้ามใช้ sense ด้วยนะ

ห้ามใช้ sense ก็กดเครื่ิงคิดเลขไปสิโว้ย


คนตั้งคำถามคงคิดไม่ถึงเนาะ ว่าเครื่องคิดเลขบ้าอะไรจะหาคำตอบของเลขใหญ่ๆ ขนาดนั้นได้

เลยลองเขียนอะไรเล่นๆ เพื่อทดสอบประสิทธิภาพทางการคำนวณตัวเลขดู พบว่าตัวเลขระดับหมื่นหลักก็ยังไหว


Fibonacci ลำดับที่ 57075 เมื่อเขียนในฐานสิบจะต้องใช้ตัวอักษร 11928 ตัว

อันที่จริง ด้วย algorithm ด้านบนนี้ จะเอาไปใช้คำนวณหา Fibonacci ลำดับที่ห้าล้าน ซึ่งถ้าจะเขียนออกมาในเลขฐานสิบ ต้องใช้ตัวอักษรถึง 1044939 ตัวก็ยังไหวนะ (ภายในเวลาประมาณครึ่งนาทีเอง)

ก็อย่าประเมินประสิทธิภาพของคอมพิวเตอร์ต่ำไปละกัน ;)

Sep 13, 2012

ทำ Recursion บนการ Integral

ฟังก์ชัน factorial นั้นมีนิยามง่ายๆ ตรงไปตรงมาคือ ผลคูณของจำนวนเต็มบวกตั้งแต่ 1 ไปจนถึง n
factorial n = product [1..n]
ซึ่งถ้าสังเกตดูซักหน่อย จะพบว่ามันสามารถเขียนนิยามเป็น recursion ได้
factorial 1 = 1
factorial n = n * factorial (n - 1)
ตอนนี้เราอาจเพิ่มนิยามที่ว่า 0! = 1 เพื่อความสะดวกในการกระจายพจน์สำหรับคำนวณความน่าจะเป็น

แต่ทั้งหมดนี้จะมีปัญหาอยู่อย่างนึง ตรงที่ทุกอย่างเป็น discrete math หมดเลย นั่นคือเราไม่สามารถหาอะไรอย่าง 0.5! ได้

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



โชคดี (?) ที่เรามีเลขมหัศจรรย์อย่าง e ซึ่งมีสมบัติประหลาดอย่าง
e^x = d/dx e^x
ดูๆ ไปแล้ว มันก็คล้ายกับ fixed-point combinator ที่เคยพูดไว้ในตอนก่อนๆ เพียงแต่เปลี่ยนจาก function call เป็นการ diff-integral แทน โดยมี terminate point อย่าง
integral 1/e^x dx from 0 to infinity = - 1/e^infinity + 1/e^0
                                     = 1
นี่ทำให้เราสามารถเขียน recursion ในรูปของ integral ได้ เช่น
Gamma(n) = integral 1/e^t t^(n-1) dt from 0 to infinity
ลองดูสมบัติของมันโดยทำการ integral เข้าไป จะได้ว่า
Gamma(n) = integral 1/e^t t^(n-1) dt from 0 to infinity
         = integral u dv from 0 to infinity            ; let u = 1/e^t, dv = t^(n-1) dt
         = (limit u*v for x from 0 to infinity) - integral v du from 0 to infinity
         = (1/n)(1/e^infinity infinity^n - 1/e^0 0^n) - integral v du from 0 to infinity
         = 0 - integral v du from 0 to infinity
         = (1/n) integral 1/e^t t^n dt from 0 to infinity
         = Gamma(n+1)/n
ดังนั้น จะเห็นว่า
Gamma(1) = 1
Gamma(n) = (n-1) * Gamma(n-1) = (n-1)(n-2) * Gamma(n-2) = ...
ซึ่งก็คือ
factorial(n) = Gamma(n+1)


ถึงตอนนี้ก็คงตอบคำถามได้แล้วว่า 0.5! นั้น สามารถหาค่าได้จาก
Gamma(3/2) = (1/2) * Gamma(1/2)

Gamma(1/2) = integral 1/e^t 1/sqrt(t) dt from 0 to infinity
           = 2 * integral 1/e^u^2 du from 0 to infinity      ; let du = 1/sqrt(t)
           = integral 1/e^u^2 du from -infinity to infinity
           = sqrt(pi)

Gamma(3/2) = sqrt(pi)/2
ตอนพยายามหาค่าของ Gamma(1/2) ต้องใช้ความรู้เรื่อง double integral และการแปลงระนาบเข้าช่วย ลองศึกษาเทคนิคที่นี่

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

Sep 11, 2012

Scope และชื่อตัวแปร

JavaScript เป็นตัวอย่างที่ดี (หรือแย่?) ในเรื่อง scope ลองดู
function starline(n) {
    stars = ''
    for (i=0; i<n; i++)
        stars += '*'
    return stars + '\n'
}

function square(n) {
    shape = ''
    for (i=0; i<n; i++)
        shape += starline(n)
    return shape
}

console.log(square(5))
ความคาดหวังของเราคือรูปสี่เหลี่ยมจตุรัสที่สร้างจากดาวขนาด 5x5 แต่ถ้าลองรันโปรแกรมนี้ดู จะเห็นผลลัพท์เป็นรูปดาวที่มีแค่แถวเดียว นั่นเป็นเพราะว่า i ของ square กับ i ของ starline มันคือตัวเดียวกัน ทำให้หลังจากทำงานที่ starline เสร็จแล้ว i ที่ square ก็จะกลายเป็น 5 ไปด้วย ส่งผลให้หลุดออกจาก loop ทันที

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

...แต่คำถามก็คือ มันมีความจำเป็นจริงๆ หรือ ที่เราต้องไล่ประกาศตัวแปรแต่ละตัวในแต่ละ scope เพียงเพื่อไม่ให้มันหลุดไปยัง scope ที่ใหญ่กว่า

ลองดูตัวอย่างแบบเดียวกันใน Python บ้าง
def starline(n):
    stars = ''
    for i in range(n):
        stars += '*'
    return stars + '\n'

def square(n):
    shape = ''
    for i in range(n):
        shape += starline(n)
    return shape

print(square(5))
ความสามารถในการละการประกาศตัวแปรของแต่ละ scope คงไม่ช่วยเรื่อง readability มากเท่าใดนัก แต่มันช่วยให้เขียนโปรแกรมด้วยความกังวลน้อยลง โฟกัสกับเรื่องของ logic มากขึ้น แถมยังไม่ต้องเจอกับ bug ประหลาดอย่างด้านบนนั้นอีก ... อันที่จริง เราสามารถทำอย่างนี้ได้ด้วยซ้ำ
def square(n):
    shape = ''
    for i in range(n):
        for i in range(n):
            shape += '*'
        shape += '\n'
    return shape

print(square(5))
พอลองย้อนกลับไปดูดีๆ บางทีเราก็ไม่จำเป็นต้องเรียกตัวแปรบางตัวมาใช้เสมอไป (แค่ประกาศมันไว้เป็น loop index) แล้วงั้นทำไมไม่ทำให้ตัวแปรนั้นไม่มีชื่อไปซะเลยหละ?
let square n = unlines [['*' | _ <- [1..n]] | _ <- [1..n]]

putStrLn $ square 5
maintenance ง่ายขึ้นเป็นกองเลย (หรือเปล่า? 555)

Sep 10, 2012

ผิดพลาด

บ่อยครั้งที่เรามักกลัวในสิ่งใหม่ๆ เพราะนั่นหมายถึงการเข้าไปอยู่ในสภาพแวดล้อมที่ไม่คุ้นเคย ส่งผลให้มีโอกาสทำผิดพลาดสูงขึ้นมากจนทนรับไม่ไหว

แต่บางครั้ง ความผิดพลาดก็นำมาซึ่งสิ่งใหม่ที่สวยงามนะ


อย่างเช่นรูปนี้ที่ตอนแรกตั้งใจว่าจะวาดออกมาเป็นท้องฟ้า (แบบ Google Sky Map) แต่ว่าแปลงสมการออกมาผิดนิดหน่อย เลยเละตุ้มเป๊ะอย่างนี้

ก็อย่าไปกลัวว่าจะทำพลาดนักเลย ถ้าผลลัพท์ที่เลวร้ายที่สุดไม่ได้ไปทำใครตายเข้า ก็หลับหูหลับตาแล้วลุยๆ ไปเถอะ อย่ามัวแต่ลีลานักเลยน่า :3

Sep 3, 2012

แก้โจทย์ป้ายสมัครงาน Google แบบบรรทัดเดียว

นอนไม่หลับ บวกกับช่วงนี้แก้โจทย์เล่นเยอะไปหน่อย เหมือนสมองไม่อยากหยุดแก้โจทย์ 555

แล้วก็นึกได้ว่า ไอ้โจทย์ป้ายสมัครงานของ Google อันนี้ ยังไม่เคยแก้เล่นเองเลยนี่หว่า


แต่ถ้าจะให้แก้ตามปรกติธรรมดา ก็ดูจะไม่ท้าทายซักเท่าไหร่ไปแล้ว (เพราะเล่นไปอ่านเฉลยมาเรียบร้อย) แบบนี้เลยต้องเพิ่ม standard ให้กับตัวเองด้วยการเขียนเป็น one-liner โดยไม่พึ่งพา stdlib ซะเลย

ตัวโจทย์คงไม่ต้องแปลแล้ว? (ถ้าตีโจทย์ไม่ออกจริงๆ แนะนำบล็อก @khajochi เลย) เอาเป็นว่าเริ่มกันเลยดีกว่านะ



โดย algorithm ที่จะใช้คำนวณหาค่า e คือ
e = 1 + 1/1! + 1/2! + 1/3! + 1/4! + ...
ต้องใช้ algorithm นี้เพราะว่ามันให้ค่าทศนิยมได้แม่นยำรวดเร็วพอสมควร และยัง implement ตามไม่ยากเท่าไหร่ ในที่นี้ใช้ถึงแค่พจน์ 1/200! ก็ให้ค่าออกมาแม่นยำเกือบ 400 ตำแหน่งแล้ว

มาเริ่มที่ส่วนที่เล็กที่สุด ฟังก์ชั่น factorial ที่ดูเผินๆ ก็ไม่มีอะไรประหลาด แต่ถ้าจะเขียนแบบ one-liner ก็เลี่ยงไม่ได้ที่จะต้องเขียนเป็น recursion และต้องไม่จองชื่อ function ไว้ก่อนด้วย ซึ่งก็ทำได้โดยใช้ combinator แบบนี้
factorial = lambda n: (lambda f, x: f(f, x))(lambda r, y: y*r(r, y-1) if y > 1 else 1, n)
(รายละเอียดอ่านได้จากตอนก่อนๆ)

ต่อมาคือการจับแต่ละพจน์มาบวกกัน ซึ่งจะ implement ตรงๆ แบบนี้ไม่ได้
sum(1/factorial(i) for i in range(200))
เพราะ float มันเก็บ precision ของทศนิยมได้เล็กนิดเดียว ดังนั้นเราต้องดัดแปลงสมการต้นแบบให้กลายเป็น
e = (1 + 1/1! + 1/2! + 1/3! + 1/4! + ...)(1!2!3!4!.../1!2!3!4!...)
เทคนิคหนึ่งที่ใช้ได้เสมอๆ เมื่อต้องการทศนิยม n ตำแหน่ง คือคูณด้วย 10 ยกกำลัง n เข้าไปนั่นเอง ดังนั้น
e*10**n = 10**n * (1 + 1/1! + 1/2! + 1/3! + 1/4! + ...)(1!2!3!4!.../1!2!3!4!...)
        = 10**n * (1 + 2!3!4!... + 1!3!4!... + 1!2!4!... + 1!2!3!... + ...)/1!2!3!4!...
จะเห็นว่าถ้าเรียงลำดับ operator ดีๆ เราจะไม่สูญเสีย precision เลย

ได้แนวคิดแล้วก็เริ่ม implement กัน เริ่มโดยหา factorial ของพจน์ต่างๆ ตามปรกติ
fact_each = [factorial(i) for i in range(200)]
ทีนี้เพื่อป้องกันการคำนวณซ้ำหลายรอบ ก็เอา lambda มาห่อหุ้มมันซะ ซึ่งสิ่งที่เราต้องการต่อจากนี้คือผลคูณรวมของค่าทั้งหมด (1!2!3!4!...) โดยเรายังคงต้องใช้ list เดิมนี้อยู่ ดังนั้น
prod_and_fact_each = (lambda l: [reduce(lambda x, y: x*y, l), l])(fact_each)
ถึงตอนนี้ก็ได้เวลาเอาผลคูณรวมของ factorial ทุกตัว มาหารแต่ละตัวใน list นั้นแล้ว (เรายังต้องใช้ผลคูณรวมอีกรอบ ดังนั้นส่งต่อมันด้วย)
prod_and_div_each = (lambda a, l: [a, [a / i for i in l]])(*prod_and_fact_each)
สุดท้ายก็จับทุกตัวมาบวกกัน คูณด้วย 10 ยกกำลัง n แล้วค่อยหารด้วยค่ารวมนั้น
e = str((lambda a, l: 10**400 * sum(l) / a)(*prod_and_div_each))
เท่านี้ก็ได้เลข e ที่มีทศนิยมตามต้องการแล้วนะ <3



สำหรับส่วนที่สองจะมาดูที่จำนวนเฉพาะ เนื่องจากเลข 10 หลักค่าที่ใหญ่ที่สุดคือ n = 9999999999 การที่จะบอกว่ามันเป็นจำนวนเฉพาะหรือไม่ ก็ทดสอบด้วยการเอาไปหารจำนวนเฉพาะทุกตัวที่น้อยกว่ามันไปจนถึง sqrt(n) ซึ่งหมายความว่าเราตรวจถึงแค่จำนวนเฉพาะที่น้อยกว่า sqrt(9999999999) = 99999 ก็พอแล้ว

เริ่มด้วยฟังก์ชั่นเพิ่มจำนวนเฉพาะเข้าไป
add_prime = lambda p, t: p + [t] if all(t % q for q in p) else p
ฟังก์ชั่นนี้จะรับตัวแปร 2 ตัวคือ list ของจำนวนเฉพาะก่อนหน้า และเลขที่จะตรวจว่าเป็นจำนวนเฉพาะหรือเปล่า ส่วนค่าที่คืนมานั้น ถ้าเป็นจำนวนเฉพาะจะคืน list ที่เพิ่มจำนวนที่ตรวจเข้าไปด้วย ดังนั้นสิ่งที่เราต้องทำคือใส่ list ว่างๆ เข้าไปก่อน แล้วส่งเลขที่น้อยที่สุดคือ 2 เข้าไปตรวจ หลังจากได้ list ที่มีเลข 2 ก็ส่งเลข 3,4,5,6,...,99999 เข้าไปตรวจทีละตัว โดย list ที่เป็นผลลัพท์แต่ละครั้งที่ได้มาก็จะเก็บไว้ใช้ต่อไปเรื่อยๆ นี่ก็คือแนวคิดของ reduce นั่นเอง ซึ่งก็จะเขียนได้ว่า
p = reduce(add_prime, [[], 2] + range(3, 100000, 2))
ถึงตอนนี้ก็มีจำนวนเฉพาะทั้งหมดที่ต้องการสำหรับงานนี้แล้ว :3



สุดท้าย ได้เวลาจับทุกอย่างมารวมกัน เริ่มจากการพยายาม loop ไปยังเลข 10 ตัวในทศนิยมของ e ก่อน ซึ่งก็คือ
[e[i:10+i] for e in [e] for i in range(len(e)-9)]
แต่เนื่องจากเราต้องการ filter ตัวที่ไม่ใช่จำนวนเฉพาะออกไป ซึ่งคราวนี้เราจะเปลี่ยนมาทดสอบด้วยฟังก์ชั่น
test_prime = lambda p, e: all(int(e) % q for q in p)
ดังนั้นก็จะได้
[e[i:10+i] for e, p in [[e, p]] for i in range(len(e)-9) if test_prime(p, e[i:10+i])]
แน่นอนว่างานนี้เราต้องการแค่จำนวนเฉพาะตัวแรกที่เจอ ดังนั้นสามารถเปลี่ยนไปเขียน
next(e[i:10+i] for e, p in [[e, p]] for i in range(len(e)-9) if test_prime(p, e[i:10+i]))
ก็จะได้คำตอบแล้วหละ \(^0^)/



สำหรับ code ฉบับเต็มๆ ที่เป็น one-liner (แต่ขึ้นบรรทัดใหม่เพื่อให้อ่านสะดวก) คือ
next(e[i:10+i] for e, p in [[
        str((lambda a, l: 10**400 * sum(l) / a)(*
            (lambda a, l: [a, [a / i for i in l]])(*
                (lambda l: [reduce(lambda x, y: x*y, l), l])(
                    [(lambda f, x: f(f, x))(
                           lambda r, y: y * r(r, y-1) if y > 1 else 1, i)
                        for i in range(200)])))),
        reduce(
            lambda ps, t: ps + [t] if all(t % p for p in ps) else ps,
            [[], 2] + range(3, 100000, 2))]]
    for i in range(len(e)-9)
        if all(int(e[i:10+i]) % q for q in p))
happy coding นะครับ ^^

Aug 30, 2012

ทำ Infinity List ใน Python

พอดีไปขุด Project Euler มาเล่น แล้วมันต้องได้ยุ่งกับพวก infinity sequence อย่างจำนวนเฉพาะ, ฟีโบนักชีบ่อยๆ ตอนแรกก็ใช้แค่ list ธรรมดาเนี่ยแหละ แล้วถ้า index ที่อยากได้ไม่มีก็ค่อยไปเรียก function แยกมาคำนวณแล้วเก็บข้อมูลกลับเข้า list เอา

ทีนี้เขียนไปเขียนมารู้สึกว่า code มันไม่สวยเอาซะเลย เพราะไอ้จำนวนเหล่านี้เนี่ย เวลาหามันจะกระโดดข้ามไป index ที่ต้องการไม่ได้อยู่แล้ว บวกกับจำนวนพวกนี้มันหาเพิ่มได้เรื่อยๆ ไม่มีที่สิ้นสุด ดังนั้นในสายตาของนักคณิตศาสตร์อย่างผม ถ้าเขียน prime[100] แล้วเจอฟ้องว่า index out of range นี่คงเซ็งน่าดู เลยคิดว่ามันน่าจะมีวิธี hack ให้ใช้ syntax นี้หาค่าใน index ที่ต้องการแม้ต้อนนี้จะยังไม่มีได้

ค้นไปค้นมาก็เจอ list.__getitem__ เลยจัดการลงมือลุย
class InfinityList(list):
    def __iter__(self):
        n = 0
        while True:
            yield self[n]
            n += 1

    def __repr__(self):
        return super(InfinityList, self).__repr__()[:-1] + ', ...]'


class fibonacci(InfinityList):
    def __getitem__(self, n):
        while True:
            try:
                return super(InfinityList, self).__getitem__(n)
            except IndexError:
                self.append( super(InfinityList, self).__getitem__(-1) +
                             super(InfinityList, self).__getitem__(-2) )

    def __init__(self):
        super(InfinityList, self).__init__([1, 1])


class prime(InfinityList):
    def __getitem__(self, n):
        while True:
            try:
                return super(InfinityList, self).__getitem__(n)
            except IndexError:
                c = super(InfinityList, self).__getitem__(-1)
                while True:
                    c += 2
                    for p in self[:]:
                        if not c % p:
                            break
                    else:
                        self.append(c)
                        break

    def __init__(self):
        super(InfinityList, self).__init__([2, 3])

fibonacci = fibonacci()
prime = prime()
กรอเวลาไปข้างหน้า 8 ชั่วโมง ก็มีของเล่นใหม่ตามที่แปะเป็นตัวอย่างไว้ด้านบน
  1. ใน __getitem__ นี่ลืมไปว่าถ้าแก้ตรงนี้แล้วมันจะทำ recursion กับตัวเอง ทำให้ไม่สามารถใช้ syntax อย่าง fibonacci[-1] เพื่อดึงเอาตัวเลขออกมาได้ ก็เลยต้องเลี่ยงไปเรียก method จาก super class เอา
  2. จะให้ syntax แบบ list[n] ทำงานโดยเรียก __getitem__ ต้อง inherit class มาแล้ว define ไว้ตอนสร้าง class เท่านั้นด้วย มาสั่ง class.__getitem__ = outer_function ไม่ได้ (ป้องกัน injection เข้าระบบ)
  3. ตัว __getitem__ มันรับ parameter ได้อันเดียวก็จริง แต่ไม่ใช่แค่ตัวเลขเท่านั้น เพราะยังมี slice object อีกด้วย (นึกถึงเวลาเขียน some_list[4:-4] ไอ้ [4:-4] นั่นแหละ slice object) ดังนั้นงานนี้ play safe ใส่ while True ไปดีกว่า ช้าหน่อยยอมรับได้
  4. สุดท้ายก็คืออยากให้ class พวกนี้เป็น static ไปซะ (ตอนแรกออกแบบว่าจะให้เขียนเป็น prime.numbers[n] ด้วยซ้ำ) แต่เหนื่อยมากขี้เกียจทำแล้ว เลยจับประกาศชื่อ fibonacci, prime ทับกับชื่อ class ตัวมันเองไปซะเลย ยังไงก็มีได้แค่ instance เดียวอยู่แล้วหนิ 555+
เพียงเท่านี้ ถ้าอยากได้ prime ตำแหน่งที่ 101 ก็แค่สั่ง
prime[100]
จบเลย ง่ายมั้ย? อยากลองเล่นเองแล้ว? ไปจิ้ม code ได้จากที่นี่เลย XD

Aug 28, 2012

สมการรัก

เขียน Haskell อยู่ดีๆ แล้วก็ได้ไอ้นี่ออกมา...
let me = (<3) u where (u,me) = (1,(<3))

Aug 22, 2012

อะไรคือ Y Combinator

ถึงตอนนี้เราคงทำ recursion บน lambda เป็นกันแล้ว (ถ้ายังไม่เป็น แนะนำให้อ่านตอนก่อน)

กลับไปดูอีกรอบ จะเห็นว่าตอนเรียก recursion ต้องใส่ชื่อฟังก์ชันตัวเองลงไปเป็นตัวแปรหนึ่งเสมอ
lambda f, x: 1 if x == 0 else x * f(f, x-1)
แล้วถ้าเราไม่อยากทำอย่างนี้หละ อยากจะเรียกแค่ f(x-1) เหมือนการเรียกใช้งานฟังก์ชันตามปรกติทั่วไป จะทำได้มั้ย?

ถอยหนึ่งก้าว กลับไปเขียน factorial แบบธรรมดาๆ ก่อน
f = lambda x: 1 if x == 0 else x * f(x-1)
ฟังก์ชันนี้ยังคงสามารถเรียกใช้ได้ เนื่องจากว่าเราจองชื่อ f ไว้ให้มันแล้ว (ซึ่งไม่ใช่สิ่งที่เราต้องการ) นั่นหมายความว่าชื่อ f ต้องมีปรากฏอยู่ใน scope นี้จึงจะทำ recursion ได้ และมันมีอีกวิธีนึงที่จะทำให้ f ปรากฏอยู่ภายใน scope นี้โดยไม่ต้องจองชื่อหรือส่งผ่านเป็นตัวแปรใน scope เดียวกัน คือ
lambda f: lambda x: 1 if x == 0 else x * f(x-1)
แต่การประกาศเช่นนี้จะทำให้เราไม่สามารถใช้ฟังก์ชันได้ทันที เพราะเราต้องเอาฟังก์ชั่นนี้ส่งไปเป็นตัวแปรให้ตัวมันเองเรื่อยๆ เช่น ถ้าต้องการหา 5 factorial ต้องเขียนออกมาอย่างนี้
(lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
  (lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
    (lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
      (lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
        (lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
          (lambda f: lambda x: 1 if x == 0 else x * f(x-1))(
            (lambda whatever: 42)
          )
        )
      )
    )
  )
)(5)
นั่นหมายความว่าเราต้องการอะไรซักอย่าง ที่จะทำหน้าที่ generate ฟังก์ชันของเรานี้ออกมาเรื่อยๆ ไม่ว่าจะมีอะไรเกิดขึ้นก็ตาม นี่คือแนวคิดของ fixed-point combinator โดยมีตัวหนึ่งที่เป็นมีชื่อเสียงเป็นที่รู้จักกันมากคือ Y combinator ซึ่งเขียนได้ดังนี้
Y = lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v)))
(นิยามเต็มๆ คือ Y = λf.(λx.f (x x)) (λx.f (x x)) ซึ่งไม่จำเป็นต้องติดตัวแปร v มาด้วย)

ลองมาสำรวจการทำงานของมันดีกว่า จะเห็นได้ว่า
Y(g)(n)
-> (lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(g)(n)
   # reduce: g -> f
-> (lambda x: g(lambda v: x(x)(v)))(lambda x: g(lambda v: x(x)(v)))(n)
   # reduce: (lambda x: g(lambda v: x(x)(v))) -> x
-> g(lambda v: (lambda x: g(lambda v: x(x)(v)))(lambda x: g(lambda v: x(x)(v)))(v))(n)
   # reduce: n -> v
-> g((lambda x: g(lambda v: x(x)(v)))(lambda x: g(lambda v: x(x)(v)))(n))
   # for inner g function, expand back to lambda: f <- g
-> g(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v)))(g)(n))
   # by definition
-> g(Y(g)(n))
   # thus
-> g(g(Y(g)(n)))
-> g(g(g(Y(g)(n))))
-> g(g(g(g(...))))
วิธีเอามาใช้งานก็ไม่ยุ่งยาก เพียง
(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(  # Y-comb
  lambda f:                                  # recursive function name
    lambda x: 1 if x == 0 else x * f(x-1)    # recursive function definition
  )(5)                                       # recursive function argument
ลองเปลี่ยนมาหา fibonacci บ้าง ก็ง่ายนิดเดียว
(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(
  lambda f:
    lambda x: x if x <= 1 else f(x-1) + f(x-2)
  )(5)
ง่ายจริงป่ะ?

Aug 21, 2012

Recursive Lambda ความงามบนความงาม...

พอถล่ำลึกลงไปใน functional programming ซักพักหนึ่ง จะเริ่มเกิดคำถามขึ้นมาว่า
"แล้วเราจะทำ recursion บน lambda ได้หรือเปล่า?"
ฟังดูเผินๆ ไม่น่าเป็นไปได้ เพราะ lambda ทำให้ฟังก์ชันไม่มีชื่อ แต่การ recursion ต้องอาศัยชื่อของฟังก์ชันนั้นๆ เพื่อเรียกมันขึ้นมาทำงานเสมอ สมมติเช่นฟังก์ชันง่ายๆ ที่จะทำการยกกำลังสองเลขที่ได้รับมาทันที
(lambda x: x**2)(5)
นี่คือฟอร์มที่เรียบง่ายสุด และถ้าเราทดลองเพิ่มตัวแปรเข้าไปให้ฟังก์ชันนี้ (โดยไม่จำเป็นต้องใช้มัน) อาจจะเขียนฟังก์ชันนี้ออกมาได้ว่า
(lambda w, x: x**2)(999, 5)
แน่นอนว่ากรณีนี้เราไม่สนใจเลข 999 ที่ถูกนำไปเก็บไว้ยังตัวแปร w เลย ...แล้วเราก็คงจะถึงทางตัน ถ้าไม่สังเกตว่าตัวแปร w นั้นหนะ รับค่าอะไรเข้ามาก็ได้ (เพราะไม่ได้ใช้) นี่รวมไปถึงเราอาจจะยัด lambda เข้าไปให้มันก็ได้
(lambda w, x: x**2)(lambda u: 42, 5)
นั่นหมายความว่า lambda ไม่จำเป็นต้องไร้ชื่อเสียทีเดียว แน่หละมันอาจจะไร้ชื่อใน scope หนึ่งๆ แต่กับ scope ที่ใหญ่ขึ้นกว่าตัวมันแล้ว เราสามารถตั้งชื่อให้มันได้ ถึงตอนนี้จะเปลี่ยนชื่อตัวแปรกันซักหน่อย พร้อมกับเอาฟังก์ชันสำหรับคำนวณจากด้านบนมาใส่แทน lambda u: 42 ไปซะ
(lambda g, y: y**2)(lambda x: x**2, 5)
จะเห็นว่าเลข 5 จะถูกเก็บไว้ในตัวแปร y และคำนวณ y**2 ตรงๆ โดยที่ lambda x: x**2 ไม่ได้แสดงบทบาทอะไร แต่เนื่องจากฟังก์ชัน lambda x: x**2 ถูก scope ด้านหน้าแปะป้ายชื่อให้ว่า g เป็นที่เรียบร้อยแล้ว ดังนั้น เราสามารถส่งผ่านเลข 5 ให้ไปทำงานกับ lambda x: x**2 ได้ดังนี้
(lambda g, y: g(y))(lambda x: x**2, 5)
แม้ว่าตอนนี้เราจะสามารถเรียกชื่อของ lambda x: x**2 ใน scope ที่ใหญ่กว่าตัวมันได้ แต่มันก็ไม่มีประโยชน์เลยเพราะการจะทำ recursion นั้นต้องสามารถเรียกชื่อตัวมันเองใน scope ที่มันอยู่ได้ ดังนั้น เราจะใช้ท่าเดิมคือส่งผ่านเจ้า lambda x: x**2 นี้ต่อไปเรื่อยๆ ไม่ให้มันหายไป โดย
(lambda g, y: g(g, y))(lambda f, x: x**2, 5)
เท่านี้ ใน scope ของ lambda f, x: x**2 ก็สามารถเรียกตัวเองได้แล้ว (ชื่อว่า f) ถึงตอนนี้ก็ได้เวลาเขียน recursion ให้มัน อาจเริ่มจากฟังก์ชันง่ายๆ อย่าง factorial ก็ได้
(lambda g, y: g(g, y))(lambda f, x: 1 if x == 0 else x * f(f, x-1), 5)
สังเกตว่าการเรียก recursion ภายใน lambda นี้ ต้องส่งผ่านชื่อฟังก์ชันตัวเองเป็นตัวแปรพ่วงลงไปด้วยเสมอนะ

จบแล้ว แค่นี้แหละ recursion บน lambda ไม่ยากอย่างที่คิดเลยใช่มั้ยครับ ;)

Aug 20, 2012

เขียน Loop ยังไงดี?

ในการเขียนโปรแกรม เรามักต้องพบปะกับท่านี้เสมอๆ
text = input()
while text != 'exit':
    # do something with text
    # ...
    text = input()
ดูเผินๆ ก็ไม่น่ามีปัญหาอะไร แต่นี่จะทำให้โปรแกรมเมอร์สำคัญผิดกับ input ครั้งแรก หรือไม่งั้นก็มองโค้ดผ่านๆ แล้วนึกว่า input ด้านนอกนั้นจะไม่เกี่ยวกับกิจกรรมที่เกิดขึ้นใน while loop ที่ตามมาเลย (ยิ่งถ้าต้องมีการ pre-process input ก่อนที่จะเอาไป check while อีก) ทางแก้ง่ายๆ คือเปลี่ยนไปเขียนแบบนี้แทน
while True:
    text = input()
    if text == 'exit':
        break
    # do something with text
    # ...


อีกเรื่องที่ไม่ค่อยเกี่ยวกันเท่าไหร่คือ การรับข้อมูลจาก database มาแสดงผล ส่วนใหญ่จะทำท่านี้
$result = mysql_query($sql);
while ($row = mysql_fetch_row($result)) {
    echo $row[0], $row[1], $row[2];
}
ในทางปฎิบัติมันก็ใช้ได้เลย แต่ถ้ามาคิดๆ ดูแล้ว ข้อมูลที่ query ออกมาจาก database ได้นั้นควรจะมีจำนวนจำกัดเสมอ (ไม่มี database ไหนที่เก็บข้อมูลได้เป็น infinity เป็นแน่??) นั่นหมายความว่าทางทฤษฎีแล้ว มันไม่ควรเขียนด้วย while loop แต่ควรเขียนด้วย for loop (อาจเป็น for-each loop) เช่นนี้
// codeigniter active record
$query = $this->db->get($sql);
foreach ($query->result() as $row) {
    echo $row->title, $row->author, $row->content;
}
ท่านี้จะทำให้มีปัญหาด้าน memory (เพราะต้อง query ข้อมูลทั้งหมดออกมาเก็บไว้ใน array ก่อน แล้วจึงค่อยวนเข้าไปอ่านข้อมูลใน array นั้น) ซึ่งแก้ได้ที่ระดับ library โดยเปลี่ยนไปใช้ generator แทน array



ท้ายสุด กลับไปที่ while loop แบบแรก โดยมีเงื่อนไขเพิ่มเติมตรงที่อยากรู้ว่าเป็นการทำงานรอบที่เท่าไหร่ อาจเขียนเพิ่มเป็น
i = 0
while True:
    text = input()
    if text == 'exit':
        break
    # do something with text
    # ...
    print('loop no:', i, '; input is:', text)
    i += 1
จะเห็นว่ามี i = 0 เป็น initial statement อยู่นอก scope ของ while ตรงนี้จริงๆ ไม่มีปัญหาอะไรเลย แต่ถ้าอยากให้มันสวยขึ้นด้วยการจัดระเบียบ block สามารถใช้ประโยชน์ของ infinity generator ได้
from itertools import count

for i in count():
    text = input()
    if text == 'exit':
        break
    # do something with text
    # ...
    print('loop no:', i, '; input is:', text)
หรือยิ่งไปกว่านั้น ทำการเปลี่ยนรูปแบบการเขียนเพื่อให้ไม่ต้องตรวจสอบสำหรับ break ออกจาก loop แต่ไปตรวจตั้งแต่หัว loop เลยว่าหลุดออกจาก loop นี้หรือยัง อย่างนี้
from itertools import count

for i, text in zip(count(), iter(input, 'exit')):
    # do something with text
    # ...
    print('loop no:', i, '; input is:', text)
แนวคิดจะเปลี่ยนไปนิดนึงด้วย ตรงที่เราไม่ได้ใช้ while loop แล้ว (loop แต่ละครั้งไม่เชื่อมโยงกัน) เปลี่ยนมาเป็น for loop (แต่ละครั้งมีความเกี่ยวเนื่องกัน อย่างน้อยๆ ก็ index ที่ไล่กันไปเรื่อยๆ) นั่นเอง ซึ่งส่วนตัวแล้วผมชอบอย่างหลังมากกว่า

Aug 19, 2012

จัดระเบียบ Blog



สืบเนื่องจากเมื่อวานอ่าน blog ของ @phatpeth แล้วรู้สึกว่า theme มันสวยดี ... อันที่จริงเราเองก็ไปเล่นตั้งแต่วันแรกๆ ที่มันออกเลยนะ แต่ทีนี้มันติดปัญหาที่ว่า syntax highlighter ตัวเก่งของเราเนี่ย มันดันทำงานไม่ได้ในโหมดนี้ซะหนิ (กลัวว่าต้องไปใช้วิธี cap code เป็นภาพเอาเหมือนของ @iMacbaszii) เลยอู้ไว้ก่อน ไม่ยอมย้ายมาใช้แบบนี้ซักที

บังเอิญว่าวันนี้คิดไงไม่รู้ (ว่างจัด?) ตอนแรกว่าจะย้ายทั้ง blog ของเราไปอยู่บน GitHub Pages แล้ว เพราะถูกโฉลกกับ markdown มากๆ แต่ลองได้แป๊ปๆ รู้สึก learning curve มันสูงเกินไป อีกทั้งพวก comment เก่าๆ ก็ไม่น่าจะย้ายตามมาได้ด้วย

ตัดใจได้ก็เปลี่ยนมานั่งหาวิธี hack เอา syntax highlighter ใช้กับเจ้า blogger จนได้ (จริงๆ เปลี่ยนค่ายไปใช้ prettifier แหละ) ก็ต้องขอขอบคุณ Alex Conrad ผู้เขียน bootstrap-lib อันนี้ไว้ให้ด้วยเน้อ ;)

ตอนนี้ก็ติดอยู่อย่างเดียวตรงที่พื้นหลังของแต่ละ post มันเปลี่ยนเป็นสีเข้มๆ ไม่ได้ --- แต่ก็ช่างมันเหอะ จะได้ออกจากโลกมืดๆ ซะบ้าง หลังจากใช้สีดำเป็น default มาตั้งแต่ปี 5 ปีก่อน (ก่อนหน้านั้นอีกตอนอยู่ที่ spaces.live.com ใช้สีขาวนะเออ) ก็หวังว่าจะอ่าน blog กันได้สบายตาขึ้นนะครับ TwT

เสียดายอย่างสองอย่างตรงที่ theme dynamic view นี่มันแสดง facebook like box ไม่ได้แล้ว พวก feed aggregator ก็เอามาแปะข้างๆ ไม่ได้ (งานนี้ tutuor0x อดอัพตาม เพราะมีสัญญาผูกพันกับ blognone อยู่ว่าต้องแสดง feed แลกกัน) ก็น่าเสียดายเล็กน้อยเหมือนกันแต่ไม่เป็นไร

อ๋อ นอกจากเปลี่ยน theme แล้ว ยังจัดระเบียบ tag ใหม่ด้วย คือเมื่อก่อนเนี่ยใช้ระบบ category มาตลอด ซึ่งก็เข้าท่าดีเพราะสมัยนั้นเขียนเป็นหัวข้อๆ ไป แต่พักหลังนี้รู้สึกว่าเขียนกระจัดกระจายมากเลย คิดว่าเปลี่ยนมาใช้ tag น่าจะ work กว่านะ

ที่เหลือก็คงต้องหาภาพมาประกอบบทความแต่ละตอนบ้างแล้วสินะ ไม่งั้นเวลาดูแบบ flipcard แล้วจะรู้สึกโล้นๆ 555

Aug 18, 2012

ทำไม Functional Programming ถึงไม่ได้รับความนิยม


จากที่ @lewcpe คอมเมนท์ไว้ในตอนก่อน มานั่งคิดๆ ดูว่าทำไม functional programming ถึงไม่ได้รับความนิยมเท่าไหร่

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

sh:
echo 'hello to the old world' | tr ' ' '\n' | sort -r | paste -sd ' '
ruby:
puts 'hello to the old world'.split.sort.reverse.join ' '
แต่ในการเขียนโปรแกรมแบบ functional programming การทำงานของจะเป็นในทิศทาง "ซ้าย <- ขวา" แทน ซึ่งขัดกับความรู้สึกของเรา

python:
print(' '.join(reversed(sorted('hello to the old world'.split()))))
haskell:
print (join " " . reverse . sort . splitOn " ") "hello to the old world"


อีกเรื่องที่ยากคงเป็นเพราะไม่มี for/while loop มีแต่ recursion ให้ใช้ อันนี้สาย imperative ทำใจลำบากพอควรเลย

ส่วนเรื่องอื่นๆ อย่าง immutable คงไม่มีปัญหาเท่าไหร่ อันที่จริงถ้าไม่สนว่าจะ optimize กันสุดๆ ไม่ว่าจะเขียนด้วยภาษาอะไรก็ควรยึดเรื่องนี้ไว้หน่อย มันทำให้ bug งี่เง่าหายไปได้หลายตัวเลยหละ :3

Aug 17, 2012

Function Composition กับ Programming


ตอนม.ปลาย คงเคยเห็นฟังก์ชั่นที่เขียนแทนด้วยสัญลักษณ์ g o f กันมาแล้ว ซึ่งมันหมายความง่ายๆ เช่นนี้
(g o f) (x) = g(f(x))
(นิยามเต็มๆ ของมันคือ ถ้า f: X -> Y และ g: Y -> Z แล้ว g o f: X -> Z ---- ที่ต้องนิยามเช่นนี้เพราะเราจะสามารถละการเขียน argument x ติดไปกับนิยามได้)

ในการเขียนโปรแกรม (โดยเฉพาะเชิง functional) เรามักต้องทำงานแบบฟังก์ชันต่อเนื่อง คือ output จาก function หนึ่ง จะถูกนำมาใช้เป็น input ให้ฟังก์ชันถัดไปเป็นลูกโซ่

เขียนอธิบายเป็นภาษาโปรแกรมได้คือ
first_input = x
first_output = f(first_input)
second_input = first_output
second_output = g(second_input)
y = second_output
หรือเพื่อไม่ให้เปลืองตัวแปร ทั้งหมดนี้สามารถย่อได้เหลือ
y = g(f(x))
คำถามคือ ถ้าเราต้องการประกาศแค่ฟังก์ชั่นที่ทำงานต่อกันไปเรื่อยๆ เช่นนี้ โดยที่ไม่ต้องการใส่ input ให้ฟังก์ชั่นโดยทันที เราจะทำอย่างไร?

ทางออกหนึ่งคือใช้ lambda เข้าช่วย
h = lambda x: g(f(x))
แล้วเวลาจะเรียกใช้ฟังก์ชั่นนี้ ก็แค่สั่ง
y = h(x)
ฟังดูง่ายดี แต่นึกดูอีกที ทำไมเราถึงต้องทำอะไรให้มันยุ่งยากด้วยการเอา lambda เข้ามาเกี่ยวด้วย?

ในภาษา imperative คงไม่มีทางเลือกอื่น แต่สำหรับภาษา functional จ๋าอย่าง Haskell เราสามารถเขียนแบบนี้ได้
let h = g . f
จะเห็นว่าง่ายดายเหมือนกับ g o f ตอนแรกเลย เวลาใช้ก็แค่
h x
หรือถ้าจะละการประกาศฟังก์ชั่น h ทิ้งไป เพื่อหาผลลัพท์ทันทีเลย ก็ทำได้โดย
(g . f) x
อ่าห์... นี่มันคณิตศาสตร์ชัดๆ!!

Aug 16, 2012

ความเคยชิน


คิดมานานแล้วว่านิ้วก้อยซ้ายมันใช้งานน้อยมากๆ และปุ่ม CapsLock ก็ไม่โดนใช้เลย น่าจะเอาปุ่มนี้ไว้ใช้เปลี่ยนภาษา

เดิมเคยเปลี่ยนภาษาด้วย ~ (Thai-Windows style) มาก่อน ต่อมาใช้ Linux ที่ต้องใช้ปุ่ม ~ บ่อยๆ เลยเปลี่ยนเป็น Alt+Shift ซึ่งมันจะมีปัญหา lost focus บ้าง คราวนีเปลี่ยนอีกรอบ เลยจับเวลาที่ต้องใช้ปรับตัวซักหน่อย (เช่นเดียวกับที่เคยทำตอนเปลี่ยนจาก Emacs มาเป็น vi [1] [2])

ปรากฏว่าใช้เวลาแค่วันเดียวเท่านั้นเองแฮะ น่าแปลกใจมากๆ

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

สรุปว่าอย่าไปใจร้อนสินะ เวลาจะเปลี่ยนอะไรซักอย่าง (แต่ก็อย่าเอื่อยเฉื่อย เฉไฉไม่ยอมเปลี่ยนพอ)

Jul 10, 2012

ความประทับใจกับห้องสมุดวิทย์ฯ มช.

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

สัปดาห์ถัดมา จ่ายตังค์ค่าเทอมเรียบร้อยหนังสือที่จะยืมตั้งแต่คราวแรกแม่งหายอีก ไม่มีระบบจองหนังสือหรือไงวะ

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

เอาใหม่ อีกวันนึงเอาใบเสร็จค่าเทอมไปยืมจนสำเร็จ ระหว่างนั้นก็ไปออกบัตรนศ.ใหม่ด้วย เพราะใบเก่าหมดอายุ

... ผ่านไปสองอาทิตย์อ่านหนังสือจบ เอาไปคืน เห็นเล่มที่จะยืมตั้งแต่คราวแรกพอดี เลยหยิบไปเคาน์เตอร์ แม่ง บอกบัตรนศ.กูเป็นบัตรใหม่ บัตรมันไม่ link กับบัตรเก่า ยืมไม่ได้ ต้องไป activate บัตรที่สำนักทะเบียนก่อน

ห่าเอ้ย หมดความอดทนแล้วโว้ย

Jul 8, 2012

ข้าวมันไก่?

สั่งหมีขาวน้ำตก... บอกว่าร้าน "ไม่น่า" จะมี เอาเป็นเย็นตาโฟแทนมั้ย (ตรูไม่กินเย็นตาโฟเว้ย เอาแบบธรรมดามาไม่ได้เหรอ -- ว่าแต่ร้านก๋วยเตี๋ยวที่ไหนบ้าง ที่ไม่มีน้ำตกเนี่ย)

เอาใหม่ สั่งข้าวมันไก่ทอด... ได้ข้าวมันไก่ต้ม (ทั้งๆ ที่คราวนี้ร้านมันแขวนไก่ทอดไว้ข้างๆ กันเลย) - -"

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

ไม่ใช่ไม่อยากคิด (โว้ย) แต่พอคิดทีไร แม่ง "ไม่เคย" ได้อย่างคิดเลย

เอ่อ ไม่คิดดีกว่าหวะ

One More Time, One More Chance



วันนี้หยิบเอา 5cm ฉบับการ์ตูนที่เพิ่งออกมาอ่าน พบว่า part 1 เนือยๆ ทำบรรยากาศได้ดีเหมือนกับในหนัง คือก็ไม่น่าเบื่อซะทีเดียว แต่ก็เหมือนว่าตอนนี้ไม่มีจุด peak เอาซะงั้น (มันหลบหายไปพร้อมกับการแสดงออกของตัวเอกนั่นแหละ)

อ่านจบ part 1 จะวางละ ง่วงนอน+ดูจากความหนาแล้ว part 2 ไม่น่าจะเล่าจบได้ในเล่มนี้... แล้วเล่มต่อไปจะออกเมื่อไหร่ก็ไม่รู้ กลัวลงแดงตายซะก่อน...

แต่จนแล้วจนรอด ก็หยิบ part 2 มาอ่านต่อจนได้ ซึ่งต้องยอมรับว่า ประหลาดใจ (แบบไม่ประหลาดใจ) พอสมควร เพราะเนื้อเรื่องเล่าไว้ละเอียดกว่าในหนังมากๆ (ก็ไม่น่าแปลก part 1 ในหนังได้เวลาไปตั้งครึ่งนึงเลย part 2, 3 ก็โดนตัดฉับๆๆ ซะ)

อ่านจบเล่ม (แต่ไม่จบ part) ทนไม่ไหว มาเปิดอ่านภาษาอังกฤษในเน็ตต่อซะ เลยเข้าใจเรื่องราวขึ้นอีกเยอะเลย

และเสียน้ำตาอีกตามเคย TwT

ไปๆ มาๆ รู้ตัวอีกทีก็นั่งดู MV ตัวข้างบนอีกแล้ว...

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

... แต่จริงๆ แล้วนะ การดับความหวังไปซะเลย ก็ยังดีกว่าปล่อยให้หวังลมแล้งๆ แทบไม่มีทางเป็นจริงสินะ

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



ปล. อันข้างล่างนี่ สำหรับใครที่อยากให้จบแบบ happy ending นะ :')

Jun 13, 2012

อยากทำ อย่างที่ 5 โปรแกรมแก้ไขเอกสารที่มองภาพรวมเป็นต้นไม้

วันนี้เห็น @awkwin ทวีตเกี่ยวกับความ *** สุดจะบรรยายของ JavaScript เลยได้ฤกษ์ขุดกระทู้ในตำนานมาอ่านอีกรอบ แล้วก็ได้สัมผัสถึงความ !@#$%^&* ของภาษา APL ซึ่งคอมเมนท์ในนั้นบอกไว้ว่า มันคงเป็นภาษาที่เจ๋งและกี๊คมากเลยถ้าอยู่ในหนัง ก็เลยทำให้นึกถึงหนังเรื่อง Stealth ตอนที่ศูนย์คอมเข้าไปดูโค้ดของโปรแกรมเพื่อหาบั๊ก (ส่วนนี้ในหนังกากมาก เพราะแม่งเล่นง่าย เอาโค้ดส่วนที่เป็นบั๊กไปไว้ "ระหว่างบรรทัด" เลย ซึ่งในความเป็นจริงโค้ดมันจะซ้อนอยู่ตรงนั้นได้ยังไงวะ) ถึงตอนนี้ก็ฉุกคิดได้ว่า เอ่อ ไม่เห็นจะเจ๋งตรงไหนเลย ถ้ามันแต่งมาเวอร์ๆ แต่ไม่มีทางเป็นไปได้ในโลกความเป็นจริง ...

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

ถอยกลับมา 1 ก้าว.. เอ่อ แต่ถ้าได้เขียนโค้ดแบบ 3 มิติจริงๆ คงเจ๋งไม่น้อยเลยนะ ก็เลยลองออกแบบว่า ไอ้การเขียนโค้ด 3 มิติ (ถ้ามันจะมีจริง) มันควรจะเป็นยังไงกันนะ?

คิดไปเรื่อยๆ โดยอิงจากตัวอย่างในปัจจุบัน ก็พบว่ารูปแบบโค้ดโดยทั่วไปจะออกแนวๆ นี้



ซึ่งถ้ามองในมุมของโปรแกรมเมอร์ที่เชี่ยวแล้วคงไม่มีปัญหา (เริ่มอ่านโปรแกรมจาก main ด้านล่างสุด แล้วโดดไปดูคลาส/ฟังก์ชันแค่เวลาที่ต้องการข้อมูลเพิ่มเติม)

แต่สำหรับโปรแกรมเมอร์มือใหม่ (อย่างผม) นี่ทำให้เกิดปัญหาที่เรียกว่า "เริ่มไม่ถูก" ขั้นรุนแรง ตั้งแต่เริ่มไม่ถูกว่าจะดูโค้ดที่ไหนก่อนดี? ทำไมต้องมองโปรแกรมตามแบบที่คอมพิวเตอร์ทำงานโดยการเริ่มจาก import requirement/dependency ต่างๆ ให้ครบก่อนแล้วจึงจะเริ่ม main ได้? ทิศทางการเขียนโปรแกรมควรจะเขียนแบบ top-down หรือ bottom-up กันแน่?

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



โดยรวมแล้วก็คงคล้ายๆ กับ Inspect Element ของเว็บเบราว์เซอร์ คือเริ่มดูจาก root node ไล่ลงไปเรื่อยๆ นั่นเอง เพียงแต่เปลี่ยนการ expand tag ไปเป็นการเปิดหน้าต่างใหม่แทน

รายละเอียดปลีกย่อย (ถ้าจะทำจริงๆ) ก็คงต้อง highlight สีของฟังก์ชันที่ยังไม่ได้เขียนกฎให้เด่นหน่อย ส่วนถ้าฟังก์ชันไหนเขียนไปแล้วแต่ติดแท๊ก FIXME หรือ TODO ไว้ ก็เปลี่ยนไป highlight ด้วยอีกสีหนึ่ง (จะได้รู้ว่ามีข้อผิดพลาดที่ฟังก์ชันนี้ และกลับมาแก้ไขทีหลังได้ง่าย) แล้วก็คงต้องดักพวก recursion ด้วย ส่วนการเขียนเพิ่มฟังก์ชันใหม่ๆ เข้าไป ต้องเอามันไปแทรกไว้ก่อน main เสมอ

คิดออกแค่นี้ ง่วงแล้ว ลองนอนดูก่อนว่าจะหายบ้ามั้ย 555

Jun 2, 2012

Opensource ไม่ใช่ของฟรี, คนทำไม่ได้อิ่มทิพย์

เรื่องหนึ่งที่เข้าใจผิดๆ กันมากเกี่ยวกับวงการไอที คือความคิดที่ว่า opensource คือของฟรี การนำ opensource มาใช้งานมีค่าใช้จ่ายเป็นศูนย์

อันที่จริงก็มีหลายคนเขียนถึงเรื่องนี้ไว้หลายครั้งแล้ว: 0, 1, 2 (ฯลฯ ที่ผมไม่สามารถหาเจอได้แล้ว) ซึ่งตอนนั้นผมเองอ่านบทความเหล่านั้นก็ยังไม่ค่อยเข้าใจเท่าไหร่ จนกระทั่งมาได้ทำงานกับ opensource จริงจัง

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

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

จาก 2 เคสที่ยกมานี้ จะเห็นว่าตัว opensource ไม่ได้จ่ายเงินให้นักพัฒนาเลย แต่เป็นผู้ว่าจ้างนั่นเองที่จ่ายเงินให้นักพัฒนาโดยตรง และโปรเจค opensource ก็โตได้เพราะเงินในส่วนนี้

ใช่ครับ opensource ไม่ใช่ของฟรี และคนทำก็ไม่ได้อิ่มทิพย์!!

May 17, 2012

ข้าวเย็น-แม่หลับรอ-ลูกกลับบ้านดึก

นอนไม่หลับ เลยตกตะกอนเรื่องตอนเย็นๆ จดเก็บไว้หน่อย เผื่อแก่ไปมีลูกเป็นวัยรุ่นจะได้กลับมาอ่านบ้าง...

ช่วงนี้เห็นโฆษณาหลายตัวเล่นประเด็นเกี่ยวกับความสัมพันธ์ในครอบครัว ตัวละครส่วนใหญ่ก็จะเป็นลูกชายกับแม่แค่สองคน (น่าสนใจมากว่าพ่อหายไปไหน ประเด็นลูกชาย-แม่มัน mass ขนาดนี้เลยหรือ?) และหนึ่งในเรื่องที่โฆษณาแทบทุกตัวหยิบมานำเสนอคือ ข้าวเย็น-แม่หลับรอ-ลูกกลับบ้านดึก



ผมก็เคยเจอกับเหตุการณ์นี้ครับ มันไม่สนุกหรอก ที่ต้องเห็นใครบางคนฟุบหลับบนโต๊ะอาหารที่เปิดไฟรอเรา ทั้งที่เค้ามีสิทธิ์เลือกสิ่งที่ดีกว่าอย่างขึ้นไปนอนเปิดแอร์บนเตียงดีๆ

คุณอาจคิดว่านี่เป็นภาพที่แสดงออกถึงความรัก ผมไม่เถียงนะ แต่คำถามคือ มันสมควรแล้วหรือ?

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

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

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

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

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

... ใครว่าความรักอันเปี่ยมล้นของพ่อแม่ไม่มีทางไม่ดีครับ? ลองคิดดูใหม่นะ

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

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

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

Apr 27, 2012

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


สืบเนื่องจาก @Blltz กับ @ngfar มาเที่ยวบ้าน เลยนั่งปั่น Daiblo II กันตั้งแต่ตีสองตีสามจนถึงเจ็ดโมงเช้า กว่าจะเริ่มทำโจทย์ (ซึ่งควรจะเริ่มทำตั้งแต่หกโมงเช้าเลย)

ข้อแรก ง่ายๆ แค่ substitution cipher ธรรมดา ก็โยน input เข้าไป map กันแล้วสร้างเป็น dict ออกมา ทีนี้เวลามันเหลือ เลยเล่นกับ code ซักหน่อย ให้คนตามอ่านปวดหัวเล่น (ของจริงมันอยู่ในบรรทัดเดียว แต่อันนี้แยกบรรทัดให้อ่านง่ายๆ ละนะ) :P
print('\n'.join(
    'Case #{}: {}'.format(
        t+1,
        ''.join(d[c]
            for d in [dict(zip('abcdefghijklmnopqrstuvwxyz ',
                               'yhesocvxduiglbkrztnwjpfmaq '))]
            for c in input()))
    for t in range(int(input()))))
แล้วก็เพิ่งมานึกได้ว่า ไม่ต้องสร้างเป็น dict ก็ได้นี่หว่า...



ข้อถัดมา อันนี้ทดในกระดาษ กระจายรูปแบบออกมาแล้วจะพบว่า ถ้าคะแนน mod 3 แล้วได้ 1 ไม่มีทางที่จะเป็นเซอร์ไพรส์ เลยดูแค่ที่มันไม่ใช่พอ แล้วก็เพิ่มข้อยกเว้นคะแนนแค่ 0 กับ 1 ด้วย
from math import ceil

for t in range(int(input())):
    gg, surprise, expected, *point = (int(n) for n in input().split())
    point.sort(reverse=True)

    count = 0
    for p in point:
        if ceil(p/3) >= expected:
            count += 1
        elif surprise > 0 and ceil(p/3)+1 >= expected:
            if p%3 == 1 or p in (0, 1):
                continue
            count += 1
            surprise -= 1

    print('Case #{}: {}'.format(t+1, count))
ถ้าดูตาม code แล้ว จะเห็นว่าเริ่มมาสั่ง sort ไว้เลย (จริงๆ ไม่ต้องก็ได้) คือตอนนั้นเตรียม optimize ไว้แล้ว แต่เท่าที่จับเวลาดูไม่น่าเป็นห่วง เลยไม่ optimize ดีกว่า เดี๋ยวเจอ bug ซ่อนเร้น



ข้อสาม สมองเริ่มตายเพราะไม่ได้นอนทั้งคืน + เห็นคำว่า recycle เลยเขียนแบบ recursive (ที่ไม่จำเป็นต้อง recursive) ซะเลย 555+
def recycle(m, r):
    global pair, count
    if r > 0:
        m = m%digit * 10 + int(m/digit)
        if lower <= n < m <= upper and m not in pair.setdefault(n, []):
            pair[n].append(m)
            count += 1
        recycle(m, r-1)

for t in range(int(input())):
    lower, upper = [int(n) for n in input().split()]
    round = len(str(lower)) - 1
    digit = 10 ** round

    pair = {}
    count = 0
    for n in range(lower, upper+1):
        recycle(n, round)

    print('Case #{}: {}'.format(t+1, count))
อันนี้ optimize นิดหน่อย ตอนแรกทำการกลับเลขโดยแปลงเป็น str -> สลับที่ str -> แปลงกลับเป็นตัวเลข ปรากฎว่ากินเวลาไปเกินกว่ารับได้ เลยเปลี่ยนวิธีเขียนเป็น math ถ้า test แค่ส่วนกลับเลขก็เร็วกว่าเป็นสิบเท่าเหมือนกัน แต่ไม่ได้ลอง test ทั้ง script ดูว่าเร็วจนน่าพอใจหรือเปล่า

อนึ่ง พอเอาไปรันใน pypy แล้วไม่มีปัญหาเรื่องความเร็วเลยแฮะ เสียเวลา optimize ทำไมเนี่ย

(ข้อนี้แอบโดน @lewcpe ว่าตรงที่ทำไมใช้ global ไม่ return เอาตามธรรมดาวิสัยฟังก์ชั่นทั่วไปด้วย)


พอทำ 3 ข้อแรกเสร็จก็ออกไปเล่นสงกรานต์ กลับมาตอนดึกๆ นั่งมึนหัวอยู่พักนึงแล้วก็คิดวิธีแก้ปัญหาข้อ 4 ได้ (แบบเดียวกับเฉลยบนเว็บ คือสร้างภาพแบบ reflex แล้วใช้วงกลมร้อมรั้วนับเอา) แต่ตอนนั้นคิดวิธี implement ดีๆ ไม่ออกแล้ว แถมเท่าที่ลองเขียนเล่นๆ ก็คิดว่าน่าจะต้องเจองานหนักกว่า 200 บรรทัดแน่ๆ (เวลาไม่น่าพอ) และยังเลือกไม่ถูกอีกว่าจะเขียนแบบ OOP หรือ functional ดี เลยตัดสินใจนอนดีกว่า

แล้วเจอกันรอบ 1A ครับ ~

Feb 25, 2012

คอนเสิร์ตโนดาเมะ


คราวนี้จัดที่กาดสวนแก้ว ใกล้บ้านขนาดนี้ ยังไงก็ไม่พลาดอยู่แล้ว

Beethoven Symphony No.7
เป็นเพลงแรกที่ถูกนำมาเล่นในคอนครั้งนี้ น่าเสียดายว่าเป็น short version คือ rearrange ให้แต่ละท่อนมีแค่ part ใจความสำคัญแค่ครั้งเดียว ไม่มีการย้อนความซ้ำอีกรอบตามรูปแบบของ symphony โดยทั่วไป และระหว่าง movement ก็เล่น (เกือบจะ) ต่อกันทันทีเลย สงสัยว่าไม่อยากให้ปรบมือคั่นระหว่าง movement? สำหรับตัวเพลงนั้น movement แรกเล่นได้ช้ามาก (น่าจะเป็นไปตามสไตล์ของอาจารย์สมัคร กาใจคำอย่างนั้นเอง?) ทั้งที่น่าจะทำให้กระฉับกระเฉงมากกว่านี้ได้อีกหน่อย พอเข้า movement 2 ก็ขาดซึ่งเสียงแตรมา intro ปลุกใจ ข้ามไปยัง part violin เลย ส่วน movement ที่ 3 และ 4 นั้น ยังขาดความสง่างาม เกรียงไกรอย่างที่มันควรเป็นอยู่ครับ

Beethoven Spring Sonata - Allegro
แต่เพลงที่สองนี้ผมเทใจให้หมดเลยนะ piano ของคุณเรมีย์ นามเทพกับ violin ของอาจารย์อภิรัตน์ ประพันธ์วงศ์เล่นประสานกันได้ลงตัวมาก เสียอย่างนึงคือนักดนตรีไม่ได้ซ้อมถึงขั้นที่ไม่ต้องดู score แล้วจังหวะที่พลิกกระดาษเปิดหน้าถัดไปนั้นเสียงดังมาก คนฮือฮากันใหญ่ (ทำไมก็ไม่รู้)

Beethoven Pathetique Sonata - Andante Cantabile
เพลงนี้ก็เอาใจไปให้หมดเลยเหมือนกันครับ ชอบๆๆ (อ๋อ ผู้เล่นคือคุณเรมีย์คนเดิมครับ) แต่เสียดายว่าตอนใกล้ๆ จบเพลง มีเด็กอายุไม่น่าเกิน 5 ขวบเสียงดังขึ้นมา (ทำไมฟระ คนกำลังอิน) แย่ๆๆ

Gershwin Rhapsody in Blue
แล้วก็กลับมาที่เพลงแบบวงใหญ่อีกครั้ง ก่อนเริ่มเพลงแน่นอนว่าต้องเทียบเสียง เด็กคนนั้นนั่นแหละก็เทียบเสียงไปกับเค้าด้วย (เอ่อ จะว่าไปมันก็ตลกนะ... แต่ตอนนั้นขำไม่ออกกับมารยาทของผู้ปกครองแฮะ) แล้วเด็กคนนั้นก็ส่งเสียงเจี๊ยวจ๊าวตลอดเพลงซะอีก (ทำไมไม่พาลูกกลับบ้านไปเลย?) ส่วนเนื้อหาเพลงนั้นก็ปรกติทั่วไปดีครับ ได้คุณ Soo Hwan Cha มาเล่น piano คู่กับการคอนดักต์ของอาจารย์ชัยพฤกษ์ เมฆรา แต่โดยส่วนตัวแล้ว เพลงนี้ผมหลับแฮะ.. (เฉยๆ กับเพลงกึง Jazz กึ่ง Classic เป็นทุนเดิมอยู่แล้ว)

Rachmaninoff Piano Concerto No.2
นับได้ว่าเป็นไฮไลท์ของงานเลยทีเดียวกับเพลงนี้ แต่ผมว่าฝีมือ piano ของคุณ Atsuko Seta ยังไม่แกร่งกล้าพอที่จะฝ่าฟันทะเล orchestra ไปได้แฮะ หลายๆ ครั้งที่เสียง piano ของเธอนั้นโดนกลืนหายไปในพายุอันหนักหน่วง หนำซ้ำ แม้ว่าพายุร้ายจะได้สงบลง เธอกลับไม่สามารถฉายสายรุ้งสวยงามทั้งสองวงได้ ที่สำคัญ เธอจำโน้ตผิดไป 1 ห้อง (อีกแล้ว) ครับ ทางฝั่ง orchestra ก็เลวร้ายไม่แพ้กัน มีท่อนนึงที่เครื่องเป่าเกือบจะทำเพลงล่มเพราะหายไป 2 ห้อง และเฟรนช์ฮอร์นเล่นเสียงดังเกินไปด้วย -___-"

แต่โดยรวมก็ถือว่า คุ้มกับบัตรราคานักศึกษาครับ ยิ่งถ้ารู้ว่าวง orchestra เป็นวงเยาว์ชนที่อาศัยแค่ซ้อมช่วงเย็นด้วยแล้ว น่าจับตามองจริงๆ ว่าถ้าเราตั้งวง orchestra เป็นเรื่องเป็นราว น่าจะไปได้ไกลกว่านี้มากเลยนะ

Feb 16, 2012

ซ้อม CodeJam


ช่วงนี้ซ้อม CodeJam อยู่ หวังว่าจะได้เสื้อ Google มาใส่เล่น (โดน @lewcpe ยั่วให้อยากแข่ง) เลยว่าจะจดรายระเอียดเอาไว้หน่อย เผื่อว่าใครอยากจะลงเล่นแย่งชิงเสื้อไปด้วย อิอิ

การเขียนโปรแกรมแก้โจทย์:
- ใช้ภาษาอะไรก็ได้ ขอแค่มี compiler เวอร์ชันฟรีให้กรรมการเอาไว้ทดสอบก็พอ (Mathematica อด)
- ส่วน editor/ide จะใช้อะไรก็ได้ อันนี้ฟรีสไตล์เลย
- แต่ระบบคิดคะแนนคือโหลด test case มารันในเครื่องตัวเอง แล้วส่งคำตอบกลับไป ไม่ใช่รันที่ฝั่ง server ปิดบัง test case
- ดังนั้น test case ที่ให้มาก็จะมี time out เพื่อป้องกันการที่เราโหลด test case มาศึกษานั่นเอง
- และเวลาส่งคำตอบ ต้องแนบไฟล์โปรแกรมของเราไปด้วย

คำถามแต่ละข้อ:
- ในหนึ่งคำถาม จะมี test case ให้ 2 ชุด (ง่ายกับโหด)
- อ่านโจทย์ + เขียนโปรแกรมให้เสร็จก่อน แล้วค่อยโหลด test case
- test case ข้อง่ายเมื่อโหลดมาแล้ว มีเวลา 4 นาทีในการหาคำตอบ และส่งข้อมูลกลับไปให้ตรวจ
- ส่งคำตอบผิดไป ทางโน้นจะบอกว่าผิด แต่ยังให้ส่งใหม่ได้เรื่อยๆ จนกว่าเวลา 4 นาทีจะหมด
- ถ้าเวลาหมดก็ต้องโหลด test case ชุดใหม่มาคิด
- เมื่อส่งคำตอบที่ถูกต้องไปแล้ว ถึงจะโหลด test case แบบโหดมาทำได้
- test case โหดให้เวลา 8 นาที แต่จะไม่บอกตอนแข่งว่าถูกหรือผิด แถมคราวนี้เวลาหมดแล้วหมดเลย (ระวังให้ดี)

ในหนึ่งรอบต้องเจอ:
- แต่ละรอบจะมีคำถามให้ 3 ข้อ เรียงจากคะแนนน้อยไปมาก (อาจจะถือว่าเรียงง่ายไปยากด้วย)
- ถ้าคิดว่าทำไม่ทันทั้ง 3 ข้อ ควรเก็บข้อแรก (ง่ายสุด) แล้วข้ามไปเก็บข้อสุดท้ายเลย จะได้คะแนนเยอะกว่าเก็บข้อแรกกับข้อที่สอง
- แต่ถ้าคิดจะทำข้อสุดท้ายด้วยการ recursive - brute force ไม่พึ่ง dynamic programming ทำใจได้เลยว่าผ่านแค่ test case แบบง่ายเท่านั้น
- เพราะฉะนั้น จะเขียน algorithm ธรรมดาเก็บ test case แบบง่ายทั้งหมดก่อน (อย่างรวดเร็ว) แล้วค่อย optimize เพื่อลุย test case โหดก็ได้

รอบที่แข่งขัน:
- รอบ qualification ให้เวลา 24 ชั่วโมง แต่ถ้าจะจริงจังกับเสื้อ ควรทำรอบนี้ให้เสร็จใน 2 ชั่วโมง เพราะคำถามง่ายมาก
- เมื่อผ่าน qualification แล้ว ก็จะพบกับรอบที่ 1 ซึ่งมีให้เลือกเล่น 3 รอบย่อย
- จะแข่งรอบย่อยกี่รอบก็ได้ ขอแค่ผ่านรอบย่อยอันใดอันหนึ่งก็พอแล้ว (เข้ารอบประมาณรอบย่อยละ 1000 คน)
- รอบย่อยของรอบที่ 1 จะเริ่มโหดขึ้นมาบ้างแล้ว ควรเก็บให้ได้อย่างน้อย 2 ข้อเต็มๆ (เวลาแข่งประมาณ 2.5 ชั่วโมง)
- ถ้าไม่มีปัญหาอะไร ก็จะได้ผ่านเข้าไปแข่งรอบที่ 2
- รอบนี้จะยากขึ้นมาอีกระดับเลย เก็บได้ข้อนึงเต็มๆ กับอีกข้อที่ผ่านแค่ test case ง่ายก็มีสิทธิ์ได้เสื้อแล้ว
- จะผ่านเข้ารอบที่ 3 ได้ต้องติด 500 อันดับแรก ส่วนถ้าจะหวังเสื้อก็ติด 1000 อันดับแรก

เอาให้ได้ถึงแค่นี้ก่อน ถ้าติดถึงรอบ 3 มหาโหดจริงจะมาเขียนต่อ (ตอนนี้หวังแค่เสื้ออย่างเดียว)

Feb 9, 2012

การศึกษาไทย

ออกตัวก่อนว่าผมไม่ได้เรียนสายคอมฯ โดยตรง แต่ด้วยความที่ชอบคอมพิวเตอร์อยู่บ้าง ก็อยู่คลุกคลีกับมันบ่อยๆ

เรียกว่าเป็นความโชคดี (หรือโชคร้ายกันแน่) ของผมเอง ที่ไม่ได้เริ่มจากภาษา C/Java อย่างที่คนเรียนสายนี้มักจะทำกัน แต่ไปเริ่มกับภาษาชั้นสูงเลยอย่าง Python/JavaScript ทำให้ผมไม่ค่อยรู้เรื่องรู้ราวอะไรกับการศึกษาในมหาวิทยาลัยของสายนี้เท่าไหร่

แถมไอ้ตอนที่ผมเรียนรู้เอง ก็ไม่ได้ค่อยๆ เรียนค่อยๆ สร้าง tool แต่ละอย่างตามที่สอนในห้องอีก อยากได้อะไรก็ถาม Stack Overflow เอาเป็นที่แรก พวก tool ต่างๆ ก็เลยไม่ค่อยได้เขียนเอง import lib เข้ามาใช้เป็นประจำ

มันเลยทำให้ผมโคตรไม่เข้าใจว่า ถ้าจะรับค่าตัวเลข 0-6 แล้วส่งคืนเป็นวันอาทิตย์-เสาร์ ทำไมต้องเขียน if-else เพื่อตรวจเอาเองด้วย ทั้งๆ ที่เราก็มี strptime-strftime เอาไว้ให้ใช้อยู่แล้ว...

แล้วข้อสอบก็ออกให้ตรวจสอบ leap year ผมก็เขียนไปอย่างนี้ (ข้อสอบเป็นภาษาอื่น ไม่ใช่ Python)
def is_leap(year):
    if not year%400 or (year%100 and not year%4):
        return True
    else:
        return False
ปรากฏว่าอาจารย์ไม่ตรวจให้ เพราะผมไม่ได้ใช้ nested-if-else อย่างที่สอนในห้อง!!



วันนี้ซ้อมโจทย์ CodeJam เล่นๆ แล้วก็คิดว่า เอ่อ! โจทย์หยั่งงี้แหละ ที่มันควรเอาไปออกเป็นข้อสอบ

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

สรุปแล้ว ผลการสอบของการศึกษาไทยมันบอกแม่งอะไรวะ?

Feb 2, 2012

Inception ส้นตีน

(ไม่ได้ด่าหนัง Inception นะครับ) วันนี้เห็นน้อง @srakrn อัพเรื่องตรรกะส้นตีนแล้วคิดถึงที่พี่ @lewcpe เคยเขียนไว้เช่นกัน

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

... ก่อนจะบอกว่าประชาธิปไตยๆ มึงเคยเข้าใจคำว่าสิทธิและเสรีภาพมั้ยวะ?

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

ส้นตีนเถอะครับ

Jan 15, 2012

จะเช็คถูกอะไรกับชีวิตนักหนา

หลายวันก่อนมีเรื่องให้ได้แวะเข้า Windows แล้วก็เจอนี่


ก็ยังสงสัยอยู่ว่า มันจะเช็กถูกอะไรกับชีวิต (กู) นักหนา (วะ) ออกแบบ icon อย่างอื่นไม่เป็นหรือไง?

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

เพียงแต่บางทีมันก็กดดันไปนะ ถ้าทุกๆ เรื่องมีกฎเกณฑ์ข้อกำหนดตายตัว ว่าเราต้องอย่างโน้นอย่างนี้อย่างนั้น เพื่อให้ผลลัพท์สุดท้ายออกมาถูกต้องหมด โดยที่ไม่มีดินแดนให้ได้ลองผิดลองถูกบ้างเลย

Jan 4, 2012

เล่นกับ Twitter & App Engine


หลายวันก่อนไปกิน Sizzler กับ @CrystalWingHero มา ก็เล่าให้ฟังว่าทำโปรเจคจบกับ @nemopear ในหัวข้อที่จะย่อ twitter ให้สั้นลง (ได้อีก) ฟังแล้วก็น่าสนใจดีครับ scope ค่อนข้างใหญ่มาก คือข้อความที่ถูกส่งเข้าไปในระบบจะถูก encode ด้วยวิธีต่างๆ ตามที่ผู้ใช้เลือก (ย่อแบบธรรมดา, ย่อแบบเกรียน, ฯลฯ) แล้วก็ยิงเข้าไปโพสต์บน twitter สำหรับตัวโปรแกรมนั้นจะเรียกผ่าน API ที่สร้างจาก Google App Engine อีกทอดหนึ่ง แน่นอนว่าโปรเจคเมพๆ อย่างนี้ มีอาจารย์ @pruet เป็นที่ปรึกษาครับ :D

ด้วยความสงสัยของผม เลยหยิบเอา tweepy มาดัดแปลงเล่นนิดหน่อย ผลที่ได้ออกมาก็เป็นอย่างนี้ครับ



เนื่องจากเสียเวลางมผิดจุดไปเยอะมาก (เยอะเกินไป อ่าน dev.twitter.com ทำไมก็ไม่รู้ อ่าน stackoverflow คงทำเสร็จด้วยเวลาน้อยกว่ากันครึ่งๆ) แถมยังเป็นโปรเจคที่ทำขำๆ จากการหยิบยืมไอเดียเพื่อนมาทดลองเล่นช่วงปีใหม่ว่างๆ อีก ตอนนี้ก็เลยมีแต่ client อย่างเดียวไม่มี API บน App Engine และไม่พัฒนาต่อแล้วครับ (ส่วน code หาดูได้ที่ github เลยครับ)

อ๋อ สุดท้ายนี้ก็สวัสดีปีใหม่ครับ :)

Jan 1, 2012

ปี 2011 ของฉัน

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

  • ฝึกงาน Blognone - ไม่น่าเชื่อนะ ว่าจะได้ไปลองฝึกงานดู จากคนที่ไม่เคยทำอะไรเป็นมาก่อน ก็ได้เรียนรู้อะไรประหลาดๆ ใหม่ๆ เยอะเลย แล้วก็ฝึกงานผ่านซะด้วยแหละ
  • แข่ง HLP Hackathon - อันนี้ก็ไม่น่าเชื่ออีกเหมือนกัน เพราะคนเก่งๆ เค้าก็ลงคัดตัวกันตั้งเยอะ งานนี้โชคดีได้เจอเพื่อนใหม่หลายๆ คนเลย ^^
  • พิมพ์สัมผัส - ยากเหมือนกันนะอันนี้ แต่ก็ยังไม่ถึงขั้นเล่นเกม The Typing of the Dead ผ่านนะ
  • ฟรีสไตล์ Tetris - อันนี้ก็ใช้เวลาซ้อมๆๆ แล้วก็ซ้อมนานมากเหมือนกัน แต่ก็ยังไม่ได้ไปคาเนกี้ฮอล์ :P
  • เรียน Coding Theory - สนุกดี สนุกมาก
  • Reunion เพื่อนม.6 - ในที่สุดเพื่อนๆ เราก็จัดงานกันเป็นซักที (รอบก่อนๆ เราต้องตัดเองหมด) แม้จะเป็นการรวมกลุ่มเล็กๆ แต่ก็มีความสุขที่เจอเพื่อนที่ไม่ได้เห็นหน้าเป็นปีเหมือนกันนะ

ถ้าปีหน้าไม่ลืมจะจดใหม่ :)

ปล.ตอนเที่ยงคืนปีใหม่พอดีกำลังขับรถไปซื้อ coke มากิน 555+