Nov 18, 2014

แอพ Calendar ตัวใหม่ของ Android Lollipop มาพร้อมกับภาพพาดหัว

ตอนนี้เวลาจดเหตุการณ์ใหม่ในแอพ calendar มันจะโชว์รูปด้วยถ้ามี keyword ไปตรงกับคำเตรียมไว้

ดูแล้วน่ารักดีครับ ลองหาคร่าวๆ เจอเท่านี้ ใครเจออะไรอีกสะกิดบอกกันได้ :D

Nov 3, 2014

Code Mania 01 และทำไมถึงควรเลิกเขียน JavaScript

เสาร์ที่ผ่านมาไปพูดเรื่อง "เลิกเขียน JavaScript กันดีกว่า" ในงาน Code Mania ที่จุฬาฯ มาครับ

คุณ @KaizerWing ให้เวลาเตรียมตัวล่วงหน้ามาเป็นเดือนแล้ว ตอนนั้นหัวโล่งๆ ยังมองภาพไม่ออกว่างานจะออกมาหน้าตายังไง เลยเลือกเรื่องที่คุ้นมือมากที่สุดในตอนนี้อย่าง CoffeeScript ไปพูด

แต่ครั้นจะพูด CoffeeScript เพียวๆ เลยเดี๋ยวคนจะตามไม่ทัน เลยหาจุดเชื่อมโยงเป็น JavaScript ที่มีจุดประหลาดน่าสนใจและคนเขียนเยอะมาปูเรื่องครับ

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

สไลด์อยู่นี่ครับ ส่วนทีมงานบอกมาว่ากำลังตัดต่อวิดีโออยู่ เรียบร้อยเมื่อไหร่เดี๋ยวผมมา update อีกที



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

Oct 29, 2014

City2048

เป็นเกมแบบเดียวกะ 2048 แหละครับ แค่เปลี่ยนจากตัวเลข ไปเป็นตึกแบบต่างๆ ไล่ลำดับแค่นั้นเอง


สนุกใช้ได้ครับ ภาพสวยไอเดียแจ่มด้วย แต่ช่วงแรกๆ ต้องจำรูปแบบตึกกันหน่อย ซึ่งเอาจริงๆ ก็ไม่ได้ยากอะไรมาก เพราะจะเล่นให้ถึง 2048 ก็จำตึกไป 10 แบบเอง (แบบแรกสุดที่เป็นฐานใช้รูปป่า -- ไม่นับนะ)

ส่วนวิธีคิดคะแนนจะต่างไปพอสมควร คือไม่ใช่คะแนนคูณสองไปเรื่อยๆ แล้ว แต่จะเพิ่มเป็นเส้นตรงครั้งละ 5 แทน

Oct 16, 2014

เปลี่ยน bio แล้ว

เมื่อก่อนรู้สึกว่าตัวเองเป็นคนจับฉ่ายมาก เลยตั้ง bio ทุกที่ไว้ว่า jack of all trades. ครับ

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

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

ส่วนตอนนี้ก็เปลี่ยนไปเป็น insufficient data for meaningful answer. กำกวมกว่าเดิมอีก :P

Oct 12, 2014

เรื่องธรรมดา

วันก่อนไปทำแบบทดสอบไอคิวแบบกึ่งจริงจังมาครับ

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

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

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

ส่วนผลลัพธ์คือผมได้ค่าไอคิว 132 คะแนน เมื่อคำนวณเทียบตามอายุปัจจุบัน

ตอนที่เพื่อนคำนวณคะแนนเสร็จ ถึงขั้นตกใจว่าคะแนนสูงมาก ไม่เคยเจอสูงเท่านี้มาก่อน (บอกประมาณว่า "เกือบอัจฉริยะ")

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

สำหรับพวกเค้าแล้ว ตัดคำว่า "เกือบ" นำหน้าทิ้งไป แล้วบอกตรงๆ ว่านี่คือ "อัจฉริยะ" ได้โดยไม่ต้องลังเล 555

ด้วยความอยากรู้อยากเห็น เลยกลับมาค้นข้อมูลดูจนพอจะสรุปได้ว่า แบบทดสอบมาตรฐานส่วนใหญ่ แม้กฎเกณฑ์การให้คะแนนจะต่างกันไปบ้าง แต่หลักคิดคือประชากรโลกมีจำนวนเยอะพอที่จะเอาหลักสถิติมาจับได้ เลยสร้างโมเดลไอคิวประชากรเป็นกราฟทรงระฆังคว่ำ ยืนพื้นที่ตัวเลข 100 คะแนนไว้ตรงกลาง แล้วให้การกระจายตัวที่ความเบี่ยงมาตรฐานหนึ่งหน่วย ตีเป็นคะแนนได้ 15 จุด

ยกตัวอย่างให้เห็นภาพ ถ้าสุ่มเลือกคนมา 100 คนแบบมั่วๆ จะพบคนที่มีไอคิวเกิน 130 (2 ความเบี่ยงเบนมาตรฐาน) อยู่เพียง 2-3 คนเท่านั้น

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


คะแนนไอคิวเทียบตามอาชีพ -- ภาพจาก BuzzFeed

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

ถึงจุดนั้น การมีไอคิวสูงก็ไม่ใช่เรื่องพิเศษแต่อย่างใด

Sep 29, 2014

Sukiyaki เพลงประกอบ MK

ไม่กี่ปีก่อนน่าจะมีคนจำได้กับเพลงประกอบโฆษณา MK ที่ร้องด้วยถ้อยคำผ่านทำนองอันแสงจะติดหูว่า

"เรากินสุกี้กิน MK เรากิน MK กินสุกี้ เรากิน MK สิ่งดีๆ มีติดตัวตั้งมากมาย"

พอเดาได้อยู่ว่าเพลงโฆษณาพวกนี้คือการเอาเพลงที่มีจริงๆ อยู่แล้วมาดัดแปลงใส่เนื้อร้องเข้าไปใหม่ (ไม่ได้แต่งหมดตั้งแต่เริ่ม)



เรื่องน่าแปลกคือเพลงนี้เดิมชื่อว่า 上を向いて歩こう (Ue o Muite Arukō) ถ้าแปลเป็นไทยสวยๆ หน่อยชื่อเพลงจะประมาณ "เงยหน้าสู้ฟ้าพร้อมก้าวไปต่อ" เนื้อเพลงก็ออกแนวตัดพ้อเรื่องความรัก แต่ก็ยังไม่ก้มหน้านะ เดี๋ยวน้ำตาจะรินไหล

คือเพลงทั้งเพลงไม่ได้มีข้อความอะไรเกี่ยวกับ "สุกี้ยากี้" ซึ่งเป็นชื่อเพลงที่คนนอกประเทศญี่ปุ่นรู้จักกันอย่างแพร่หลายเลย

คงจะคล้ายกับการที่เราตั้งชื่อหนังว่า "ต้มยำกุ้ง" ทั้งๆ ที่หนังทั้งเรื่องไม่มีอาหารที่ชื่อว่า "ต้มยำกุ้ง" โผล่มาให้เห็นเลยกระมั้ง :P

Sep 11, 2014

หนังสือ 9 เล่มในดวงใจ

เขียนไว้บน Facebook รอบนึงแล้วครับ ก๊อปมาแปะตรงนี้อีกรอบกัน blog ร้าง :P

  1. ไบเบิล - เนื่องจากเรียนโรงเรียนคริสเตียนก็เลยมีโอกาสได้อ่านผ่านๆ ตั้งแต่ตอนประถม แม้จะอ่านไม่จบ (ถึงอ่านจบตอนนี้คงลืมไปหมดแล้ว) แต่ก็ถือว่าเป็นหนังสือที่เปิดโลกด้านการนับถือศาสนาได้ดีทีเดียว นอกจากนี้ยังช่วยปูทางไปสู่ philosophy และ cosmology อีกด้วย
  2. ตำนานเทพเจ้ากรีกโรมัน - ฉบับภาษาไทยที่ไม่ได้แปลมาจาก Homer ตรงๆ แต่เป็นการเรียบเรียงเรื่องราวสำคัญของเหล่าเทพมาเล่า เล่มนี้มีจุดสำคัญที่ทำให้เรามองว่าเหล่าเทพเจ้านั้น สุดท้ายก็ไม่ต่างอะไรกับคนทั่วไป มีรักโลภโกรธหลงและทำผิดพลาดกันได้
  3. Stephen Hawking's A Brief History of Time - หลังจากมองในมุมศาสนาว่าพวกเขาหาคำตอบในชีวิตกันอย่างไรแล้ว ก็มามองในมุมวิทยาศาสตร์บ้าง ผมอ่านหนังสือตอเล่มนี้ตอนม.ต้นและเห็นว่าการให้เหตุผลแบบวิทยาศาสตร์มันเข้าท่ากว่าใช้ความเชื่ออย่างเดียวแบบศาสนา แม้จะอ่านได้ไม่เข้าใจทั้งเล่ม แต่ก็ทำให้ตั้งเป้าไว้ว่าอยากเก่งด้านวิทยาศาสตร์มากกว่านี้ อยากใช้ให้เหตุผลให้ได้เก่งกว่านี้ เพื่อที่จะได้นำไปตอบคำถามว่าชีวิตคืออะไรกันแน่
  4. แคลคูลัส 1 โดย วิรัตน์ สุวรรณาภิชาติ - อ่านตอนม.ต้นเช่นกันหลังจากอ่านเล่มที่แล้วจบ เป็นเล่มที่ไปยืนเลือกที่ร้านหนังสือแล้วพบว่าอ่านเข้าใจง่ายที่สุดแล้ว เลือกอ่านเล่มนี้เพราะต้องการอ่านเล่มที่แล้วให้รู้เรื่องนั่นแหละ (ซึ่งก็ไม่ช่วยเท่าไหร่) แต่ก็เปิดโลกด้านคณิตศาสตร์ได้ดีทีเดียว
  5. ทฤษฎีดนตรี โดย ณัชชา พันธุ์เจริญ - หลังจากสนใจแต่วิทยาศาสตร์มาตลอด (ยอมรับว่าช่วงนึงเคยดูถูกสายศิลป์ด้วยว่า แค่ละเลงสีมั่วๆ เป็นภาพก็ขายได้แล้ว) ก็มีเรื่องดลใจให้ไปหัดพังเพลงคลาสสิก ซึ่งตอนฟังอย่างเดียวก็รู้สึกแค่ว่ามันเพราะดีนะ แต่พอได้อ่านเล่มนี้แล้วเหมือนเปิดโลกเลยว่า ศิลปศาสตร์ไม่ได้เป็นเรื่องที่จะเอามาทำเล่นๆ ได้เลยทีเดียว
  6. Roger B. Nelsen's Proofs Without Words - แล้วศาสตร์กับศิลป์ก็มาบรรจบกัน กับหนังสือที่ดึงความสวยงามของคณิตศาสตร์ออกมาแสดงในเชิงศิลปะได้เป็นอย่างดี หนังสือเล่มนี้น่าจะเป็นจุดสำคัญที่ทำให้ตัดสินใจเลือกเรียนเอกคณิตศาสตร์อีกด้วย
  7. Tanigawa Nagaru's Sizumiya Haruri Series - อ่านตอนม.ปลายช่วงที่กระแสการ์ตูนกำลังดัง ชอบมากตรงที่คนเขียนเอาทฤษฎีทางวิทยาศาสตร์ประหลาดๆ ที่เข้าใจได้ยากมาผูกกับตัวละครเกรียนๆ จนได้เป็นเนื้อเรื่องย่อยง่าย หนังสือชุดนี้นี่เองที่ส่งผลให้ศึกษา philosophy จริงจัง
  8. Isaac Asimov's The Last Question - จริงๆ เป็นแค่เรื่องสั้น แต่แค่เรื่องนี้เรื่องเดียวก็เหมือนกับการสรุปคำถามจากหนังสือข้างต้นแทบทั้งหมดแล้วมาหาคำตอบให้มัน อ่านจบแล้วก็คิดได้ว่าศาสนาไม่จำเป็นต้องยืนอยู่คนละข้างกับวิทยาศาสตร์เลย
  9. Douglas Adams' Hitchhiker's Guide to the Galaxy - ผมเชื่อว่า พอเรียนรู้สิ่งต่างๆ ไปจนความรู้มันแทบจะระเบิดออกมา สุดท้ายแล้วก็ต้องหาทางลืมความรู้นั้น (unlearn) หนังสือชุดนี้แสดงถึงความไร้สาระของชีวิตได้ดีจริงๆ และน่าจะเป็นบทสรุปให้กับคำตอบของชีวิตที่ผมตั้งคำถามไว้อย่างเนิ่นนานด้วย

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

Sep 5, 2014

Barcamp Bangkok 2014

บาร์แคมป์กรุงเทพปีนี้จัดที่ TK Park ที่อยู่ชั้นบนสุดของห้าง Central World ครับ เดินทางง่ายไม่มีคำว่าหลงแน่นอน


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

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


@dtinth ที่ลืมสายต่อโปรเจคเตอร์จนต้องให้ทุกคนมารุมดูจอใกล้ๆ


@awkwin กับ @nonene_desu มาเถียงกันว่า DotA 2 กับ League of Legends เกมไหนเจ๋งกว่ากัน

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

สรุปว่างานนี้ไม่ปลื้ม แต่ก็ยังดีที่รวมพลไปเล่นบอร์ดเกมต่อหลังงานได้ (ถือเป็นเรื่องสนุกที่สุดของวันเลย)


เกมสร้างเรือเหาะกลางทะเลทราย ที่เล่นกับ @Blltz @scomma @chakeaw @hlungx และ @patamaAun ไม่ผ่านซักที

Sep 2, 2014

ใดๆ ในโลกล้วนการเมือง

ตอนเด็กๆ เคยปลื้มกับคำพูดนี้มาก

Politics is for the present, but an equation is something for eternity.
-- Albert Einstein

ได้ไอดอลเช่นนี้ก็เลยเลือกเรียนสายวิทยาศาสตร์บริสุทธิ์ เรียนไปเรียนมารู้ตัวอีกทีก็เข้ามหาวิทยาลัยไปอยู่เอกคณิตศาสตร์เรียบร้อย

แต่ยิ่งศึกษาลึกลงไปเท่าไหร่ยิ่งรู้สึกว่าศาสตร์ต่างๆ มันต้องเกิดมาให้ถูกที่ถูกเวลาถึงจะไปรอด

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

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

ทฤษฎีกราฟเกิดขึ้นราว 300 ปีก่อน เมื่อ Euler ดันไปสนใจการจัดผังสะพานเมือง Königsberg ซึ่งตอนนั้นทฤษฎีกราฟก็ใช้หาประโยชน์อะไรไม่ได้นอกเสียจากเอาไว้ตอบปัญหาเชาว์เรื่องการเดินข้ามสะพานนั่นเอง ต้องรอจนกระทั่งคอมพิวเตอร์ถือกำเนิดขึ้นเป็นจำนวนมากจนต้องมีการวางระบบเครือข่ายให้คอมพิวเตอร์คุยกัน เราจึงได้เห็นประโยชน์ของทฤษฎีกราฟจริงจัง

มองย้อนกลับไปยังผู้กล่าวประโยคข้างต้น กับสมการสร้างชื่อสุดอมตะอย่าง E = mc2 ที่เปรียบได้ดั่งกุญแจไขความลับจักรวาลว่าสสารและพลังงานนั้นแท้จริงแล้วรากฐานเป็นสิ่งเดียวกัน อย่าลืมว่าผลลัพธ์จากสมการนี้ทำให้เกิดอาวุธที่น่ากลัวที่สุดที่มนุษย์เคยสร้างมา

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

หรือถ้าสะพานเมือง Königsberg ไม่ได้มีแค่ 7 สะพานและเรียงตัวกันอย่างนั้น Euler อาจไม่สนใจที่จะพัฒนาทฤษฎีกราฟ ซึ่งซักวันมันจะมีประโยชน์อย่างมากในการวางเครือข่ายระบบคอมพิวเตอร์ก็ได้

แต่เมื่อถึงเวลาที่เราต้องใช้ความรู้เหล่านั้นเป็นพื้นฐานเพื่อต่อยอดสิ่งอื่นจริงๆ การสร้างองค์ความรู้ใหม่อาจไม่ได้เชื่องช้าอย่างที่คิด คือแทนที่จะใช้เวลาทั้งชีวิตเพื่อเขียนทฤษฎีที่เราจะตายไปก่อนที่จะได้เห็นการนำมันไปใช้ประโยชน์ (หรือบางทฤษฎีก็อาจหาประโยชน์จริงๆ ไม่ได้เลย) หากอยู่ในโมเมนตัมทางสังคมที่ถูกต้อง เราอาจใช้เวลาเพียงไม่กี่ปีกับการพัฒนางานที่จะก่อให้เกิด paradigm shift บนโลกใบนี้ก็ได้

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

ป.ล. เผื่ออ่านประกอบ: บล็อกของ @lewcpe, นิยาย Foundation เล่มที่ 3

Aug 29, 2014

เพราะห้องสมุดใหญ่เกินกว่าที่คนเดียวจะอ่านหนังสือครบทุกเล่ม

น่าเสียดายนะครับ ชีวิตคนเรานี้มันสั้นสิ้นดี

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

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

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

ดังนั้นเวลาคุยกันไม่รู้เรื่อง การไล่ไปหาหนังสืออ่านเองในห้องสมุด ผมว่ามันเสียมารยาทมากๆ นะ

อย่างน้อยก็บอกมาหน่อยว่าต้องหยิบหนังสือเล่มไหน ใจความสำคัญอยู่หน้าที่เท่าไหร่

hint: ลองเปลี่ยนคำว่าห้องสมุดเป็น Google

Aug 26, 2014

O Mio Babbino Caro

พอดี @isamare แนะนำวิชา Listening to Music มาครับ ก็นั่งดูไปเรื่อยๆ ด้วยความสนุกสนาน จนถึงตอนนึงอาจารย์ Craig Wright เล่าเหตุการณ์ในบ้านแกให้ฟังว่า

อาจารย์: (นั่งเล่นเปียโนอย่างสบายอารมณ์)
ลูกชาย: เพลงนี้เพราะมากเลยนะพ่อ
อาจารย์: โอ้ ให้มันได้อย่างนี่สิลูกชายฉัน (คงตัวลอยฝันไปไกลว่าลูกโตขึ้นเป็นนักดนตรีตามรอยตัวเองแน่ๆ 555)
ลูกชาย: ได้ยินมาหลายครั้งละใน Grand Thief Auto เพลงนี้เพลงอะไรครับพ่อ?
อาจารย์: เดี๋ยวก่อนนะ ... อะไรคือ Grand Thief Auto???



จุดเด่นของเพลงนี้ที่อาจารย์แกยกมาเล่าเรื่องเมโลดี้ครับ ปรกติเพลงร้องมักจะค่อยๆ ไล่โน้ตไปทีละขั้นสองขั้น แต่เพลงนี้กลับใช้การกระโดด 1 octave เลยทีเดียว

Jul 23, 2014

Wolfgang Amadeus Mozart - Symphony No. 25 in G minor, K. 183



มีคำกล่าวว่า Mozart ไม่ค่อยแต่งเพลงในกุญแจไมเนอร์ ซึ่งก็จริงอยู่เพราะจาก Symphony ราว 40 บท มีเพียง 2 บทเท่านั้นที่อยู่ในกุญแจไมเนอร์

แต่นั่นไม่ได้หมายความว่า Mozart แต่ง Symphony ในกุญแจไมเนอร์ไม่เก่ง

เพราะหมายเลข 25 นี้เอง ที่เป็นสิ่งพิสูจน์ข้อความข้างต้นครับ

I. Allegro Con Brio (8:00 นาที)

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

ในภาพยนต์เรื่อง Amadeus ซึ่งเป็นชีวประวัติของ Mozart ฉากแรกที่เปิดตัวมาด้วยภาพผู้คนจำนวนมากล้มตาย ก็ได้ท่อนนี้มาประกอบนี่แหละครับ เข้ากั๊นเข้ากัน ฟังแล้วเหมือนรอความตายยังไงหยั่งงั้นเลย

II. Andante (3:30 นาที)

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

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

III. Menuetto & Trio (3:30 นาที)

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

IV. Allegro (6:00 นาที)

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

ก็เป็นเพลงแบบเศร้าๆ มืดๆ จาก Mozart ที่เจ๋งดีครับ ฟังแล้วจินตนาการเป็นอย่างอื่นไม่ค่อยออก นอกจากเห็นภาพความตายที่รอคอยอยู่เบื้องหน้าจริงๆ

Jul 15, 2014

The Swing

ดู Frozen เมื่อปีก่อนก็ตะหงิดๆ อยู่ในใจว่า ตอนที่อันนากระโดดดึ๋งๆ ในเพลง For the First Time in Forever แล้วทำท่าเลียนแบบรูปวาดบนผนัง มันมีรูปนึงที่คุ้นตาเอาซะมากๆ

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

แต่หนังสนุก เพลงเพราะ ก็ลืมไปเลยว่าเคยสงสัยข้อนี้

จนกระทั่งวันก่อนเดินเข้าร้านหนังสือฆ่าเวลาเล่น แล้วไปเปิด 1001 Paintings You Must See Before You Die (edition ไหนไม่รู้ แต่น่าจะล่าสุด) พลิกกระดาษมั่วๆ ไปไม่กี่หน้าก็เจอภาพนี้เข้าพอดี


Les Hasards Heureux de l'Escarpolette โดย Jean-Honoré Fragonard วาดในปี 1767

อันนี้เทียบกับเวอร์ชั่นดิสนีย์


ภาพวาดที่อันนาทำท่าเลียนแบบ (บน) และภาพแบบธรรมดา (ล่าง) ภาพจาก Animate Film Review

ก็เป็นอันว่า ปริศนาในขีวิตไขกระจ่างไปอีกหนึ่งข้อ :P

Jul 12, 2014

Self Printing Program

หลายวันก่อนเปิดเจอโพสนี้ของคุณ @theppitak ซึ่งเป็นเรื่องของการเขียนโปรแกรมที่ต้องการผลลัพท์เป็นโปรแกรมที่กลับมาพิมพ์โปรแกรมตัวเอง

พูดง่ายๆ ว่าถ้าสมมติว่าโปรแกรมนั้นชื่อ self.c การสั่งคำสั่ง diff ครั้งสุดท้ายในโค้ดต่อไปนี้ต้องให้ค่าคืนมาเป็นว่า 2 ไฟล์มีเนื้อหาเหมือนกัน
$ gcc self.c
$ ./a.out > other.c
$ diff self.c other.c
เนื่องจากคุ้นว่าจะเคยเห็นคำถามแนวนี้ใน Ruby มาก่อนแล้ว ตอนแรกก็ไม่ได้คิดว่าจะลองทำหรอก แต่เท่าที่สังเกตแล้วจุดที่น่าจะเป็นปัญหาน่าจะมาจากเรื่อง escape string ซึ่งใน Python มันคงไม่ยุ่งยากเท่าไหร่ เพราะมี raw string และฟังก์ชั่น repr ไว้คอยจัดการปัญหา escape string อยู่แล้ว

แต่คิดไปคิดมา เอ่อ แล้วถ้าเป็นใน C (ที่ต้นทางเค้าสงสัย) นี่มันจะทำยังไงหว่า? เลยลงมือเขียนเล่นๆ จนได้ออกมาดังนี้
void main() {
  int i = 0, j = 0;
  char* x = "void main() {\n\tint i = 0, j = 0;\n\tchar* x = \"%s\";\n\twhile (x[i]) {\n\t\tif (x[i++] == '%%' && x[i++] == 's') {\n\t\t\twhile (x[j]) {\n\t\t\t\tswitch (x[j++]) {\n\t\t\t\t\tcase '\\n': putchar('\\\\'); putchar('n'); break;\n\t\t\t\t\tcase '\\t': putchar('\\\\'); putchar('t'); break;\n\t\t\t\t\tcase '\\\"': putchar('\\\\'); putchar('\"'); break;\n\t\t\t\t\tcase '\\\\': putchar('\\\\'); putchar('\\\\'); break;\n\t\t\t\t\tdefault: putchar(x[j-1]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tputchar(x[i-1]);\n\t\t}\n\t}\n}\n";
  while (x[i]) {
    if (x[i++] == '%' && x[i++] == 's') {
      while (x[j]) {
        switch (x[j++]) {
          case '\n': putchar('\\'); putchar('n'); break;
          case '\t': putchar('\\'); putchar('t'); break;
          case '\"': putchar('\\'); putchar('"'); break;
          case '\\': putchar('\\'); putchar('\\'); break;
          default: putchar(x[j-1]);
        }
      }
    } else {
      putchar(x[i-1]);
    }
  }
}
ไม่ได้จับ C มาหลายปี หมดพลังไปเยอะกว่าคิดออก 555+

Jul 8, 2014

ชีวิต (เกือบ) ไม่ติดเน็ต

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

ก็เป็นประสบการณ์ที่แปลกดีนะ วันแรกๆ รู้สึกจดจ่อกับงานได้ดีขึ้นเยอะมาก ไม่มีสิ่งกวนใจเลย

แต่พอทำไปทำมาแล้วเจอปัญหาใหญ่ครับ เพราะต้องพึ่ง Google, StackOverflow บ่อยๆ ครั้นจะหาในมือถือจอเล็กๆ แล้วพิมพ์ code ซ้ำในคอมก็ลำบากไปหน่อย กลายเป็นว่าวันหลังๆ นี่งานเดินช้ากว่าช่วงที่มีเน็ตไวไฟซะอีก

เดี๋ยวว่าจะกลับไปติดเน็ตคืนละครับ แล้วค่อยหาวิธีคุมสิ่งกวนใจด้วยวิธีอื่นแทน งานนี้ถือว่า trade-off ไม่คุ้มซะเท่าไหร่ :s

Jun 17, 2014

The Angels' Share

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

... ไปแบบไม่รู้อะไรซักอย่างจนกระทั่งเข้าโรงเนี่ยแหละ ถึงรู้ว่ามันคือเรื่อง The Angels' Share ครับ


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

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

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

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

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

สิ่งที่ชอบที่สุดคงหนีไม่พ้นประโยคที่ว่า "หนึ่งเพื่อลิ้มลอง หนึ่งเพื่อสะสม หนึ่งเพื่อแบ่งบัน"

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

Jun 7, 2014

ได้ Google+ Custom URL ซักที

จริงๆ Google+ Custom URL นี่ไม่ใช่ของใหม่ มันเปิดตัวตั้งแต่ปลายปี 2012 และขยายวงให้ผู้ใช้ทุกบัญชีตอนปลายปี 2013 อันที่จริงผมก็ได้หลังจากเปิดตัวไม่กี่เดือนนะ แต่ตอนนั้นดันปรับชื่อตามใจไม่ได้ (ใช้ได้อย่างเดียวคือชื่อและนามสกุลจริง) เลยงอน Google ไม่ยอมกดรับชื่อ แล้วก็ดองมาตั้งแต่นั้น

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

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

ก็เลยจัดการเปลี่ยนชื่อจริงเป็น nei และนามสกุลเป็น zod เพราะตอนนั้นเดาว่าตาม policy แล้ว อาจจะปรับตัวอักษรตัวแรกของชื่อ/นามสกุลให้เป็นตัวพิมพ์ใหญ่ ก็เลยแบ่งคำกันไว้ก่อน (จะว่าไป จริงๆ ก็น่าจะแบ่งว่า neiz กับ od นะ :P) หรือถ้าขี้เกียจตัดแบ่งคำ จะใช้นามสกุลเป็นจุดตัวเดียวเพื่อบอกว่าไม่มีนามสกุลก็ได้

ปรากฏว่ารอแค่ 3 วัน ก็ได้ Custom URL อย่างที่ต้องการครับ (ตอนแรกนึกว่าจะนานกว่านี้เสียอีก) ส่วนเรื่องที่เดาว่าอักษรตัวแรกของชื่อ/นามสกุลจะถูกเปลี่ยนเป็นตัวใหญ่นี่คิดผิดแฮะ ให้มาเป็นตัวเล็กหมดเลย ดีมากๆ

ก็เจอกันได้ที่ google.com/+neizod ครับ แม้จะไม่ค่อยได้เล่นเท่าไหร่ แต่เล่นเยอะกว่า Facebook แน่นอน ฮาาา

Jun 1, 2014

การสอนด้วยตัวอย่างที่ดีนั้นยาก

ถ้าช่วงนี้ใครเข้าเว็บ Python.org คงจะเห็นตัวอย่างโค้ดนี้
list = [2, 4, 6, 8]
sum = 0
for num in list:
    sum = sum + num
print("The sum is:", sum)
เรียบง่ายและงดงาม...

แต่ถ้าไปถามคนที่เขียน Python ประจำนี่ด่าตรึมเลย เพราะโค้ดด้านบนนี้มีจุดอ่อนอยู่ 2 ประการ
  1. มีการประกาศชื่อตัวแปร list กับ sum ซึ่งทั้ง 2 ชื่อนี้ เป็นชื่อของฟังก์ชันมาตฐาน ถึงแม้ว่าจะไม่มีข้อห้ามให้ประกาศชื่อซ้ำได้เหมือนกับพวก reserved keyword แต่การทำเช่นนี้ก็บาปมากเพราะ 2 ฟังก์ชันนี้จะไม่สารมารถถูกเรียกใช้ได้โดยง่ายอย่างที่ตัวภาษาออกแบบไว้
  2. Python มีฟังก์ชัน sum สำหรับหาผลรวมของชุดตัวเลขให้ใช้งาน นอกจากจะเขียนได้ง่ายและทำงานเร็วกว่าแล้ว มันยังสร้างสุขลักษณะที่ดีต่อการเขียนโปรแกรมสำหรับใช้งานในโลกจริงด้วย
  3. (ข้อนี้เพิ่มให้ในฐานะนักทฤษฎีคณิตศาสตร์และคอมพิวเตอร์) ขั้นตอนวิธีข้างต้นสามารถพิสูจน์ลดรูปให้เป็น O(1) ได้ ถ้าลำดับเลขนำเข้าเป็นเลขจำนวนเต็มเรียงกันทั้งหมด หรือเป็นเลขคู่เรียงกัน หรือเป็นเลขคี่เรียงกัน
ตัวอย่างนี้จึงเป็นตัวอย่างที่ไม่ค่อยดีในการนำมาสอนเขียนโปรแกรมที่จริงจังหน่อย เพราะว่าโค้ดแบบนี้ไม่ควรเกิดขึ้นในโลกจริงเลย (แน่นอนว่าคนที่ยกตัวอย่างนี้มา อาจแก้ต่างว่าโค้ดนี้มีเจตนาที่จะยกตัวอย่างลูปให้มือใหม่หัดเขียนโปรแกรม ซึ่งโดยส่วนตัวผมไม่ค่อยเห็นด้วยเพราะนี่คือหน้าเว็บที่มีทุกระดับฝีมือมาเยี่ยมเยียน ไม่ใช่บทที่ 1 ในหนังสือ Programming 101)

ถ้าเช่นนั้น แล้วจะมีตัวอย่างแบบไหนที่ดีพอหละ?

ผมคิดว่าตัวอย่างที่ดี เข้าเกณฑ์แค่ข้อเดียวก็พอ คือต้องไม่ทำหน้าที่ซ้ำซ้อนกับสิ่งที่มีอยู่แล้ว (do not reinvent the wheel)

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

โชคดี (?) ที่ Python ยังไม่มีฟังก์ชันผลคูณรวม (product) ทำให้โค้ดในโลกความจริง ที่สามารถนำมาสอนเขียนได้ดังนี้
numbers = [2, 4, 6, 8]
product = 1
for number in numbers:
    product = product * number
print("The product is:", product)
เพราะการสร้างตัวอย่างที่ดีไม่ใช่เรื่องง่ายๆ

May 28, 2014

อย่าเสียเวลาให้กับลูปที่เรียก System บ่อยๆ

ช่วงนี้มีวิเคราะห์ string บนไฟล์จำนวนมหาศาล (หลายแสนไฟล์) ตอนแรกก็เขียน Python ง่ายๆ ตามความเคยชิน
counter = 0
for file in glob('*.cpp'):
    if '//' in file.read():
        counter += 1
print(counter)
โค้ดนี้ทำงานได้ดีเลยแหละ ติดตรงที่ไฟล์ที่นำมาวิเคราะห์บางไฟล์ก็ใช้ encoding แปลกๆ แล้วเปิดอ่านไม่ได้ (ยังงงอยู่ว่า Python ไม่เช็คว่าเป็น ASCII หรือ UTF-8 ให้อัติโนมัติ?) ก็แก้ๆ โค้ดด้านบนนี้โดยใช้โมดูล codecs เข้าช่วย ไม่มีปัญหาอะไร

แต่แล้วก็คิดขึ้นมาได้ว่า งานประเภทนี้ถ้าทำใน Bash ไปเลยก็น่าจะแจ่มเหมือนกัน เพราะปรัชญาของ Unix คือให้โปรแกรมจัดการกับ I/O Stream แบบข้อความอยู่แล้ว เลยได้โค้ดออกมาเป็น
counter=0
for file in *.cpp
do
    grep -q '//' "$file" && : $((counter++))
done
echo $counter
ผลลัพท์ออกมาไม่ต่างกับ Python ด้านบน แต่ว่า Bash ทำงานช้ามากกกกก (ก.ไก่ 140 ตัว) ตอนแรกก็คิดว่า Bash มันน่าจะทำลูปไม่เก่งมั้ง เพราะเท่าที่อ่านโค้ดของโครงการ Linux ก็ไม่ค่อยเห็นเขียนลูปกันเท่าไหร่ แต่พอลองเขียนลูปเปล่าแสนรอบใน Bash มันก็ไม่เห็นจะช้า เลยคิดว่าน่าจะเป็นที่ grep มั้ง? แต่ก็ไม่อยากจะเชื่ออยู่ดีเพราะเคยอ่านงานของ Russ Cox ก็ไม่เห็นว่า grep มันจะช้าซักเท่าไหร่

อาจารย์ @juggapong ผ่านมาเห็นเลยยิงคำถามมาว่า มันช้าเพราะมัวแต่มุดเข้ามุดออก system หรือเปล่า?

ได้ยินดังนั้นเลยใช้ time จับเวลาดู โอ้ว user กับ sys ใช้เวลาพอๆ กันเลยแฮะ

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

คิดได้ดังนี้ก็เปลี่ยนโค้ดไปเป็น (ใช้เตารีดตัวเดียว เสียบปลั๊กรอร้อนครั้งเดียว)
grep -l '//' *.cpp | wc -l
คราวนี้เร็วส์เสียยิ่งกว่า Python อีก :P

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

May 25, 2014

ที่ทางของคำสั่ง : ใน Shell

เคยเขียน (มั้ง) มาทีละว่า ทุกคำสั่งใน linux มันมีที่ทางของมันหมด แม้จะเป็นคำสั่งเล็กๆ ที่ทำหน้าที่ง่อยๆ ก็ตามที ... แต่วันนี้จะมานำเสนอคำสั่ง : (colon ตัวเดียว) ซึ่งเป็นคำสั่งที่ไม่ทำอะไรเลย

อ้าว แล้วเมื่อมันไม่ทำหน้าที่อะไรเลย จะมีไว้ทำไมหละ?

งั้นต้องถามก่อนว่า เคยประสบปัญหานี้มั้ย
> # ตั้งตัวแปรไว้ใช้ดีกว่า
> foo="hahaha"
> # ทำงานอื่นไปเรื่อยๆๆๆๆ
> # เอ๊ะ ตัวแปร $foo โดนเปลี่ยนค่า (?) ไปเป็นอะไรแล้วนะ?
> $foo
hahaha: command not found
ด้านบนนี้เป็น user error เอง อยากดูค่าก็อย่าลืม echo สิ :P

อย่างไรก็ตาม อย่าลืมว่า Shell มันเป็น programming language ตัวหนึ่งเลย ความสามารถที่หลายๆ คนลืมคือเราสามารถทำการคำนวณใน Shell ได้
> i=0
> for f in *
> do
>     [ -s "$f" ] && i=$((i+1))
> done
> echo $i
โค้ดด้านบนนี้จะถามว่าไฟล์ในแฟ้มปัจจุบัน มีกี่ไฟล์ที่มีเนื้อหา (ขนาดไม่เป็น 0 byte) ซึ่ง counter จะเพิ่มขึ้นตรงหลังเครื่องหมาย && นั่นเอง

ในโลกความจริงเราไม่ได้เขียนโค้ดอย่างนี้ตลอด อาจมีบางครั้งที่ต้องเอาการคำนวณแยกไว้ในที่เดี่ยวๆ เช่น
> i=0
> for ...
> do
>     # ทำอะไรซักอย่าง
>     i=$((i+1))
> done
เขียนไปเขียนมาจะพบว่า i=$((i+1)) มันตลกสิ้นดี ยิ่งเมื่อรู้ว่า Shell เขียน i++ ได้! แต่อย่าลืมว่าถ้าเปลี่ยนไปเขียนตรงๆ ก็จะเจอปัญหาเดิม
> $((i++))
0: command not found
ทั่วไปแล้ว การเขียนในแนวนี้จะเป็นการสร้างตัวนับเพื่อบอกว่า process นี้ทำงานไปถึงไหนแล้ว ซึ่งมันสามารถเอาไปแทรกไว้กับคำสั่ง echo ได้
> echo "now doing item number: $((++i))"
แต่ถ้าไม่ต้องการให้มัน echo อะไรระหว่างทางหละ? นี่แหละคือจุดยืนของคำสั่ง :
> : $((i++))
ป.ล. Shell รุ่นเก่าๆ ไม่มีคำสั่ง true เลยต้องใช้คำสั่ง : แทน

Apr 13, 2014

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


ปีนี้ปัญหาเยอะนิดหน่อยเพราะวันแข่งตรงกับวันเดินทางออกต่างจังหวัดพอดี และเนื่องจาก #แล้วรถไฟความเร็วสูงกูหละ ทำให้เสียเวลาเดินทางสั้นๆ แค่ 300 กว่ากิโลเมตรไป 8 ชั่วโมงครึ่ง แถมพอถึงที่หมายแล้วก็หาเวลาส่วนตัวไม่ค่อยได้เลย กว่าจะได้ลงมือจริงๆ ก็ตีหนึ่งแล้ว (มีเวลา 8 ชั่วโมงจาก 27 ชั่วโมง) ผลงานที่ได้เทียบกับ effort ที่สามารถลงให้มันไปถือว่าน่าผิดหวังเล็กน้อย :\

อย่างไรก็ตาม ปีนี้ก็บรรลุเป้าข้อนึงคือใช้ภาษาอย่างน้อย 3 ภาษาเขียนแก้โจทย์ ก็ได้แก่ Python, Haskell และ CoffeeScript (อันที่จริง CoffeeScript นี้แปลจาก Python มาตรงๆ นาทีเดียวเสร็จเลยนะ :P) ปีต่อไปพยายามตั้งเป้าไว้ที่ Racket/Scheme กับ matrix processing language ซักตัวครับ

มาดูข้อแรกข้อ A ที่ตามธรรมเนียมจะง่ายที่สุดกัน ยิ่งใช้ Python ยิ่งง่ายใหญ่เลยเพราะมี set intersection ให้ใช้ เราก็แค่หาว่า intersect ของตัวเลขที่นักมายากลถามมานั้นคืออะไร ถ้ามีตัวเดียวก็คือตัวนั้นแหละ ถ้ามีมากกว่าหนึ่งแปลว่านักมายากลแม่งกาก แต่ถ้าไม่มีเลยนี่ผู้ชมโกงแล้วหละ

def foo(answer_1, matrix_1, answer_2, matrix_2):
    candidate_1 = matrix_1[answer_1-1]
    candidate_2 = matrix_2[answer_2-1]
    final = set(candidate_1) & set(candidate_2)
    if len(final) == 0:
        return 'Volunteer cheated!'
    elif len(final) > 1:
        return 'Bad magician!'
    else:
        return final.pop()

for case in range(int(input())):
    answer_1 = int(input())
    matrix_1 = [[int(n) for n in input().split()] for _ in range(4)]
    answer_2 = int(input())
    matrix_2 = [[int(n) for n in input().split()] for _ in range(4)]
    answer = foo(answer_1, matrix_1, answer_2, matrix_2)
    print('Case #{}: {}'.format(case+1, answer))



ข้อ B ถัดมาเริ่มจะต้องสังเกตละ แต่เนื่องจากผมตั้งใจเขียนข้อนี้ในสไตล์แบบ clean code พอสมควร (ไม่มี comment, ตั้งชื่อตัวแปรให้รู้เรื่อง) ดังนี้ข้อนี้ขออธิบายด้วย code อย่างเดียวนะครับ

def optimal_cookie(new_farm_cost, prodct_cookie, finale_cookie):
    income_cookie = 2.0
    optimal_spent = 0
    while True:
        new_farm_time = new_farm_cost / income_cookie
        expect_cookie = prodct_cookie + income_cookie
        wait_win_time = finale_cookie / income_cookie
        upgd_win_time = finale_cookie / expect_cookie + new_farm_time
        if wait_win_time > upgd_win_time:
            income_cookie = expect_cookie
            optimal_spent += new_farm_time
        else:
            optimal_spent += wait_win_time
            break
    return optimal_spent

for case in range(int(input())):
    answer = optimal_cookie(*[float(n) for n in input().split()])
    print('Case #{}: {:.7f}'.format(case+1, answer))

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



กระโดดข้ามมาข้อ D ก่อน ข้อนี้ยากตรงที่เราต้อง proof จนได้ strategy ที่ดีที่สุด เราอาจสังเกตว่า ในเกม war ธรรมดามีวิธีเล่นที่ดีที่สุดดังนี้
  • ถ้า Naomi เล่นกล่องที่หนักกว่าทุกกล่องของ Ken ทางเลือกที่ดีที่สุดสำหรับ Ken คือเล่นกล่องที่เบาที่สุด เพื่อให้กล่องที่เหลือๆ มีประสิทธิภาพในการสู้สูงที่สุด
  • ถ้า Naomi เล่นกล่องที่เบากว่ากล่องบางกล่องของ Ken แล้ว Ken จะตอบโต้ด้วยกล่องที่หนักกว่ากล่องของ Naomi แต่เป็นกล่องที่เบาที่สุดที่ยังจะหนักกว่ากล่องของ Naomi เพราะการเล่นแบบนี้ทำให้ได้คะแนน และยังเก็บกล่องหนักๆ ไว้ใช้สู้ในอนาคตได้อีก
พอเกมเปลี่ยนมาเป็น deceitful war ทาง Naomi ก็ใช้ข้อได้เปรียบจากการล่วงรู้น้ำหนักกล่องของ Ken ทั้งหมดมาเล่น โดยยึดกลยุทธ์ดังนี้
  • Naomi สามารถเล่นกล่องที่เบาที่สุดของตน แต่หลอกว่ากล่องนี้มีน้ำหนักน้อยกว่ากล่องที่หนักที่สุดของ Ken เนื่องจาก Ken ยังยึดกฎเดิม เขาจะตอบโต้ด้วยกล่องที่หนักที่สุด (กินคะแนน)
  • Naomi สามารถเล่นกล่องที่เบาที่สุดของตน แต่หลอกว่ากล่องนี้มีน้ำหนักมากกว่ากล่องที่หนักที่สุดของ Ken เช่นกันเพราะ Ken ยึดกฎเดิม เขาจะตอบโต้ด้วยกล่องที่เบาที่สุด (ทิ้งของกากสุด)
เทคนิคสำคัญคือรอจังหวะที่แต่ละกล่องของ Naomi เมื่อเรียงน้ำหนักและนำไปจับคู่กับ Ken แล้ว ทุกกล่องมีน้ำหนักมากกว่า เมื่อนั้น Naomi จะใช้กลยุทธ์ล่างทันที แต่ถ้ายังไม่มีโอกาสก็จะยึดกลยุทธ์แรกไปก่อน เพื่อกำจัดกล่องหนักๆ ของ Ken ทิ้งไป

from collections import deque

def normal_war(ns, ks):
    c = 0
    while ns:
        n = ns.pop()
        if ks[-1] < n:
            ks.pop(0)
            c += 1
        else:
            i = 0
            while n > ks[i]:
                i += 1
            ks.pop(i)
    return c

def deceitful_war(ns, ks):
    while any(n < k for n, k in zip(ns, ks)):
        ns.popleft() and ks.pop()
    return len(ns)

for case in range(int(input())):
    _ = input()
    ns = sorted(float(n) for n in input().split())
    ks = sorted(float(n) for n in input().split())
    dw_answer = deceitful_war(deque(ns), deque(ks))
    nw_answer = normal_war(ns[:], ks[:])
    print('Case #{}: {} {}'.format(case+1, dw_answer, nw_answer))

ข้อ C ถามว่าในอุดมคติแล้ว เราสามารถกดปุ่มเดียวผ่าน minesweeper ได้มั้ย ซึ่งข้อนี้มีจุดน่าสนใจอยู่ที่การแตกเคสต่างๆ ออกมาให้ครอบคลุม ดังนี้
  • ถ้ากระดานมีความกว้างแค่ 1 หน่วย ยังไงก็กดครั้งเดียวผ่านแน่ เพราะเราก็ให้ระเบิดทั้งหมดไปกองด้านนึง อีกด้านก็ไม่มีอะไร
  • ถ้ากระดานมีความกว้าง 2 หน่วย เราจะได้ว่าต้องมีพื้นที่ว่าง 1 ช่อง (สำหรับกดครั้งเดียวผ่านเลย) หรือที่ว่างต้องเป็นเลขคู่ที่มากกว่า 2 (ถ้ามี 2 ช่องพอดีแม้ช่องนั้นจะอยู่ติดกันก็ต้องกด 2 ครั้ง)
  • กระดานแบบอื่นๆ ทั้วไป พยายามกั้นช่องว่างให้ตัวเองเป็นจัตุรัสก่อน แล้วค่อยๆ เวนคืนที่ว่างเพิ่มเติม ดังนี้
    • ที่ว่าง 1 ช่องเป็นจัตุรัสขนาด 1x1 ง่ายสุด คือกดครั้งเดียวผ่านเลย อย่างไรก็ตาม ที่ว่างแบบนี้ทำให้เราไม่สามารถมีที่ว่าง 2, 3 ได้
    • ที่ว่าง 4 ช่องเป็นจัตุรัสขนาด 2x2 สามารถมีติ่งเพิ่มออกมาอีก 2, 4 ช่องได้ คือจะได้ ที่ว่างรวมเป็น 6, 8 ช่อง ส่วนติ่งที่เพิ่มแค่ 1, 3 ช่องเพิ่มไม่ได้
    • ที่ว่าง 9 ช่องเป็นจัตุรัสขนาด 3x3 และใหญ่กว่า คราวนี้จะมีติ่งเป็นเศษแค่ 1 ช่องก็ได้ ทำโดยไปยืมพื้นที่ว่างหัวมุมมาจับคู่เข้ากับติ่งนี้ซะ
    • สรุปว่าท่านี้ไม่รองรับการเหลือที่ว่างแค่ 2, 3, 5, 7 ครับ
    • ท่านี้ต้องพลิกแพลงนิดหน่อยด้วย ตรงที่ถ้าเกิดขนาดจัตุรัสใหญ่เกินด้านใดด้านหนึ่ง ต้องเปลี่ยนไปใช้โหมดเติมเต็มทีละบรรทัดแทน
อ่านข้อความอย่างเดียวอาจคิดไม่ออก ดูตัวอย่างการ fill แบบต่างๆ บนตาราง 4x4 นี้น่าจะง่ายกว่า
Case #0:      Case #1:      Case #2:      Case #3:
c...          c...          c...          c...
....          ....          ....          ....
....          ....          ....          ...*
....          ...*          ..**          ..**

Case #4:      Case #5:      Case #6:      Case #7:
c..*          c..*          c..*          c..*
...*          ...*          ...*          ...*
...*          ...*          ..**          ...*
...*          ..**          ..**          ****

Case #8:      Case #9:      Case #10:     Case #11:
c.**          Impossible    c.**          Impossible
..**                        ..**
..**                        ..**
..**                        ****

Case #12:     Case #13:     Case #14:     Case #15:
c.**          Impossible    Impossible    c***
..**                                      ****
****                                      ****
****                                      ****
ส่วน code ก็หน้าตาประมาณ
def transpose(matrix):
    return [list(line) for line in zip(*matrix)]

def fill_line(r, c, left):
    width, length = sorted([r, c])
    matrix = [['*' for _ in range(length)] for _ in range(width)]
    for i in range(width):
        matrix[i][:left//width] = ['.' for _ in range(left//width)]
    matrix[0][0] = 'c'
    return matrix if r == len(matrix) else transpose(matrix)

def fill_rect(r, c, left):
    width, length = sorted([r, c])
    sq_length = int(left**0.5)
    matrix = [['*' for _ in range(width)] for _ in range(length)]
    if sq_length > width:
        i = 0
        while left - width > 0:
            matrix[i] = ['.' for _ in range(width)]
            left -= width
            i += 1
        if left == 1:
            matrix[i-1][-1:] = ['*']
            matrix[i][:2] = ['.' for _ in range(2)]
        else:
            matrix[i][:left] = ['.' for _ in range(left)]
    else:
        i = 0
        while i < sq_length:
            matrix[i][:sq_length] = ['.' for _ in range(sq_length)]
            left -= sq_length
            i += 1
        if left > sq_length:
            if left == sq_length + 1:
                matrix[i][:sq_length-1] = ['.' for _ in range(sq_length-1)]
                left = 2
            else:
                matrix[i][:sq_length] = ['.' for _ in range(sq_length)]
                left -= sq_length
            if i == len(matrix)-1:
                matrix = transpose(matrix)
            else:
                i += 1
            matrix[i][:left] = ['.' for _ in range(left)]
        elif left > 0:
            if left == 1:
                matrix[i-1][sq_length-1:sq_length] = ['*']
                left = 2
            matrix[i][:left] = ['.' for _ in range(left)]
    matrix[0][0] = 'c'
    return matrix if r == len(matrix) else transpose(matrix)

def mine_fill(r, c, m):
    width = min(r, c)
    left = r * c - m
    if width == 1:
        if left == 0:
            return None
        return fill_line(r, c, left)
    if width == 2:
        if left in [0, 2] or left % 2 == 1 and left != 1:
            return None
        return fill_line(r, c, left)
    else:
        if left in [0, 2, 3, 5, 7]:
            return None
        return fill_rect(r, c, left)

for case in range(int(input())):
    r, c, m = [int(n) for n in input().split()]
    answer = mine_fill(r, c, m)
    print('Case #{}:'.format(case+1))
    if answer is None:
        print('Impossible')
    else:
        for line in answer:
            print(''.join(line))
ข้อนี้เสียดายมากที่หา bug ไม่เจอ มาเจออีกทีตอนแข่งจบไปแล้ว ไม่งั้นรอบนี้คงได้ทำคะแนนเต็มให้เป็นเกียรติเป็นศรี \(TwT)/

Mar 31, 2014

PHP Closure: เริ่มต้นชีวิตใหม่ปิดกั้นตัวเองยอมลืมทุกสิ่งอย่าง

ภาษาทั่วไปเช่น Python เราสามารถทำ closure เช่นนี้ได้
read_only = 42

def f(x):
    return read_only + x

print(f(10)) # 52
หรือกระทั่ง
ls = [4, 8, 15, 16, 23, 42]

def g(x):
    ls.append(x)

g(99)
print(ls) # [4, 8, 15, 16, 23, 42, 99]
ความประหลาดและน่ารำคาญใน PHP คือเราไม่สามารถเขียนแค่นี้เพื่อทำ closure ง่ายๆ ตามข้างบนได้ เพราะเมื่อเราสร้างฟังก์ชันขึ้นมาแล้ว ฟังก์ชันนั้นจะไม่รู้จักตัวแปรใดๆ เลย (ยกเว้นพวก superglobals อย่าง $_GET) เราต้องเพิ่ม keyword global เข้าไปอีก เช่น
$ls = array(4, 8, 15, 16, 23, 42);

function g($x) {
    global $ls;
    $ls[] = $x;
}
ซึ่งท่านี้ก็ยังมีปัญหาอีกว่าถ้าตัวแปรที่ต้องการไม่อยู่ใน global scope (เช่นไปอยู่ใน local scope ของฟังก์ชันที่สร้างฟังก์ชันนี้อีกที) PHP ก็จะหาตัวแปรนั้นไม่เจอ

ทางแก้ที่ดูแล้วเป็น functional มากที่สุด คือใช้ keyword use เช่นนี้
$ls = array(4, 8, 15, 16, 23, 42);

$g = function($x) use($ls) {
    $ls[] = $x;
    return $ls;
};
แน่นอนว่าเมื่อทำแบบ functional แล้ว ตัวแปร $ls เก่าจะไม่เปลี่ยนค่า เพราะเมื่อมันถูกเรียกผ่าน use นั่นหมายถึงการคัดลอกค่าตัวแปรมาทั้งหมด แล้วตั้งชื่อตัวแปรให้เหมือนกันใน scope ต่างกัน แต่ถ้าอยากให้ตัวแปรเดิมเปลี่ยนค่าก็ยังสามารถใช้เทคนิคเดิมได้คือ
function($x) use(&$ls) { ... }
อย่างไรก็ตาม ท่านี้ยังมีปัญหาตรงที่การประกาศฟังก์ชันต้องทำแบบ anonymous (แล้วค่อยเอาตัวแปรไปรับ) แถมถ้าเราจะอ้างค่าใน scope อื่นเป็นจำนวนมาก ที่หัวฟังก์ชันจะเขียนได้รุงรังอย่าบอกใคร



ข้อดีเดียวที่นึกออกจากการบังคับใช้ global หรือ use สำหรับเรียกตัวแปรนอก scope คือ PHP อนุญาตให้ไม่ต้อง init ตัวแปรก็ได้ (ถ้าไม่มีการ init มาก่อน มันจะถือว่าเป็นค่าว่างตามการใช้งานนั้นๆ) ทำให้เราสามารถเขียนอะไรเช่นนี้ได้
function query_to_array() {
    $res = mysql_query('SELECT * FROM blah_blah_blah');
    foreach ($res as $row) {
        $ls[] = $row['foo_blah'];
    }
    return $ls;
}
เราอาจมองว่าท่านี้สวยตรงที่ไม่ต้อง init ตัวแปร $ls ที่รู้ๆ กันอยู่แล้วว่าต้องเป็น array ว่างแน่ๆ แต่ถ้าเกิดว่า query ข้างบนให้ผลลัพท์เป็นเซ็ตว่าง ตอน return เอาไปใช้ต่อจะเกิด bug เพราะ PHP ไม่สามารถบอก type ของ $ls ได้ สุดท้ายก็ต้องกลับไปประกาศตัวแปรไว้ที่จุดเริ่มต้นฟังก์ชันอยู่ดี หรือไม่งั้นก็เปลี่ยน return เป็น
return $ls ?: array();



ส่วนข้อเสียของการไม่ยอมให้อ้างตัวแปรนอก scope ได้นั้น นอกจากความหงุดหงิดแล้ว ก็มาจากวิวัฒนาการของภาษาสมัยนี้ที่พยายามทำให้เป็น OOP มากขึ้น ทุกวันนี้มันคงไม่แปลกที่จะเขียน
$db = new PDO('mysql: ...');
$res = $db->prepare('SELECT * FROM blah_blah_blah WHERE answer = ?');
$res->execute(array(42));
ในความจริงแล้ว ขั้นตอน prepare/execute มักถูกเขียนในส่วนอื่นๆ ไม่เอาไว้ติดกันเช่นนี้ ยิ่งไปกว่านั้นมันมักโดน refactor ไว้ในฟังก์ชันเพื่อจัดระเบียบให้อ่านง่ายด้วย ในเมื่อตัวแปร $db ที่ควรเป็น global ดันไม่สามารถเรียกใช้ได้ง่ายๆ ใครมันจะอยากเขียนแบบ OOP กันหละ?

ทางออกโดยทั่วไปก็คือสร้าง model ที่เป็น interface สำหรับ query ทั้งหมดให้ทำผ่านตัวมัน แล้วตอนสร้าง model ก็ bind ตัวแปร $db เข้าไป ก็คือเราสามารถเข้าถึง database ได้โดย $this->db ซึ่งท่านี้ก็ยังรุงรังเหมือนเดิม แถมตอนเรียกใช้ก็ยังต้องพิมพ์ยาวขึ้นอีกด้วย

ท่าที่ผมชอบมากกว่าเป็นของ Laravel ที่สร้าง class เชื่อมต่อ database นั้นๆ ไว้ให้เลย ทำให้เวลาจะทำ query ก็เพียงแค่
$res = DB::select('SELECT * FROM blah_blah_blah WHERE answer = ?', array(42));
เพราะว่า class และฟังก์ชันใน PHP สามารถเรียกใช้จาก scope ไหนๆ ก็ได้ครับ



ข้อดีนิดเดียว (แถมยังไม่แน่ว่ามันเป็นข้อดีจริงๆ หรือเปล่า) แต่ข้อเสียบานเลย รู้งี้แล้วยังเขียน PHP กันอยู่อีกรึ :P

Feb 14, 2014

ประสบการณ์สร้างเกมจริงจังครั้งแรก

ไอเดียมันเริ่มมาจากทวีตนี้


(ว่าแต่ใครมันจุดกระแส #ถีบเนยสด ฟระ Orz)

เลยจัดการซะคืนนั้นเลย ... ถ้าไล่ดูตาม commit log จะเห็นว่าใช้เวลาไป 2 เดือนพอดีจนปิดโปรเจคได้ ก็นับว่าใช้เวลาเยอะโขอยู่กับโปรเจคขำๆ

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

ซึ่งเวลาส่วนใหญ่ที่หมดไปก็เพราะเจ้าเนี่ยแหละ จริงๆ ถ้าดูตาม log อย่างละเอียดแล้วลองคำนวณเวลาที่ใช้ จะพบว่าโปรเจคนี้ทำเสร็จภายใน 42 ชั่วโมง (บวกลบไม่เกิน 10%) ถ้ามีไอเดียเจ๋งๆ เกี่ยวกับ game play เตรียมไว้อยู่แล้ว

แต่ก่อนที่จะไปดูการออกแบบเกมนี้ ลองเรียนรู้จากเกมอื่นๆ ก่อน



Flappy Bird

  • infinity - เล่นได้เรื่อยๆ จนกว่าจะพลาดหรือเบื่อ
  • social - อวดคะแนนแข่งเพื่อน
  • reflex - ตอบสนองทันทีต่อสิ่งใหม่
(เกาะกระแสซักหน่อยครับ) เกมนี้ใช้ระบบที่เรียบง่ายมากๆ คือพานกบินลอดท่อไปเรื่อยๆ ลอดผ่านได้ 1 ท่อก็เพิ่ม 1 คะแนน อย่างไรก็ตามเกมประเภทนี้จะสุ่มด่านใหม่มาให้เสมอ ผู้เล่นแต่ละคน (หรือแม้แต่คนเดียวกันเมื่อเล่นรอบใหม่) ก็จะพบกับด่านที่ไม่เหมือนเดิม การเพิ่มคะแนนแบบไม่เกี่ยวข้องกับความยากของด่านจึงเหมาะสม เพราะจะกดดันให้เล่นเกมซ้ำๆ เผื่อฟลุ๊คเจอด่านง่ายแล้วได้คะแนนเยอะโดยออกแรงไม่มากไปกว่าเดิมนัก ซึ่งการเล่นซ้ำๆ (ด้วยความโมโหว่าทำไมคะแนนไม่ขึ้นซักที 555) จะย้อนกลับมาช่วยให้เราเรียนรู้ระบบเกมและฝึกประสาทตอบสนอง ส่งผลให้ผู้เล่นได้คะแนนง่ายขึ้นเรื่อยๆ ตามเวลาที่ฝึก ยิ่งผสมกับการนับคะแนนตรงไปตรงมา (1 ท่อ = 1 คะแนน) ยิ่งทำให้เรากะถูกว่าต้องใช้ความพยายามเพิ่มอีกแค่ไหนเพื่อจะทำคะแนนให้ชนะเพื่อนได้

Super Hexagon

  • goal set - เกมตั้งจุดมุ่งหมายให้แล้ว
  • social - แต่จะแข่งกับเพื่อนก็ได้
  • reflex - ตอบสนองทันทีต่อสิ่งใหม่
แนวคิดของเกมนี้ก็ง่ายเช่นเดียวกัน คือพาเจ้าตัวสามเหลี่ยมวิ่งวนคอยหลบไม่ให้โดนกำแพงอัดแบน และใช้ "เวลาที่อยู่รอด" เป็นคะแนนนั่นเอง งานนี้ไม่ต้องคิดมากว่าจะเลือกท่าหลบให้ให้สวยงามได้คะแนนโบนัสเพิ่มหรือเปล่า เช่นเดียวกับ Flappy Bird คือต้องฝึกเยอะๆ ฝึกไปเรื่อยๆ และแม้ว่าจะกำหนดเวลาผ่านด่านไว้ที่ 60 วินาที แต่ถ้าจะทำคะแนนไว้อวดเพื่อน ก็สามารถเล่นต่อหลัง 60 วินาทีนั้นไปได้เรื่อยๆ ครับ

Jubeat

  • goal set - เกมตั้งจุดมุ่งหมายให้แล้ว
  • social - แต่จะแข่งกับเพื่อนก็ได้
  • rhythm - เกมเข้าจังหวะ
Jubeat เป็นเกมแนวดนตรีที่จะให้กดปุ่ม 16 ปุ่ม (บน grid ขนาด 4x4) ตามจังหวะของแต่ละปุ่ม แต่ละเพลงมีรูปแบบที่ตายตัว แถมเกมยัง cap แต้มไว้ที่หนึ่งล้านแต้มเสมอไม่ว่าจะเป็นเพลงยากหรือง่าย ดังนั้นรูปแบบการฝึกจะไม่เหมือน 2 เกมข้างบนเท่าไหร่ เช่นฝึกซ้ำบ่อยๆ เฉพาะรูปแบบที่ยาก ไปจนถึงการจำรูปแบบจังหวะของเพลงทั้งเพลง เกมแบบนี้ต้องให้คนรักในความ perfect จริงๆ มาเล่น ไม่งั้นคงรู้สึกเหมือนฟินไม่สุด

The Typing of the Dead

  • goal set - เกมตั้งจุดมุ่งหมายให้แล้ว
  • co-op - ร่วมมือกันเล่นได้
  • reflex - ตอบสนองทันทีต่อสิ่งใหม่
จริงๆ เกมนี้จะนับเป็นแนวกึ่ง rhythm กึ่ง reflex ก็ได้ เพราะเราไม่ได้สนใจแค่ตัวอักษรที่โผล่มาให้พิมพ์เพียงอย่างเดียว ในบางจังหวะเราสามารถจำว่าต้องยิงซอมบี้ตัวไหนก่อน เพื่อในกลับมาเล่นด่านเดิมด้วยคะแนนที่สูงขึ้นได้ จุดเด่นอีกอย่างในเกมแบบนี้คือความสามารถในการร่วมมือกันเล่น ที่ผู้เล่นต้องสมดุลระหว่างการยิงรัวๆ กันโดนซอมบี้กัดตาย กับการเหลือซอมบี้ไว้ให้เพื่อนร่วมทีมยิงเพื่อให้คะแนนไม่ห่างกันนักด้วย (หรือใครเป็นพวกซาดิสม์ ชอบทำคะแนนให้ห่างกันเยอะๆ ก็ไม่ว่ากัน 55+)

Diablo II

  • goal set - เกมตั้งจุดมุ่งหมายให้แล้ว
  • co-op - ร่วมมือกันเล่นได้
  • leveling - ตัวละคร/ด่านมีพัฒนาการ
ผมจำได้เลยว่าตอนอยู่ม.ต้นเล่น Diablo II ก็รู้สึกหนุกดีนะ เดินผ่านด่านไปเรื่อยๆ ไม่คิดอะไรมาก ถ้าตรงไหนดูยากก็กลับไปเล่นด่านเก่าอัพเลเวลซักหน่อย เพื่อที่จะได้ใช้ท่าใหม่ๆ ตีมอนได้แรงขึ้น เล่นไปเล่นมาก็รู้สึกว่า เอ๊ะทำไมเหมือนฟาร์มเลเวลได้ช้าลง ค่าประสบการณ์สำหรับเลเวลถัดไปที่เพิ่มขึ้นเร็วเป็นเอกซ์โพเนนเชียลก็ยังไม่น่าทำให้รู้สึกได้ขนาดนี้ จนเมื่อได้อ่านสูตรการคิดค่าประสบการณ์ที่ได้จากมอนตัวนึงแล้วถึงบางอ้อ ความต่างของเลเวลมอนกับผู้เล่นก็มีผลต่อค่าประสบการณ์ด้วย นั่นทำให้ผมรู้สึกประทับใจในรายละเอียดและความเชื่อมโยงกันของค่าต่างๆ ภายในเกม ไม่ใช่การที่ developer สักแต่คิดว่าใส่ factor นี้เข้ามาแล้วเกมมันน่าจะสนุกขึ้น ซึ่งในความเป็นจริงเกมมันอาจจะสนุกขึ้น 10 นาที แต่หลังจากนั้นก็กลายเป็นหายนะเพราะเสียสมดุลไปเรียบร้อย



จริงๆ ตอนเอาไอเดียมาแปลงเป็น game play ก็ไม่ได้ไล่คิดถึงเกมอื่นเยอะขนาดนี้หรอก (เอ หรือมันจะเป็น unconscious?) แต่อยากได้แบบ infinity+leveling และคิดว่าต้องปรับแน่ๆ ดังนี้

  • เกมฝึกพิมพ์ภาษาไทยยากมาก ทั้ง font เล็ก ทั้งสระบนล่าง ... ทำภาษาอังกฤษง่ายกว่า
  • 1 นาทีจะว่านานก็นานเกินไปสำหรับเกมฝึกพิมพ์ ยิ่งเจอแต่คำซ้ำๆ นี่ยิ่งน่าเบื่อ
  • แต่ 1 นาทีก็สั้นเกินไปถ้า random คำมาได้ไม่ซ้ำกันเลย
  • เลยคิดว่าทำเป็นระบบหัวใจดีกว่า พิมพ์ไม่ทันก็โดนโจมตี หัวใจหมดเมื่อไหร่ก็จบเกม
  • ถึงแม้ส่วนกริยาจะไม่ซ้ำจากการ random แล้ว แต่ให้พิมพ์ชื่อ (@neizod) บ่อยๆ ก็เบื่อได้เช่นกัน เลยทำระบบเก็บคำกริยาหลายๆ คำไว้ก่อน แล้วค่อยพิมพ์ชื่อเมื่อต้องการจบประโยค
  • อนึ่ง ในความเป็นจริงเกมก็ไม่ได้มี word list ใหญ่ขนาดนั้น เลยหาแผนมารับมือเวลาเจอคำซ้ำไม่ให้รู้สึกเบื่อ คือเมื่อพิมพ์ศัพท์ซ้ำจะทำการดึงคำนั้นออกจากประโยคที่กำลังสร้างซะเลย

ตอนนี้ก็ได้ game play หลักมาครบแล้ว ซึ่งเอาจริงๆ prototype พวกนี้ให้ทำ 3-4 วันก็เสร็จ

แต่ก่อนหน้านั้นก็มีสิ่งที่สำคัญไม่แพ้กัน คือการเลือก engine สำหรับโปรเจคนี้ครับ

  • เลือกเป็นเว็บเพราะทำ GUI ได้ง่ายและเร็วสุด (จริงๆ คือเป็นอยู่แค่เว็บ 555+)
  • ตอนแรกก็ว่าจะใช้ canvas เพราะทุกอย่างสามารถยัดเป็น JavaScript ล้วนเลยได้ แต่ก็จะไม่ได้ฝึก CSS อีกทั้งเกมนี้ยังเป็นแบบ text-base ไม่ใช่เกมตัวละครลุยด่าน เลยคิดว่าเล่นกับ DOM ดีกว่า
  • จะเล่นกับ DOM ทั้งที ก็ต้อง jQuery และ position: absolute สิครัช
  • ถ้า JavaScript เขียนสั้นๆ ก็พอไหว แต่มาเขียนซับซ้อนๆ แบบนี้ CoffeeScript เถอะครัฟ จะได้ไม่เป็นภาระ @plynoi
  • ไม่ได้ใช้ framework อื่นใดช่วยอีกเพราะไม่ค่อยรู้จัก + learning curve ท่าทางจะสูงน่าดู รอลุยโปรเจคนี้เสร็จพลังวัตรน่าจะกระโดดข้ามไปอีกขั้น
  • ไม่อยากแตะ database เท่าไหร่เพราะต้องใช้ host แยก ไม่งั้นก็ไปขอ AppEngine ที่ตอนนี้เก็บครบโควต้าแล้ว
  • เลยกะว่าเอาง่าย เล่นเกมเสร็จก็ทวีตคะแนนซะเลย ใครจะโกงก็ช่างมัน (แต่ประสบการณ์ก็บอกว่าไม่ค่อยมีใครโกงหรอก เท่าที่สังเกตจาก Temple Run)
  • พอไม่แตะ database ทุกอย่างเป็น static หมด ก็หา host ง่ายขึ้นจม แต่มีที่เดียวที่ผุดมาในใจคือ GitHub Pages

พอรู้ spec env ครบก็สบายใจไปกว่าครึ่งแล้ว

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

  • ค่าหลักของเกมคือคะแนน ซึ่งควรคิดตามจำนวนตัวอักษรต่อคำที่พิมพ์เข้าไป เช่น kick ได้ 4 คะแนน เพราะมี 4 ตัวอักษร
  • อย่างไรก็ตาม การคิดคะแนนที่ 1 ตัวอักษร = 1 คะแนน อาจทำให้เบื่อได้ และยังไม่ได้ใช้ประโยชน์จากการบังคับพิมพ์ชื่อ (@neizod) เท่าไหร่ด้วย
  • คิดว่าอีกปัจจัยที่ควรจะมีผลต่อคะแนนคือเลเวล (เหมือนเกมอื่นๆ) ยิ่งผู้เล่นเลเวลสูงก็น่าจะได้คะแนนเยอะ เลยเอาคะแนนที่ได้ไปคูณด้วยเลเวลเมื่อพิมพ์จบประโยค
  • แต่ก็ไม่อยากให้ผู้เล่นเก็บคำศัพท์ไว้เป็น 20 คำแล้วค่อยจบประโยคเพื่อคูณคะแนนตูมเดียว เลยให้แค่คำแรกของประโยคที่ได้คูณเลเวลเต็มๆ คำถัดๆ มาก็คูณกับค่าเลเวลที่ลดลงเรื่อยๆ จนคำท้ายๆ ไม่ได้ประโยชน์จากการเก็บดองคำศัพท์ไว้ไม่ยอมสร้างเป็นประโยคซักที
  • ส่วนการเพิ่มเลเวลนั้นก็คิดจากคะแนน ตรงนี้ไม่อยากให้คะแนนต่อเลเวลเป็นกราฟเส้นตรง เพราะจะทำให้ตัวคูณเพิ่มเร็วมาก และเนื่อจากไม่ค่อยชอบกราฟกำลังสอง เลยเลือกค่าคะแนนสำหรับเพิ่มเลเวลต่อไปเป็นแบบเอกซ์โพเนนเชียลซะ
  • หัวใจลดได้ก็ย่อมเพิ่มได้ ง่ายสุดไม่คิดอะไรแล้ว เลเวลเพิ่มเมื่อไหร่ หัวใจเพิ่มเมื่อนั้น (เชื่อว่าระบบการเพิ่มเลเวลถูกออกแบบมาเป็นอย่างดี 555+)
  • แต่แน่นอนว่าเพิ่มเลเวล = เพิ่มความยาก ในที่นี้ก็คือเพิ่มจำนวนคำศัพท์บนหน้าจอให้ออกมาเท่ากับเลเวล และเพิ่มความเร็วให้มันทีละนิดๆ

ถ้าแม่น math หน่อย ระบบพวกนี้ implement เข้าไปไม่นานครับ ดีไม่ดีเร็วกว่าออก prototype ข้างบนอีก ปัญหาคือจะทำได้เร็วๆ นี่ต้องไอเดียบรรเจิดมาก ควรหา paper ด้านจิตวิทยาและการออกแบบเกมอ่านตุนไว้เป็นอย่างยิ่ง :p

พอ core ทั้งหลายเสร็จแล้ว ก็แต่งสวยครับ

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

หลังจาก implement เสร็จหมดก็ได้ฤกษ์ปล่อยเกม ตามไปเล่น (ด้วยความรัก) ได้ที่ neizod.github.io/kick เลยครับ \(;w ;)/

Feb 7, 2014

พื้นที่สำหรับความผิดพลาด

วันก่อนเห็นทวีตนี้



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

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

หลายคนโชคดีหน่อย เกิดมาในตระกูลเศรษฐี ก็มีของเล่นได้หลากหลายชิ้นกว่าเพื่อนๆ

หลายคนขยันหน่อย มีความสามารถโดดเด่น ก็มีชื่อเสียงได้รับการยอมรับจากเพื่อนๆ

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

โดยที่ไม่ได้คิดถึงสิ่งที่ต้องแลกมา คือพื้นที่สำหรับความผิดพลาด

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

...

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

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

เพราะเราไม่รู้ว่าจะได้มีโอกาสผิดพลาดโดยไม่เจ็บตัวอีกเมื่อไหร่

Feb 1, 2014

อำนาจ

Power tends to corrupt, and absolute power corrupts absolutely. Great men are almost always bad men.

-- Lord Acton

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

หึหึหึ

Jan 5, 2014

0!=1

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

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



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

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



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


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



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

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

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

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



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

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

Jan 1, 2014

ปี 2013 ของฉัน

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

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

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