คลังเก็บหมวดหมู่: คอมพิวเตอร์จ๋า

บล็อกที่เกี่ยวกับคอมพิวเตอร์

ข้อจำกัดใน iOS ที่มีผลต่อ UX ของ Notification

ผมใช้มือถือ Android มาโดยตลอด แต่ตอนตัดสินใจซื้อ tablet ได้ไปลองเล่น iPad 2 กับ Galaxy Tab 10 เทียบกันดู ปรากฏว่าความลื่นไหลของ iPad 2 นี่มันสุดยอดจริงๆ (ณ เวลานั้นน่ะนะ ตอนนี้เปลี่ยนมาใช้ Nexus 7 ละ :P)

ระหว่างใช้ iPad ก็นึกสงสัย ว่าทำไมแอพดังๆ หลายตัวมันถึงมี User Experience (UX) แบบนี้เนี่ย ถ้าปรับปรุงอีกหน่อย ผู้ใช้จะสะดวกขึ้นอีกเยอะเลย

วันนึงผมมีโอกาสได้เขียนโปรแกรมบน iOS แล้วได้พบกับข้อจำกัดหลายๆ อย่างของ Apple Push Notification (APN) ซึ่งมีทั้งข้อดี/ข้อเสีย และก่อให้เกิดข้อจำกัดกับ UX ของแอพใน iOS เอง

วันนี้เลยอยากแชร์ข้อจำกัดนั้น (“ทำไมแอพมัน…ฟระ”), ข้อดีข้อเสียของ APN, และท่า Compromise เทพๆของแอพ Facebook เพื่อปรับปรุง UX ครับ

คำถาม

  1. เวลา Twitter เตือนว่ามี Direct Message ใหม่
    ทำไมเรากดเข้าไปแล้วไม่เจอข้อความทันที?
    ทำไมต้องรอโหลด message อีกรอบ รอให้โหลด message เสร็จแล้วค่อยเตือนไม่ได้เหรอ? จะได้กดเข้าไปเจอ message ทันทีไม่ต้องรอ
  2. เวลา LINE เตือนว่ามีข้อความใหม่ ทำไมบางทีกดเข้าไปแล้วไม่เจอข้อความ?

ตอนแรกผมก็โทษโปรแกรมเมอร์ครับ… ทำไมแกไม่ออกแบบแอพให้ดีกว่าเน้!
พอได้เขียนโปรแกรมกับ APN แล้วเริ่มเข้าใจหัวอกคนทำแอพมากขึ้น

อ่านเพิ่มเติม ข้อจำกัดใน iOS ที่มีผลต่อ UX ของ Notification

Advertisements

Catch kill signal in Python

We can check for Ctrl-C with KeyboardInterrupt exception as follows:

try:
    while True:
        print "Echo ", raw_input(">")
except KeyboardInterrupt:
    print "Good bye"

When python process was killed, we will not get KeyboardInterrupt. But we can instead catch SIGTERM sent by kill command.

In order to catch SIGTERM, we can do:

import signal
import sys

def signal_term_handler(signal, frame):
    print 'got SIGTERM'
    sys.exit(0)

signal.signal(signal.SIGTERM, signal_term_handler)

List of signal is available in POSIX Signals. Note that SIGKILL cannot be caught.

Schlemiel the Painter

นาย Schlemiel เป็นช่างทาสี เขารับงานตีเส้นถนน

วันแรกทาสีได้ 300 เมตร หัวหน้าเขาก็ชมว่า “เยี่ยมมากเลย”
วันที่สองทาสีได้ 150 เมตร หัวหน้าบอกว่า “อืม ก็ยังโอเคอยู่นะ”
วันที่สามทาสีได้ 30 เมตร หัวหน้าเขาบอก “เฮ้ย นี่มันใช้ไม่ได้เลยนะเนี่ย วันแรกแกตีได้มากกว่านี้ตั้ง 10 เท่า”

ช่างทาสีบอกว่า “ช่วยไม่ได้นี่นา ก็กระป๋องสีมันอยู่ไกลจากฉันมากขึ้น มากขึ้นทุกวัน”

เขาวางกระป๋องสีไว้ที่จุดเริ่มต้น โดยไม่ได้ย้ายมันตามมา เขาจึงต้องเดินกลับไปไกลขึ้นๆ ทุกครั้งที่เดินกลับไปจุ่มแปรงสี

เรื่องนี้แต่งขึ้นโดย Joel Spolsky เพื่อเลียนแบบพฤติกรรมของ C String ที่การ concatenate ทำได้ช้า เพราะต้องวิ่งจากหัว string ไปจนสุด string ก่อนทุกครั้ง ถึงจะหาตำแหน่งเพื่อเขียน string ต่อท้ายได้ครับ

อ่านเพิ่มได้ใน http://en.m.wikipedia.org/wiki/Schlemiel_the_Painter’s_algorithm

ใช้ python dict อย่างเท่ๆ

dict ใน python เป็นโครงสร้างข้อมูลชนิดนึง ที่ใช้เก็บ key -> value ได้ เวลาเขียนโปรแกรมกับ dict เราก็มักจะมีท่าที่ต้องเขียนโปรแกรมซ้ำๆ… ผมเพิ่งรู้จักท่าอื่นๆ ที่อยากเอามาแชร์ตามนี้ครับ:

1. defaultdict

เรามักจะต้องเขียนโปรแกรมเช็คว่ามีคีย์ (key) นี้อยู่ใน dict อยู่แล้วรึเปล่า ก่อนที่จะนับค่าเพิ่ม (increment) เช่นโปรแกรมนับความถี่ของตัวอักษรข้างล่างนี้

text = "aaaabbcdeff"
freq = {}
for c in text:
    if c not in freq:
        freq[c] = 0
    freq[c] += 1

เราสามารถใช้ defaultdict มาช่วยให้โค้ดดูสวยงามขึ้นได้ตามนี้ (ขอบคุณพี่ @lewcpe ที่แนะนำให้รู้จักฮะ)

from collections import defaultdict
text = "aaaabbcdeff"
freq = defaultdict(int)
for c in text:
    freq[c] += 1

ตัวอย่างข้างบน เราบอก defaultdict ว่าถ้าคีย์ที่ต้องการยังไม่มี ให้เรียก int() เพื่อกำหนดค่าเริ่มต้น

x = freq['a']       # ปกติจะ KeyError, แต่ถ้าเป็น defaultdict มันจะเรียก
freq['a'] = int()   # เพื่อกำหนดค่าเริ่มต้นให้

2. .setdefault

เวลาเขียนโปรแกรมอ่านค่าจาก dict เราสามารถใช้เมธอด .get เพื่ออ่านค่าจาก dict ได้โดยไม่ต้องกังวลว่าจะมี คีย์นั้นอยู่รึเปล่า เช่น

options = { 'width': 500 }
width = options.get('width', 1000)      # 500
height = options.get('height', 300)     # 300

ตัวอย่างข้างบน เราสามารถอ่านค่า options[‘width’] ได้สำเร็จ ส่วน height เราได้ค่า default คือ 300 เพราะไม่มีคีย์ height ใน options

สำหรับเมธอด .setdefault นั้นมีพฤติกรรมคล้ายๆ .get แต่มันจะกำหนดค่า default กลับลงไปใน dict ด้วยถ้ายังไม่มีคีย์นั้นใน dict

people_by_blood = { "A": ["Adam", "Eva"],
                    "B": ["John"]}
people_by_blood.setdefault("O", []).append("Job")
people_by_blood.setdefault("A", []).append("Nin")

print people_by_blood
{"A": ["Adam", "Eva", "Nin"],
 "B": ["John"],
 "O": ["Job"] }

3. คลาส collections.Counter

คลาส Counter เป็นคลาสเฉพาะกิจ ใช้นับ frequency ครับ ดูตัวอย่างเลยดีกว่า

from collections import Counter
print Counter("aaaabbcdeff")
# Counter({'a': 4, 'b': 2, 'f': 2, 'c': 1, 'e': 1, 'd': 1})
print Counter("aaaabbcdeff").most_common(3)
# [('a', 4), ('b', 2), ('f', 2)]

นอกจากใช้นับ frequency ได้ง่ายแล้ว ยังมีเมธอดสะดวกๆ เช่น most_common ให้ใช้ด้วย

ใครมีท่าอื่นๆ ของ dict ใน python อย่าลืมมาแชร์กันนะครับ ^^

Migrating WordPress Blog to AppFog

I used to run this blog on a shared web hosting. Recently, I came across AppFog, a PaaS provider, who gives a generous free plan to developers (2GB of rams, and 10 services). It runs PHP, Django, Rails, and so forth.

So I gave it a try. Creating WordPress blog on AppFog is just 2-3 clicks away. You pick an application to deploy, and choose target site (Amazon US/AP, Windows Azure, Rackspace, …)
อ่านเพิ่มเติม Migrating WordPress Blog to AppFog

Web development in 2000 vs. 2012

It’s going to be Barcamp Bangkhen 2012 soon. So I tried to come up with some topics for the camp.

Because I have a chance to try Bootstrap framework while I was working on a website at work. I found it is a lot more convenient than it was in 2000. อ่านเพิ่มเติม Web development in 2000 vs. 2012

หมาป่า แกะ กะหล่ำปลี

เกมพาคนสัตว์สิ่งของข้ามแม่น้ำนี่มีหลายเวอร์ชั่นมาก เกมนึงที่น่าจะเคยเล่นกันคือ “เกมพาหมาป่า แกะ และกะหล่ำปีข้ามแม่น้ำ” ในเกมนี้ เราต้องพาของทั้งสามอย่างข้ามฝั่งแม่น้ำโดยมีกฎอยู่ว่า

  • พาของข้ามแม่น้ำได้ทีละ 1 อย่าง
  • ถ้าอยู่กันสองต่อสอง หมาป่าจะกินแกะ และ แกะจะกินกะหล่ำ

อ่านกฎ อ่านกติกาเข้าใจยาก… คลิกที่รูปเพื่อลองเล่นเลยดีกว่าครับ

 

wolf-sheep-cabbage-300x179
เกมหมาป่า แกะ กะหล่ำ

อ่านเพิ่มเติม หมาป่า แกะ กะหล่ำปลี

Django: QuerySet is not a List

เวลาดึงข้อมูลจาก Model เช่น

blogs = Blog.objects.all()    # ได้ QuerySet ของบล็อกหลายๆ อัน
print blogs[0].title
print blogs[0].body
print blogs[1].title
print blogs[1].body

จะเห็นว่าเราใช้ QuerySet ได้เหมือนเป็นอาเรย์หรือ list เลย
แต่สิ่งที่ต้องระวังคือ blogs เป็น QuerySet ไม่ใช่ list

เวลาอัพเดทข้อมูลใน model ต้องระวัง

bug แบบนี้

blogs[0].title = "New Title"
blogs[0].body = "new content"
blogs[0].save()

ถ้ารันโค้ดข้างบนแล้ว blogs[0] จะไม่เปลี่ยนแปลงค่าอะไรเลย เพราะ:

  • blogs[0] ในแต่ละบรรทัดเป็น object คนละอันกัน (คนละ instance กัน)
  • การเรียก blogs[0] แต่ละครั้ง QuerySet จะให้ object ใหม่ทุกครั้ง ทำให้การกำหนดค่ามีผลกับ object ในบรรทัดนั้นเฉยๆ
  • blogs[0].save() ในบรรทัดที่ 3 จะอ่านค่า blogs[0] จาก database และ save() กลับไปโดยยังไม่ได้แก้ไขอะไรเลย

ที่ถูกต้องควรเป็น

b = blogs[0]
b.title = "New Title"
b.body = "new content"
b.save()

แต่ถ้าอยากใช้ได้เหมือน list จริงๆ ก็ทำได้โดยบังคับให้ Django มัน evaluate QuerySet ให้กลายเป็น list ด้วยฟังก์ชัน list
เช่น

blogs = list(blogs)

มีข้อเสียคือ
* มี large memory overhead
* blogs.filter ต่อไม่ได้… เพราะโดน evaluate ไปแล้ว

รวมความมึนของผม ใน python

rules = (
    ( 'foo', 1),
    ( 'spam', 2),
    ( 'bar', 3),
"""
    ( 'egg', 4),
    ( 'python', 5),
"""
)

for name, val in rules:
    print name, val

โค้ดข้างบนพอรันแล้วจะเจอ ValueError (too many values to unpack)
ตอนมองแว๊บแรก ก็คิดว่าโค้ดน่าจะโอเค แค่ comment egg กับ python ออกไปแบบหลายบรรทัด… แต่จริงๆ แล้วมันไม่ใช่

เราสร้าง list ที่มี (‘foo’, 1), (‘spam’, 2), (‘bar’, 3) และ “”” (‘egg’, 4),…””” <– ก้อนนี้เป็น string แบบหลายบรรทัด ไม่ใช่ comment!!

ปล. ตอนนี้มีอยู่ 1 bug, เดี๋ยวมีอะไรเพิ่ม จะเอามาแปะอีกครับ

Closure ใน Python

แฮ่ วันนี้ลองเล่น closure ใน python เต็มที่เลยครับ

funcs = []
i=0
def funcfactory():
    global i
    i += 1
    j = i
    def callback():
        print j
    funcs.append(callback)

funcfactory()    # ได้ callback ที่มี j = 1
funcfactory()    # ได้ callback ที่มี j = 2
funcfactory()    # ได้ callback ที่มี j = 3
print funcs
for f in funcs:
    f() 

list funcs จะเก็บ function ‘callback’ ที่มีโค้ดแบบเดียวกัน (print j) แต่มี context ต่างกัน (j = 1, 2, 3)

closure = (function, environment ของฟังก์ชั่น)

Edit: ข้อจำกัดสำคัญของ Closure ใน python คือ “อ่านได้อย่างเดียว” วิธีแก้ปัญหาทำได้โดยส่ง reference แทนตามตัวอย่างใน [ref]

ใน Python 3 มีคีย์เวิร์ด nonlocal ไว้ใช้อ้างถึงตัวแปรที่อยู่ใน Scope ก่อนหน้าได้