ครั้งที่แล้วเราได้โปรแกรมที่ใช้งานจริงไปเรียบร้อยแล้ว แต่ในแง่ของตัวโปรแกรมเอง ยังไม่น่าพอใจสักเท่าไหร่ แถมตัวโปรแกรมเองยาวตั้ง 25 บรรทัด ซึ่งงานในลักษณะนี้ ภาษา python ไม่น่าจะต้องเขียนยาวขนาดนั้น เสียยี่ห้อ python หมด
ครั้งนี้ เราจะมาเรียนรู้กันว่า ถ้าเขียนโปรแกรมทำ reverse complement แบบ python ควรจะเป็นอย่างไร
Reverse_sequence function revisit
จุดแรก เรามาดูกันที่ฟังก์ชัน reverse_sequence กันก่อนนะครับ ซึ่งครั้งที่แล้วเราเขียนเอาไว้ 5 บรรทัดดังนี้
def reverse_sequence(dna):
____result = ""
____for base in dna:
________result = base+result
____return result
จุดประสงค์คือคืนค่า sequence ที่ reverse แล้ว โดยเราจะวนลูปเองทีละเบส แต่ครั้งนี้เราจะเรียกใชัฟังก์ชันที่ python เตรียมเอาไว้ให้เราอยู่แล้ว ชื่อว่า reverse ที่ออกแบบมาใช้กับตัวแปรประเภท list ดังตัวอย่าง
ผมลองสาธิตตัวอย่างคำสั่งนี้ด้วยการพิมพ์คำสั่งที่ python command line ถ้าหากสนใจจะลองเปิดโปรแกรม python command line แล้วพิมพ์ตามได้ครับ บรรทัดที่พิมพ์คือบรรทัดที่ขึ้นต้นด้วย >>> ส่วนบรรทัดที่ไม่มีสัญลักษณ์ >>> จะเป็นสิ่งที่ python ตอบเรากลับมา
>>> a = [1,2,3,4]
>>> a.reverse()
>>> a
[4, 3, 2, 1]
จะเห็นว่าคำสั่ง reverse จะ reverse ข้อมูลใน list ให้เรา โดยที่เราไม่ต้องเขียนลูปเอง แต่ถ้าเราใช้คำสั่ง
>>> a = "atgc"
>>> a.reverse()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'str' object has no attribute 'reverse'
โปรแกรม python จะโวยวายออกมาว่า เราไม่สามารถใช้คำสั่ง reverse กับตัวแปร string ได้ ทั้งที่ในความจริงแล้ว ตัวแปร string ก็คือ list ของตัวอักษรนั่นเอง เราจะแก้ปัญหานี้ด้วยการบอก python ว่าเราต้องการให้มอง string เป็น list
>>> a = "atgc"
>>> list(a)
['a', 't', 'g', 'c']
จะเห็นว่า ถ้าเราส่งตัวแปร string ไปให้ฟังก์ชัน list เราก็จะได้ list ของเบสต่างๆ ออกมา แต่เราไม่ได้เขียนฟังก์ชัน list ขึ้นมา ทำไมเราถึงเรียกใช้ได้ คำตอบคือ ฟังก์ชัน list เป็น built-in function ที่ python เตรียมเอาไว้ให้เราเรียกใช้ได้เลย เราไม่ต้องเขียนเองเพิ่ม แต่งานของเรายังไม่จบครับ เพราะว่าการทำงานของฟังก์ชันเดิม โปรแกรมจะคืนค่าเป็น string ที่ reverse แล้ว แต่ถ้าเราใช้คำสั่ง reverse เราจะยังได้ข้อมูลเป็น list อยู่ โจทย์ถัดไปที่เราจะต้องหาคำตอบให้ได้คือ ทำอย่างไรจึงสร้าง string จาก list ได้
วิธีตรงไปตรงก็มา ก็คือใช้ลูป
dna_string = ""
for base in my_list:
____dna_string = dna_string+base
พอลูปครบถ้วน เราก็จะได้ตัวแปร string ที่มาจาก list
แต่ถ้าเราทำแบบนี้ โปรแกรมครั้งนี้ของเราก็ไม่ได้สั้นกว่าครั้งที่แล้วเลย ยาวกว่า และออกจะน่าเกลียดกว่าด้วย
python มีวิธีที่ดีกว่านี้มาให้เราแล้ว
คำสั่ง Join เพื่อเชื่อมแต่ละ items ใน list
เราจะใช้คำสั่งอีกตัวหนึ่งคือ join เพื่อเชื่อมโยงแต่ละข้อมูลใน list ดังตัวอย่าง
>>> a = ["a","b","c","d"]
>>> '*'.join(a)
'a*b*c*d'
ถ้าเรามีตัวอักษรอยู่ใน list แล้วเราใช้คำสั่ง join ดังตัวอย่าง python จะนำเอาตัวอักษรที่อยู่ก่อน join ไปแทรกอยู่ระหว่างข้อมูลแต่ละ items ใน list ซึ่งเราสามารถในไปใช้งานได้หลายอย่างเช่น
>>> a = ["apple", "banana", "cow"]
>>> 'and'.join(a)
'appleandbananaandcow'
ซึ่งอ่านไม่รู้เรื่อง เราแก้ไขด้วยการเพิ่ม space (เว้นวรรค) หน้าและหลัง and
>>> ' and '.join(a)
'apple and banana and cow'
ก็จะได้ string ที่อ่านออก เราสามารถทำ comma-delimited text ง่ายๆ ด้วยคำสั่ง join เช่นกัน
>>> ', '.join(a)
'apple, banana, cow'
ให้สังเกตการใช้คำสั่ง join ให้ดีนะครับ จะเริ่มต้นด้วย string ที่เราต้องการใช้เชื่อมแต่ละ items ใน list แล้วตามด้วยจุดและคำสั่ง join และส่งค่า string เป็นพารามิเตอร์ (parameter) ไปให้กลับ function
ถ้าหากเราต้องการเชื่อม items ใน list เฉยๆ เราก็สามารถทำได้ด้วยการใช้ empty string ในการ join ดังตัวอย่าง
>>> ''.join(a)
'applebananacow'
ซึ่งก็จะเป็นสิ่งที่เราต้องการพอดี
ถึงตรงนี้ เราก็มีทุกอย่างพร้อมแล้วสำหรับการเรียกใช้ฟังก์ชันที่มากับ python แทนที่จะต้องมาเขียนเอง (ในเมื่อ python มีมาให้แล้ว เราจะเขียนเองให้ยุ่งยากทำไม จริงไหมครับ)
def reverse_sequence(dna):
____result = list(dna)
____result.reverse()
____result = ''.join(result)
____return result
Chain of functions
จะเห็นว่าฟังก์ชันใหม่ของเรา จะเรียกใช้ฟังก์ชันของ python ที่มีอยู่แล้วทั้งหมด คราวนี้เรามาเรียนรู้ concept อีกอย่างหนึ่ง ซึ่งจะทำให้เราเขียนโปรแกรมให้กะทัดรัดขึ้น แต่อาจจะทำให้มือใหม่งุนงงได้พอสมควร นั่นคือ concept เรื่อง chain of functions ครับ
เรามาดูตัวอย่างง่ายๆ กันก่อน สมมติว่าเรามี 2 functions ดังนี้ครับ
def function_addone(x):
____return x+1
def function_timetwo(x):
____return x*2
คิดว่าคงเข้าใจได้ไม่ยากนะครับ function_addone จะคืนค่าที่ส่งไปบวกหนึ่ง ส่วน function_timetwo จะคืนค่าที่ส่งไปคูณสอง
ลองมาดูตัวอย่างการใช้งานนะครับ
>>> function_addone(2)
3
>>> function_timetwo(2)
4
คราวนี้เราเอาผลลัพธ์มาใส่ไว้ในตัวแปร x ก่อน แล้วเอาผลลัพธ์ที่ได้ ไปใช้ใน function_timetwo ต่อ แล้วพิมพ์ผลลัพธ์ออกมาดู
>>> x = function_addone(3)
>>> x = function_timetwo(x)
>>> print x
8
จะเห็นว่าค่าของ x เป็นค่าที่ได้จากการคืนค่าของ function_addone แล้วค่านั้นก็จะถูกส่งต่อไปที่ function_timetwo ทันที เท่ากับว่าเรียกใช้ฟังก์ชันเพื่อให้ได้คำตอบของสมการ (3+1)*2 = 8 เราจึงอาจจะเขียนได้ดังด้านล่าง
>>> x = function_timetwo(function_addone(3))
>>> print x
8
ซึ่งเราจะได้คำตอบเดียวกัน ถ้าหากเราอ่านโปรแกรม python คุ้นเคยแล้ว การเขียนแบบหลังจะทำให้อ่านได้สะดวกและง่ายกว่า เพราะว่าเราจะทราบได้ทันทีว่า ตอนแรกเรียกใช้ function_addone แล้วผลที่ได้ส่งต่อไปให้ function_ timetwo เลย (ให้อ่านจากตัวที่อยู่ในวงเล็บในสุด แล้วค่อยๆ ขยายออกมาด้านนอก)
เพราะฉะนั้น ถ้าเมื่อไหร่เราเห็นการเขียนโปรแกรมในลักษณะที่ส่งค่าต่อไปเรื่อยๆ เราสามารถย่อโปรแกรมได้ด้วยการใช้ chain of functions
ย้อนกลับมาที่โปรแกรมของเรา จะเห็นว่าเราน่าจะใช้ chain of functions ได้ ยกเว้นคำสั่ง reverse() ซึ่งไม่ได้คืนค่าใดกลับมา แต่คำสั่งนี้ไป reverse ข้อมูลของ list ทำให้เรายังไม่สามารถใช้ chain of functions ได้ เราสามารถแก้ไขได้ด้วยการใช้คำสั่งอีกหนึ่งคำสั่งที่คล้ายกันคือ
reversed() ให้สังเกตตัว d ที่อยู่ข้างหลัง ที่จะแตกต่างจากคำสั่ง reverse ที่เราใช้ก่อนหน้านี้
คำสั่ง reversed() จะคืนค่ากลับมา เราจึงเขียน function ได้ใหม่ ดังนี้
def reverse_sequence(dna):
____result = list(dna)
____result = reversed(result)
____result = ''.join(result)
____return result
ให้สังเกตการเรียกใช้ที่เปลี่ยนไป จะเห็นว่าคราวนี้ค่าสั่ง reversed จะคืนค่ากลับมาให้ เราจึงสามารถนำค่าที่ได้ใส่กลับลงไปที่ตัวแปร result ต่างจากที่เราใช้คำสั่ง reverse() ก่อนหน้านี้
เมื่อเราได้รูปแบบดังนี้แล้ว เราก็พร้อมจะใช้ chain of functions แล้วครับ โดยเราจะค่อยๆ ย้อนจากล่างขึ้นบน
def reverse_sequence(dna):
____result = list(dna)
____result = ''.join(reversed(result))
____return result
ผมเอา reversed(result) มาแทนที่ result ในบรรทัดที่ใช้คำสั่ง join เพราะเมื่อเราได้ผลลัพธ์จาก reversed(result) เราก็ส่งต่อค่านี้ไปที่ join ทันที จึงไม่จำเป็นต้องมาพักค่าเอาไว้ที่ตัวแปร result เราจะรวบคำสั่งต่อ ได้เป็น
def reverse_sequence(dna):
____result = ''.join(reversed(list(dna)))
____return result
ผมแทนที่ result ในบรรทัด join ด้วย list(dna) เพราะว่าตัวแปร result ที่จะมาที่บรรทัดนี้ก็คือผลลัพธ์ที่ได้จาก list(dna) จึงไม่จำเป็นต้องพักค่าเช่นกัน
สุดท้ายเราไม่จำเป็นต้องเก็บค่าในตัวแปร result อีกต่อไป เราสามารถ return ค่าได้เลย จึงได้ฟังก์ชันสุดท้ายเป็น
def reverse_sequence(dna):
____return ''.join(reversed(list(dna)))
ครั้งนี้เราจะจบที่ฟังก์ชันนี้ก่อนนะครับ เราได้เรียนคำสั่งเพิ่มเติมหลายคำสั่ง สามารถเปลี่ยน string ให้เป็น list และเปลี่ยน list ให้เป็น string ได้ และได้เรียนรู้ concept เรื่อง chain of functions ได้แล้ว ฟังก์ชันใหม่ที่เราได้ มีแค่ 1 บรรทัดเท่านั้น ครั้งหน้า เราจะมาดูกันว่า function complement ที่เราได้เขียนไปนั้น จะปรับปรุงให้ดีขึ้นได้อย่างไร