Table of Contents

8. สายอักขระ

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

8.1 สายอักขระเป็นลำดับ

สายอักขระ คือ ลำดับของอักขระ เราสามารถเข้าถึงอักขระทีละตัวได้โดยใช้ตัวดำเนินการวงเล็บสี่เหลี่ยม (bracket):

>>> fruit = 'banana'
>>> letter = fruit[1]

คำสั่งที่สองเลือกอักขระเบอร์ 1 จาก fruit และกำหนดให้เป็นค่าของ letter

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

แต่เราอาจจะไม่ได้ค่าที่เราคาดไว้:

>>> letter
'a'

สำหรับคนส่วนใหญ่แล้ว อักษรตัวแรกของ 'banana' คือ b ไม่ใช่ a แต่สำหรับนักวิทยาการคอมพิวเตอร์แล้ว ดัชนีคือค่าชดเชย (offset) จากตอนต้นของสายอักขระ ค่าชดเชยของอักษรตัวแรก คือ ศูนย์

>>> letter = fruit[0]
>>> letter
'b'

ดังนั้น b คืออักษรตัวที่ 0 ของคำว่า 'banana', a คืออักษรตัวที่ 1, และ n คืออักษรตัวที่ 2

สำหรับค่าของดัชนี เราสามารถใช้นิพจน์ที่มีตัวแปรและตัวดำเนินการได้:

>>> i = 1
>>> fruit[i]
'a'
>>> fruit[i+1]
'n'

แต่ค่าของดัชนีจะต้องเป็นจำนวนเต็ม ไม่เช่นนั้น เราจะได้:

>>> letter = fruit[1.5]
TypeError: string indices must be integers

8.2 ฟังก์ชัน len

len เป็นฟังก์ชันที่มีอยู่ในตัวที่คืนค่าเป็นจำนวนของอักขระในสายอักขระ:

>>> fruit = 'banana'
>>> len(fruit)
6

ในการเข้าถึงอักษรตัวสุดท้ายของสายอักขระ เราอาจจะอยากลองอะไรแบบนี้:

>>> length = len(fruit)
>>> last = fruit[length]
IndexError: string index out of range

เหตุผลที่เกิด IndexError หรือ ข้อผิดพลาดกับดัชนี คือว่า ใน 'banana' ไม่มีอักษรตัวที่ดัชนีเท่ากับ 6 เนื่องจากเราเริ่มนับจาก 0 อักษรทั้ง 6 ตัวในคำมีหมายเลขเป็น 0 ถึง 5 ในการที่จะเอาค่าของอักขระตัวสุดท้าย เราจะต้องลบ 1 ออกจากความยาว (length) ของสายอักขระ

>>> last = fruit[length-1]
>>> last
'a'

หรือ เราสามารถใช้ดัชนีค่าติดลบได้ ซึ่งจะนับกลับหลังจากจุดสิ้นสุดของสายอักขระ นิพจน์ fruit[-1] ให้ค่าเป็นอักษรตัวสุดท้าย fruit[-2] ให้ค่าเป็นอักษรตัวรองสุดท้าย เป็นแบบนี้ไปเรื่อยๆ

8.3 การแวะผ่านด้วยลูป for

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

index = 0
while index < len(fruit):
    letter = fruit[index]
    print(letter)
    index = index + 1

ลูปนี้ได้แวะผ่านสายอักขระและแสดงอักษรแต่ละตัวบนบรรทัดของใครของมัน เงื่อนไขของลูป คือ index < len(fruit) ดังนั้น เมื่อ index มีค่าเท่ากับความยาวของสายอักขระ เงื่อนไขจะเป็นเท็จ และส่วนตัวของลูปจะไม่ทำงาน อักขระตัวสุดท้ายที่ถูกเข้าถึงคือตัวที่ดัชนีเป็น len(fruit)-1 ซึ่งเป็นอักขระตัวสุดท้ายในสายอักขระนี้

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

อีกทางหนึ่งที่จะเขียนการแวะผ่าน คือ การใช้ลูป for:

for letter in fruit:
    print(letter)

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

ตัวอย่างต่อไปนี้แสดงให้เห็นว่าการเชื่อมต่อสายอักขระนั้นทำอย่างไร (string concatenation, string addition) และการใช้ลูป for ในการสร้างซีรีส์เอบีซีดาเรียน (abecedarian series) (นั่นคือ ซีรีส์ของอักษรตามลำดับอักษร) ในหนังสือของโรเบิร์ต แมคคลอสกี้ (Robert McCloskey) ที่ชื่อว่า Make Way for Ducklings ชื่อของลูกเป็ดคือ Jack, Kack, Lack, Mack, Nack, Ouack, Pack, และ Quack ลูปต่อไปนี้แสดงชื่อดังกล่าวตามลำดับ:

prefixes = 'JKLMNOPQ'
suffix = 'ack'
 
for letter in prefixes:
    print(letter + suffix)

เอ้าต์พุต คือ:

Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack

แน่นอนว่ามันไม่ค่อยถูก เพราะ “Ouack” และ “Quack” นั้นสะกดผิด เพื่อเป็นการหัดทำ ให้แก้โปรแกรมนี้เพื่อแก้ไขข้อผิดพลาด

8.4 ช่วงของสายอักขระ

ท่อนของสายอักขระเรียกว่า ช่วง หรือ สไลซ์ (slice) การเลือกช่วงเหมือนกับการเลือกอักขระ:

>>> s = 'Monty Python'
>>> s[0:5]
'Monty'
>>> s[6:12]
'Python'

ตัวดำเนินการ [n:m] คืนค่ามาเป็น ส่วนของสายอักขระจากอักขระตัวที่ n ถึง m รวมตัวแรก แต่ไม่รวมตัวหลัง (รวม n ไม่รวม m) พฤติกรรมแบบนี้ไม่ตรงตามสัญชาตญาณ แต่มันอาจจะช่วยถ้าเราลองจินตนาการว่าดัชนีมันชี้อยู่ตรง ระหว่าง อักขระแต่ละตัว เหมือนในรูปที่ 8.1

 ดัชนีของช่วง
รูปที่ 8.1 ดัชนีของช่วงสายอักขระ

ถ้าเราไม่ใส่ดัชนีตัวแรก (ก่อนเครื่องหมายทวิภาค หรือ โคลอน) ช่วงจะเริ่มที่จุดเริ่มต้นของสายอักขระ ถ้าเราไม่ใส่ดัชนีตัวที่สอง ช่วงนี้จะยาวไปถึงตอนท้ายของสายอักขระ:

>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'

ถ้าดัชนีตัวแรกนั้นมากกว่าหรือเท่ากับตัวที่สอง ผลลัพธ์จะเป็น สายอักขระว่าง (empty string) แทนด้วยเครื่องหมายอัญประกาศสองตัว:

>>> fruit = 'banana'
>>> fruit[3:3]
''

สายอักขระว่างไม่มีอักขระอยู่เลย และมีความยาวเป็น 0 แต่นอกจากนี้ มันเป็นเหมือนกับสายอักขระอื่นๆ

มาทำตัวอย่างนี้ต่อ เราคิดว่า fruit[:] หมายความว่าอะไร? ลองดูสิ

8.5 สายอักขระเปลี่ยนแปลงไม่ได้

มันน่าลองที่จะใช้ตัวดำเนินการ [] ในทางซ้ายของคำสั่ง โดยตั้งใจให้เปลี่ยนค่าอักขระในสายอักขระ เช่น:

>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: 'str' object does not support item assignment

“object” ในกรณีนี้คือสายอักขระ และ “item” คืออักขระที่เราพยายามจะเปลี่ยนมัน สำหรับตอนนี้ วัตถุ (object) ก็เหมือนกับค่า แต่เราจะเกลานิยามของคำนี้ทีหลัง (หัวข้อที่ 10.10)

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

>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> new_greeting
'Jello, world!'

ตัวอย่างนี้ทำการเชื่อมอักษรตัวแรกตัวใหม่เข้ากับสไลซ์ของ greeting มันไม่มีผลกับ สายอักขระตัวเดิม

8.6 การค้นหา

ฟังก์ชันต่อไปนี้ทำอะไร?

def find(word, letter):
    index = 0
    while index < len(word):
        if word[index] == letter:
            return index
        index = index + 1
    return -1

ในมุมหนึ่ง ฟังก์ชัน find เป็นส่วนกลับของตัวดำเนินการ [] แทนที่จะรับดัชนีแล้วแยกส่วนอักขระออกมา มันกลับรับอักขระเข้ามา แล้วหาดัชนีที่อักขระตัวนั้นอยู่ ถ้าหาอักขระตัวนั้นไม่พบ ฟังก์ชันคืนค่าเป็น -1

นี่คือตัวอย่างแรกที่เราเจอคำสั่ง return ที่อยู่ในลูป ถ้า word[index] == letter ฟังก์ชันจะออกจากลูปและคืนค่ากลับไปทันที

ถ้าอักขระนั้นไม่มีในสายอักขระ โปรแกรมจะออกจะลูปแบบปกติ และคืนค่า -1 กลับไป

รูปแบบของการคำนวณแบบนี้—การแวะผ่านข้อมูลตามลำดับ และคืนค่าเมื่อเราพบสิ่งที่เราหาอยู่— เรียกว่า การค้นหา (search)

เพื่อเป็นการฝึกทำ ให้แก้ฟังก์ชัน find ให้มีพารามิเตอร์ตัวที่สาม คือ ดัชนีใน word ตรงที่ให้เริ่มค้นหาค่า

8.7 การทำลูปและการนับ

โปรแกรมต่อไปนี้นับว่าอักษร a ปรากฏในสายอักขระกี่ครั้ง:

word = 'banana'
count = 0
for letter in word:
    if letter == 'a':
        count = count + 1
print(count)

โปรแกรมนี้สาธิตอีกรูปแบบหนึ่งของการคำนวณที่เรียกว่า ตัวนับ (counter) ตัวแปร count มีค่าเริ่มต้นเป็น 0 และจากนั้นเพิ่มทีละหนึ่งในแต่ละครั้งที่เจอ a เมื่อออกจากลูป count ก็จะมีค่าผลลัพธ์—จำนวนทั้งหมดของ a

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

จากนั้นให้เขียนฟังก์ชันนี้ใหม่ โดยแทนที่จะใช้วิธีแวะผ่านสายอักขระ (string traversing) มันจะใช้ฟังก์ชัน find เวอร์ชันที่มีพารามิเตอร์สามตัวจากหัวข้อที่แล้ว

8.8 เมธอดของสายอักขระ

สายอักขระเตรียมเมธอดสำหรับทำงานที่มีประโยชน์ได้หลากหลาย เมธอด (method) เหมือนกับฟังก์ชัน— มันรับอาร์กิวเมนต์และคืนค่ากลับมา—แต่มีกฎวากยสัมพันธ์ที่ต่างออกไป เช่น เมธอด upper รับค่าสายอักขระเข้ามาและคืนค่าเป็นสายอักขระตัวใหม่ที่เป็นอักษรตัวใหญ่ทั้งหมด

แทนที่จะใช้กฎวากยสัมพันธ์ของฟังก์ชัน คือ upper(word) มันจะใช้กฎวากยสัมพันธ์ของเมธอด คือ word.upper()

>>> word = 'banana'
>>> new_word = word.upper()
>>> new_word
'BANANA'

รูปแบบของสัญกรจุด (dot notation) นี้ ระบุชื่อของเมธอด, upper, และชื่อของสายอักขระ ที่จะใช้เมธอดนั้น, word วงเล็บว่างระบุว่าเมธอดนี้ไม่รับอาร์กิวเมนต์เข้ามา

การเรียกเมธอดนั้นเรียกว่า การร้องขอ (invocation); ในกรณีนี้ เราพูดได้ว่า เรา ร้องขอให้ทำการ upper กับ word

กลายเป็นว่า มีเมธอดของสายอักขระที่ชื่อว่า find ที่เหมือนกันอย่างประหลาดกับฟังก์ชันที่เราได้เขียนไป:

>>> word = 'banana'
>>> index = word.find('a')
>>> index
1

ในตัวอย่างนี้ เราร้องขอให้ทำการ find กับ word และผ่านอักษรที่เราต้องการหาเข้า ไปเป็นพารามิเตอร์

ที่จริงแล้ว เมธอด find มันครอบคลุมมากกว่าฟังก์ชันของเรา; มันสามารถหา สายอักขระย่อย (substring) ไม่ใช่แค่อักขระ (character):

>>> word.find('na')
2

โดยปริยาย find จะเริ่มที่จุดเริ่มต้นของสายอักขระ แต่มันสามารถรับอาร์กิวเมนต์ตัวที่สอง คือ ดัชนีที่มันควรจะเริ่มต้นค้นหา:

>>> word.find('na', 3)
4

นี่คือตัวอย่างของ อาร์กิวเมนต์ทางเลือก (optional argument); find สามารถรับอาร์กิวเมนต์ตัวที่สามได้ด้วย คือ ดัชนีที่มันควรจะหยุดหา:

>>> name = 'bob'
>>> name.find('b', 1, 2)
-1

การค้นหานี้ไม่สำเร็จ เพราะ b ไม่ปรากฏอยู่ในช่วงดัชนี 1 ถึง 2 โดยไม่รวม 2 การค้นหาไปจนถึง, แต่ไม่รวม, ดัชนีตัวที่สองนี้ ทำให้ find ทำงานสอดคล้องกันกับตัวดำเนินการเลือกช่วงสายอักขระ (slice)

8.9 ตัวดำเนินการ in

คำว่า in เป็นตัวดำเนินการบูลีน ซึ่งรับสายอักขระสองตัว และคืนค่าเป็น True ถ้า ตัวแรกเป็นสายอักขระย่อย (substring) ของตัวที่สอง:

>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False

ตัวอย่างเช่น ฟังก์ชันต่อไปนี้พิมพ์อักษรทุกตัวจาก word1 ที่อยู่ใน word2:

def in_both(word1, word2):
    for letter in word1:
        if letter in word2:
            print(letter)

ถ้าเราตั้งชื่อตัวแปรดีๆ บางครั้งไพธอนจะอ่านเหมือนเป็นภาษาอังกฤษปกติเลย เราอาจจะอ่าน ลูปนี้ว่า “for (each) letter in (the first) word, if (the) letter (appears) in (the second) word, print (the) letter.” แปลว่า สำหรับ letter (อักษรแต่ละตัว) ใน word (คำแรก), ถ้า letter (อักษรตัวนั้น) (ปรากฏอยู่) ใน word (อักษรตัวที่สอง) ให้พิมพ์ letter (อักษรตัวนั้น) ออกมา

นี่คือสิ่งที่เราจะได้ถ้าเราเปรียบเทียบคำว่า apples กับ oranges:

>>> in_both('apples', 'oranges')
a
e
s

8.10 การเปรียบเทียบสายอักขระ

ตัวดำเนินการเชิงสัมพันธ์ทำงานได้กับสายอักขระ เพื่อที่จะหาว่าสายอักขระสองตัวเท่ากันหรือเปล่า:

if word == 'banana':
    print('All right, bananas.')

การดำเนินการเชิงสัมพันธ์แบบอื่นเป็นประโยชน์สำหรับเรียงคำตามลำดับตัวอักษร:

if word < 'banana':
    print('Your word, ' + word + ', comes before banana.')
elif word > 'banana':
    print('Your word, ' + word + ', comes after banana.')
else:
    print('All right, bananas.')

ไพธอนไม่จัดการอักษรตัวใหญ่และตัวเล็กเหมือนกับที่มนุษย์ทำ อักษรตัวใหญ่ทั้งหมดจะอยู่ก่อนอักษร ตัวเล็กทั้งหมด ดังนั้น:

Your word, Pineapple, comes before banana.

วิธีที่นิยมใช้กันเพื่อที่จะแก้ปัญหานี้ คือการแปลงสายอักขระให้เป็นรูปแบบมาตรฐาน, เช่น แปลง ให้เป็นตัวเล็กทั้งหมด, ก่อนที่จะเปรียบเทียบ ให้จำอันนี้เอาไว้ในกรณีที่เราจะต้องป้องกัน ตัวเองจากคนที่ติดอาวุธเป็นสับปะรด (Pineapple)

8.11 การดีบัก

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

def is_reverse(word1, word2):
    if len(word1) != len(word2):
        return False
 
    i = 0
    j = len(word2)
 
    while j > 0:
        if word1[i] != word2[j]:
            return False
        i = i+1
        j = j-1
 
    return True

คำสั่ง if อันแรกตรวจสอบว่าคำที่รับเข้ามามีความยาวเท่ากันหรือไม่ ถ้าไม่ เรา สามารถคืนค่า False ได้เลย ไม่เช่นนั้น ในการทำงานที่เหลือของฟังก์ชันเราสามารถ สมมติว่าคำสองคำมีความยาวเท่ากัน นี่คือตัวอย่างของรูปแบบผู้พิทักษ์ (guardian pattern) ที่กล่าวถึงในหัวข้อที่ 6.8

ตัวแปร i และ j คือดัชนี: i แวะผ่าน word1 จากหน้าไปหลัง ในขณะที่ j แวะผ่าน word2 จากหลังมาหน้า ถ้าเราเจอ อักษรสองตัวที่ไม่เหมือนกัน เราสามารถคืนค่า False กลับไปได้ทันที ถ้าเรา ทำลูปจนครบรอบแล้วและทุกอักษรเหมือนกัน เราจะคืนค่าเป็น True

ถ้าเราทดสอบฟังก์ชันนี้ด้วยคำว่า “pots” และ “stop” เราคาดว่าจะได้ค่าที่คืนกลับไปเป็น True แต่เรากลับได้ข้อผิดพลาดทางดัชนี (IndexError):

>>> is_reverse('pots', 'stop')
...
  File "reverse.py", line 15, in is_reverse
    if word1[i] != word2[j]:
IndexError: string index out of range

สำหรับการดีบักข้อผิดพลาดประเภทนี้ สิ่งที่ผมทำอย่างแรก คือ พิมพ์ค่าของดัชนี ในบรรทัดติดกันที่อยู่ก่อนบรรทัดที่เกิดข้อผิดพลาด

    while j > 0:
        print(i, j)        # print here
 
        if word1[i] != word2[j]:
            return False
        i = i+1
        j = j-1

ตอนนี้ เมื่อผมรันโปรแกรมอีกครั้ง ผมได้ข้อมูลเพิ่มเติม:

>>> is_reverse('pots', 'stop')
0 4
...
IndexError: string index out of range

ครั้งแรกที่เข้าไปในลูป ค่าของ j เป็น 4 ซึ่งมันเกินช่วง (out of range) สำหรับสายอักขระ 'pots' ดัชนีของอักขระตัวสุดท้ายคือ 3 ดังนั้น ค่าเริ่มต้นของ j ควรจะเป็น len(word2)-1

ถ้าผมแก้ไขข้อผิดพลาดนั้นแล้วรันโปรแกรมอีกที ผมก็จะได้:

>>> is_reverse('pots', 'stop')
0 3
1 2
2 1
True

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

 แผนภาพสถานะ
รูปที่ 8.2 แผนภาพสถานะ

ผมขออนุญาตจัดเรียงตัวแปรในเฟรมใหม่ และเพิ่มเส้นประเพื่อจะแสดงว่าค่าของ i และ j ระบุอักขระใน word1 และ word2

เริ่มด้วยแผนภาพอันนี้ รันโปรแกรมบนกระดาษ แล้วเปลี่ยนค่าของ i และ j ในแต่ละรอบของการวนซ้ำ ให้หาข้อผิดพลาดข้อที่สองในฟังก์ชันและแก้มันซะ [isreverse]

8.12 อภิธานศัพท์

8.13 แบบฝึกหัด

แบบฝึกหัด 1
อ่านเอกสารของเมธอดของสายอักขระที่ http://docs.python.org/3/library/stdtypes.html#string-methods เราอาจจะอยากทดลองกับบางเมธอดเพื่อให้แน่ใจว่าเราเข้าใจว่ามันทำงานยังไง เมธอด strip และ replace เป็นเมธอดที่มีประโยชน์เป็นพิเศษ

เอกสารข้างต้นใช้กฎวากสัมพันธ์ที่อาจจะสับสนหน่อย เช่น ใน find(sub[, start[, end]]) วงเล็บทำการระบุอาร์กิวเมนต์ทางเลือก (optional argument) ดังนั้น sub เป็นสิ่งจำเป็น แต่ start เป็นทางเลือก และถ้าเราใส่ start เข้าไปแล้ว end ก็เป็นทางเลือก

แบบฝึกหัด 2
มีเมธอดของสายอักขระที่ชื่อว่า count ที่ทำงานเหมือนกับฟังก์ชันในหัวข้อที่ 8.7 ให้อ่านเอกสารของเมธอดนี้และเขียนการร้องขอ (invocation) ที่นับจำนวนของ a ในคำว่า 'banana'

แบบฝึกหัด 3
ช่วงของสายอักขระสามารถรับค่าดัชนีตัวที่สามซึ่งระบุ “ขนาดของขั้น (step size)” ได้; นั่นคือ จำนวนของที่ว่างระหว่างอักขระที่ติดกัน ขนาดของขั้นเท่ากับ 2 หมายความว่าอักขระเว้นอักขระ; ส่วน 3 หมายความว่าเอาอักขระมาทุกๆ 3 ตัว, และอื่นๆ

>>> fruit = 'banana'
>>> fruit[0:5:2]
'bnn'

ขนาดของขั้นเท่ากับ -1 จะทำงานกับคำนั้นแบบกลับหลัง ดังนั้น การตัด [::-1] ทำให้เกิดสายอักขระแบบกลับหลัง

ให้ใช้ลักษณะเฉพาะอันนี้เขียนฟังก์ชัน is_palindrome จากแบบฝึกหัดที่ 3 ให้เป็นเวอร์ชันที่มี 1 บรรทัด

แบบฝึกหัด 4
ฟังก์ชันต่อไปนี้ทั้งหมด ตั้งใจ ที่จะตรวจสอบว่าสายอักขระมีอักษรตัวเล็กหรือไม่ แต่ อย่างน้อยบางฟังก์ชันก็ผิด ในแต่ละฟังก์ชัน ให้บรรยายว่าฟังก์ชันนั้นทำงานอะไรกันแน่ (สมมติว่าพารามิเตอร์ของแต่ละฟังก์ชันเป็นสายอักขระ)

def any_lowercase1(s):
    for c in s:
        if c.islower():
            return True
        else:
            return False
 
def any_lowercase2(s):
    for c in s:
        if 'c'.islower():
            return 'True'
        else:
            return 'False'
 
def any_lowercase3(s):
    for c in s:
        flag = c.islower()
    return flag
 
def any_lowercase4(s):
    flag = False
    for c in s:
        flag = flag or c.islower()
    return flag
 
def any_lowercase5(s):
    for c in s:
        if not c.islower():
            return False
    return True

แบบฝึกหัด 5
รหัสซีซาร์ (Caesar cypher) เป็นรูปแบบการเข้ารหัสที่อ่อนแออย่างหนึ่ง ซึ่งเกี่ยวข้องกับการ “หมุน” อักษรแต่ละตัวไปเป็นจำนวนตำแหน่งที่ตายตัว การหมุนอักษรหมายความว่าการเลื่อน ไปในรายการพยัญชนะ แล้ววนกลับมาพยัญชนะเริ่มต้นหากจำเป็น ดังนั้น การหมุน ’A’ ไป 3 ตำแหน่งจะได้ ’D’ และการหมุน ’Z’ ไป 1 ตำแหน่งจะได้ ’A’

การหมุนคำ เราหมุนอักษรแต่ละตัวในคำในจำนวนที่เท่ากัน เช่น การหมุน “cheer” ไป 7 ตำแหน่งจะได้ “jolly” และการหมุน “melon” ไป -10 ตำแหน่งจะได้ “cubed” ในหนังสือ 2001: A Space Odyssey คอมพิวเตอร์ของยานชื่อว่า HAL ซึ่งเป็นการหมุน คำว่า IBM ไป -1 ตำแหน่ง

ตัวอย่างเช่น คำว่า “sleep” หมุนไป 9 ตำแหน่ง คือคำว่า “bunny” และ คำว่า “latex” หมุนไป 7 ตำแหน่ง คือคำว่า “shale”

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

เราอาจจะต้องการใช้ฟังก์ชันที่มีอยู่ในตัว ord ซึ่งแปลงอักขระเป็นรหัสตัวเลข และ ฟังก์ชัน chr ซึ่งแปลงรหัสตัวเลขเป็นอักขระ อักษรต่างๆ ถูกเข้ารหัสโดยเรียงตามตัวอักษร ดังนั้น เช่น:

>>> ord('c') - ord('a')
2

เพราะว่า 'c' คือ อักษรลำดับที่ 2 ของพยัญชนะ แต่ระวัง: รหัสตัวเลข สำหรับอักษรตัวใหญ่นั้นแตกต่างออกไป

(หมายเหตุผู้แปล: อักษรลำดับที่ 0 คือ 'a')

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

เฉลย: http://thinkpython2.com/code/rotate.py

https://greenteapress.com/thinkpython2/html/thinkpython2009.html