Monthly Archives: January 2013

ขี้เกียจเขียน For loop ภาค 3 – ของจริง!

อารมณ์ยังค้างไม่หายจากภาค 2 ที่เขียนไปเมื่อวานนี้ วันนี้ขอเขียนอย่างต่อเนื่องอีกซักบล๊อกนึง เช่นเคยใครยังไม่ได้อ่านตอนที่แล้ว เชิญติดตามได้ที่ ขี้เกียจเขียน For loop ภาค 2 ครับ

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

แต่โดยส่วนตัวมีความประทับใจกับการแก้โจทย์ข้อนี้ได้เป็นอย่างมาก เพราะเป็นครั้งแรกที่สามารถแก้ปัญหาแข่งขัน โดยไม่มีการ mutate ค่าของตัวแปร ได้สำเร็จเป็นครั้งแรก ถือเป็นหนึ่งใน achievement จากการที่นั่งขุดนั่งเล่น functional programming concept มาซักพัก

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

โจทย์โดยสรุปมีอยู่ว่า เค้าให้ string ที่มีตัวอักษร a-z ปนตัวใหญ่ตัวเล็กแก่เรามา ให้ assign เลข 1 – 26 ให้แก่ตัวอักษรแต่ละตัว แบบไม่สนใจตัวใหญ่ตัวเล็ก ให้ได้ผลรวมเยอะที่สุด

เช่น ABbCcc => c*26 + b*25 + a*24 = 152

อย่าลืม ตามสไตล์พวกเรา ห้ามใช้ loop และห้าม reassign ค่าให้กับตัวแปรครับ

ผมแก้ปัญหาข้อนี้ได้ด้วย code ชุดนี้

s = 'ABbCcc'
s.split('').map(&:downcase).select { |c| c =~ /[a-z]/ }
 .group_by(&:to_s).map { |_,v| v.count }.sort { |x,y| y <=> x }
 .zip((1..26).to_a.reverse).inject(0) { |a, (n,e)| a + n*e }

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

s.split('') #=> ["A", "B", "b", "C", "c", "c"]
.map(&:downcase) #=> ["a", "b", "b", "c", "c", "c"]
.select { |c| c =~ /[a-z]/ } #=> ["a", "b", "b", "c", "c", "c"]
.group_by(&:to_s) #=> {"a"=>["a"], "b"=>["b", "b"], "c"=>["c", "c", "c"]}
.map { |_,v| v.count } #=> [1, 2, 3]
.sort { |x,y| y <=> x } #=> [3, 2, 1]
.zip((1..26).to_a.reverse) #=> [[3, 26], [2, 25], [1, 24]]
.inject(0) { |a, (n,e)| a + n*e } #=> 152

มาลองดูด้วย Clojure กันบ้าง

(->> "ABbCcc"
     (re-seq #"[a-zA-Z]")
     (map clojure.string/lower-case)
     (group-by identity)
     (map #(count (last %)))
     (sort >)
     (zipmap (range 26 1 -1))
     (map #(* (key %) (val %)))
     (reduce +))

ปล. แอบไปดูเฉลยจากทาง Facebook มา พบว่าเค้าใช้วิธีเดียวกับเราเป๊ะ แต่เขียนด้วย Python

Advertisements

ขี้เกียจเขียน For loop ภาค 2

หลังจากคราวที่ผมได้มาเล่าไปแล้วว่าผมเอาตัวรอดในฐานะโปรแกรมเมอร์มาได้ไงเป็นเวลากว่าปีโดยไม่ใช้ for loop ใครยังไม่ได้ติดตามความขี้เกียจครั้งที่แล้ว เชิญติดตามได้ทางลิงค์นี้ครับ ขี้เกียจเขียน For loop คราวนี้ จะมาเล่าถึงเทคนิคนึง ซึ่งผมเพิ่งได้เรียนรู้เมื่อไม่กี่เดือนมานี้เอง คิดว่าน่าจะมีประโยชน์กับท่านที่สนใจศาสตร์ทาง functional programming และ immutable data ไม่มากก็น้อย

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

ตัวอย่างของปัญหาในครั้งนี้ คือ ฟอร์แมทผลลัพธ์ของการแข่งขันเขียนโปรแกรม ซึ่งไม่ว่าจะเป็นทั้ง Google Code Jam หรือ Facebook Hacker Cup จะผู้แข่งขันสร้างไฟล์สำหรับผมลัพธ์ซึ่งมีหน้าตาเป็นแบบนี้

Case #1: 100
Case #2: 200
Case #3: 300
...

เราจะมาเขียน code ที่รับอาเรย์ของผลลัพธ์แต่ละข้อมา(เช่น [100,200,300,…]) แล้วสร้าง string ของฟอร์แมทผลลัพท์เหมือนด้านบนกัน โดยอย่าลืมว่าเราไม่มี for loop นะ

เริ่มต้นด้วยวิธีแรกที่ชาว Ruby นิยมใช้กัน each_with_index

output = [100,200,300]
result = ''
output.each_with_index do |e,i|
  result += "Case ##{i+1}: #{e}\n"
end
puts result

วิธีนี้ใช้ได้ดีครับ ติดอยู่เพียงแค่ว่ามันมีการ assign ค่าใหม่หลายครั้งให้แก่ตัวแปร result ไม่ถูกใจขาโจ๋ pure function, immutable data อย่างเราเลย

มาดูวิธีต่อไป วิธีนี้ไม่มีการ assign ค่าให้แก่ตัวแปรเลย แต่ขออนุญาตสงวนสิทธิ์ห้ามลอกเลียนแบบนำไปใช้นะครับ ผมเคยใช้วิธีนี้ตอนสมัยหัด functional programming ใหม่ๆ ด้วย Scala เอาไปแข่ง Code Jam ครับ ผลลัพธ์ครับ ทำไม่เสร็จซักข้อครับ

วิธีนั้นคือ recursive ครับ

def results(a, i)
  if a.empty?
    ''
  else
    "Case ##{i}: #{a.first}\n" + results(a[1..-1], i+1)
  end
end
results([100,200,300], 1)

เหนื่อยมากครับ แต่ก็ถือว่าเอาตัวรอดมาได้ แบบไม่ต้องมีการ assign ตัวแปรเลยครับ

มาดูวิธีที่สามกันบ้าง ซึ่งเป็นวิธีที่แสดงเทคนิคที่ทำให้ผมมานั่งเขียน blog นี้กันครับ

 output = [100,200,300]
 index = (1..output.length).to_a
 output.zip(index).map { |(e,i)| "Case ##{i}: #{e}" }.join("\n")
 

เทคนิคมันอยู่ตรงไอ่เจ้า zip นี่แหละครับ ในเมื่อไม่มี i ก็หา i มาแปะเข้าไปซะเลย คราวนี้ก็จะได้คู่ตัวเลข e และ i ไปทำอะไรก็ได้ตามใจเราครับ จะประยุกต์ให้เป็น i+2, i*8 ยังไงตามใจเรา เลือกสร้างอาเรย์ของ “i” แบบตัวแปร index ได้ตามความเหมาะสมเลยครับ

เจ้า zip นี้ชื่ออาจจะต่างกันไปในแต่ละภาษาครับ โดยการทำงานของมันคือเอาอาเรย์มา 2 (หรือหลายอัน) แล้วสร้างอาเรย์ใหม่โดยกรุ๊ปค่าในตำแหน่งเดียวกันเข้าด้วยกันครับ

ความรู้สึกของคนที่มีอุปกรณ์ iOS รุ่นล่าสุด 2 ชิ้น

จากคนที่เคยพูดว่ามีความสุขดีกับมือถือแอนดรอยด์ที่มีอยู่ กลายเป็นคนที่มี iPhone5 และ iPad Mini ในระยะเวลา 1 เดือนครึ่ง เรามาฟังคำชี้แจงของเค้ากันครับ

ถ้าย้อนกลับไปเมื่อราว 2 ปีที่แล้ว ใครเชียร์ให้ผมซื้อ iPhone ผมจะปฏิเสธและตอบกลับด้วยเหตุผลที่ว่าผมใช้ Linux ไม่มี iTunes ให้ sync จะไปใช้ iPhone ได้ไง ณ ตอนนั้นผมเลือกซื้อ HTC Desire S ซึ่งถือเป็น smart phone เครื่องแรกของผม

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

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

  1. อ่านหนังสือ software development text book
  2. ดู screencast, presentation
  3. อ่าน feed, twitter, facebook

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

ในขณะนั้นเป็นช่วงที่ Nexus 7 เปิดตัวเป็นช่วงแรกๆ ผมอยากได้ แต่ขี้เกียจจะต้องใช้ท่าพิเศษในการหาซื้อ หรือซื้อราคาแพงจากคนที่หิ้วมา แถมเมื่อมีคนนำเข้ามาขายอย่างเป็นทางการ ก็กลายเป็นว่าแพงกว่าราคาที่ US 2-3 พันบาทอีก ประกอบกับมีข่าวของ iPad Mini ผมจึงตัดสินใจรอและซื้อ iPad Mini เพราะคิดว่ามันต้องถูกว่า iPad ธรรมดาแน่นอน ทำให้ราคาไม่ต่างกับ Nexus 7 ที่ขายในโซนนี้เท่าไหร่

จะเรียกได้ว่า logistic ของ Apple ชนะใจของผมอีกคร้ังเลยก็ว่าได้ เพราะสินค้าราคาต่างจาก US แค่ประมาณ 40 เหรียญ ซึ่งเทียบได้กับค่า shipping หากเราซื้อแล้วให้ส่งมา และสินค้ายังถูกจัดจำหน่ายในแถบนี้ได้เร็วมาก ครั้งที่แล้วผมตัดสินใจซื้อ Macbook Air เมื่อเทียบกับ Thinkpad ด้วยเหตุผลเดียวกันนี้ ผมชอบ logistic ของ Apple มากครับ

ในช่วงที่ผ่านมาตั้งแต่ตัว iPad Mini เปิดตัว จนแม้กระทั่งถึงตอนนี้ประเด็นเรื่องการไม่มี Retina Display เป็นเรื่องที่ถูกพูดถึงเป็นอย่างมาก แต่สำหรับผมแล้วหากต้องรอไปอีก 6  เดือน  ผมก็ต้องเสียเวลารอความรู้ที่จะได้จากการอ่านหนังสือ และดู presentation และ screencast ไปอีก 6  เดือน คงไม่คุ้มเท่าไหร่ และอีกหนึี่งปัจจัยคือ ผมไม่เคยมีอุปกรณ์ที่มีจอแบบ Retina Display เลย ผมเลยคิดว่าคงจะไม่ระคายเคืองตาใดๆ

มาพูดถึงสิ่งที่ชอบ ไม่ชอบ และสิ่งที่ทำให้คิดถึงแอนดรอยด์ จาก iPad Mini และ iOS กันบ้าง

สิ่งที่ชอบ

  • น้ำหนักของ iPad Mini เนื่องด้วยตั้งใจจะนำมาใช้ขณะโหนรถไฟฟ้า น้ำหนักจึงเป็นสิ่งสำคัญ
  • การที่ file system ไม่ถูกแชร์ให้ app ทำให้เมื่อลบ app แล้วให้ความรู้สึกว่าทุกอย่างที่เกี่ยวกับมันถูกลบจากเครื่องไปจริงๆ
  • Podcast อันนี้เพราะ Apple ผูกขาดระบบ podcast อยู่
  • Wifi sync คอนเซปถือว่าดีมาก แต่ระบบยังห่วยๆ ค้างๆ ช้าๆ อยู่
  • แบตทน อันนี้น่าจะเป็นเพราะไม่ได้ใช้โปรเซสเซอร์ตัวล่าสุด และไม่มี Retina Display

สิ่งที่ทำให้คิดถึงแอนดรอยด์

  • การที่ต้อง login Google app สำหรับทุกๆ app ที่จะใช้บริการ อันนี้เป็นเรื่องช่วยไม่ได้ ก็ยังดีที่ใช้ได้ ถ้าไปใช้ iCloud บนแอนดรอยด์น่าจะแย่กว่า
  • ไม่สามารถดาวโหลดไฟล์ใดๆ ด้วย browser ได้ อันนี้ห่วยสุดๆ จริงๆ
  • การที่ file system ไม่ถูกแชร์ให้ app ทำให้การจะใช้โปรแกรมๆหนึ่งเปิดไฟล์ ยากลำบากมาก แถมบางครั้งทำให้ไฟล์เดียวกันกระจายอยู่กับหลาย app ทำให้เปลืองพื้นที่อีก
  • เวลาดาวโหลดไฟล์หรือทำการประมวณผลอะไรอยู่แล้วมีเหตุให้ต้องออกจากโปรแกรม หรือปิดหน้าจอ การดาวโหลดหรือการประมวณผลนั้นหยุดทำงาน
  • App intent ของแอนดรอยด์ ตัวอย่างเช่น ไม่ใช่ทุกที่ๆ  Open with Safari ได้แล้วจะ Open with Chrome ได้
  • Widget สำหรับ app อย่างเช่น reminder จะมีประโยชน์กว่ามาก ถ้าเรามองเห็นได้เลยโดยไม่ต้องกดเข้าไปดู

ประมาณ 1 เดือนหลังจากได้ iPad เหตุการณ์ที่ทำให้ผมได้เป็นเจ้าของอุปกรณ์ iOS ชิ้นที่ 2 ก็มาถึง เมื่อคุณพ่อของผมถือ iPhone5 กลับมาและยังไม่มั่นใจว่าจะทำอย่างไรกับมัน เพราะน้องสาวสองคนเพิ่งได้จะ 4S ไป เหมือนกับมีคลื่นรังสีอะไรบางอย่างจากเจ้าอุปกรณ์ชิ้นนี้ ภายในระยะเวลา 1 วัน เจ้า Desire S ของผม ลำโพงสำหรับสนทนาก็ไม่มีเสียงออกมาดื้อๆ ไปซะอย่างนั้น หากเสียบหูฟังก็ยังได้ยินเสียง และสามารถสนทนาได้อย่างปกติ เจ้า iPhone5 จึงได้มาอยู่กับผมตั้งแต่วันนั้น

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

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

สรุป presentation: The Deep Synergy Between Testability and Good Design by Michael Feathers

The Deep Synergy Between Testability and Good Design.

ได้ลิงค์ talk นี้มาจาก pick ของ RubyRouges ตอนล่าสุด แปลใจว่า talk นี้มันประมาณ 2 ปีมาแล้ว ทำไมถึงไม่เคยเห็นผ่านตามมาก่อน ปกติ topic แบบนี้ โดยคนพูดที่รู้จักแบบนี้ จะไม่ค่อยพลาดเท่าไหร่

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

มาเริ่มกันจาก quote เท่ๆ กันก่อน

ดีไซน์ดีทำให้เทสง่าย เทสง่ายไม่ได้แปลว่าดีไซน์ดี

อีกอัน

เมื่อมันเทสยาก อย่าแก้เทสให้เทสง่าย ให้มองหาปัญหาของดีไซน์ แล้วแก้ดีไซน์ เทสก็จะง่ายไปเอง

ต่อมาๆ พูดถึงปัญหาในการเทสกัน

  • อยากจะ access  local variable ในเทส – method ทำหลายอย่างเกินไป
  • setup เทสยาก – coupling เยอะเกินไป
  • เทสตายแบบรันไม่เสร็จ – คลาสไม่ดูแลสิ่งที่ตนควรทำ (เช่น ไม่ release memory)
  • รันเทสเดี่ยวๆ ผ่าน, รันหลายๆ เทสพร้อมกัน ไม่ผ่าน – มี global state
  • เฟรมเวิร์คทำให้เทสยาก – โดเมนของงานเราปนอยู่ในเฟรมเวิร์คมากเกินไป
  • ต้อง stub ผลลัพธ์ของ stub ซ้อนกันหลายชั้น – คลาสรู้จักส่วน private  collaborator ของคลาสอื่น
  • stub หรือ mock dependency ยาก – code อิงกับ implementation ของคลาสอื่นมากเกินไป
  • เทสคลาสแล้วมีผลลัพธ์อื่นที่คาดไม่ถึง (เช่น อยู่ดีๆ ก็ส่งอีเมล์ออกไปจริงๆ) – คลาสใหญ่ไปเกินที่เราจะรู้ว่ามันทำอะไรบ้าง
  • ไม่มีช่องให้ใส่ parameter เพื่อจะทดสอบกรณีต่างๆ ง่ายๆ – คลาสทำหลายอย่างเกินไป
  • เวลาจะเทสต้องเตรียม parameter เยอะเกินไป – คลาสหรือเมท็อด ทำหลายอย่างเกินไป
  • อยากจะเขียนเทส private method – คลาสทำหลายอย่างเกินไป
  • แก้ code นิดหน่อย unit test พังเยอะ – แปลว่าเราดีไซน์ในลักษณะที่เข้าไปแก้คลาสง่ายกว่าเขียนต่อยอด

ตอนท้ายผู้พูดได้เสนอ แนวคิดว่าทำไมดีไซน์ดีถึงทำให้เทสง่าย ทำไมมันถึงเกี่ยวข้องกัน ว่าดังนี้

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