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)

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