#!/usr/bin/env python # coding: utf-8 # # การจัดเรียงข้อมูล (Sorting) # # เราสามารถจัดเรียงข้อมูลตามลำดับที่เราต้องการได้ง่ายๆด้วยคำสั่ง `sorted()` ในภาษา Python # # ถ้าอ่านภาษาอังกฤษได้สามารถไปดูเพิ่มเติมที่ [https://docs.python.org/3/howto/sorting.html] นะครับ # # สมมุติว่าเรามีข้อมูลรวมกันอยู่ในลิสต์ชื่อ a ดังนี้: # In[1]: a = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3] # เราสามารถจัดเรียงข้อมูลใน a แล้วเอาผลลัพธ์ไปใส่ไว้ในลิสต์ใหม่ชื่อ b แบบนี้: # In[2]: b = sorted(a) # ข้อมูลใน b จะเรียงลำดับน้อยไปมากแบบนี้: # In[3]: b # แต่ข้อมูลใน a ยังเหมือนเดิม: # In[4]: a # เราสามารถเรียงจากมากไปน้อยด้วยใส่ `reverse = True` เข้าไปใน sorted ด้วย เช่นลิสต์ c ข้างล่างจะมีข้อมูลใน a เรียงลำดับจากมากไปน้อย: # In[5]: c = sorted(a, reverse = True) c # นอกจากตัวเลขแล้ว `sorted()` สามารถจัดเรียงข้อมูลประเภทต่างๆได้ เช่นเรียงตามตัวอักษร: # In[6]: words = ["chicken", "ant", "zebra", "boa", "cat", "panda", "dog"] sorted(words) # เวลาเราใช้คำสั่ง `sorted()` เราสามารถใส่ `key = ...` เข้าไปเพื่อบอก `sorted()` ว่าควรเปรียบเทียบว่าอะไรควรอยู่หน้าอยู่หลัง โดยสิ่งทื่อยู่หลัง `key = ` ควรเป็นฟังก์ชั่นที่ให้ค่าที่เอาไปเปรียบเทียบกันได้ ยกตัวอย่างเช่นเรามีข้อมูลนักเรียนที่อยู่ในรูป (ชื่อ, เดือนเกิด 1-12, ปีเกิดเป็นพ.ศ.) ดังนี้: # In[7]: students = [("Chunli", 2, 2548), ("Tatia", 12, 2548), ("Jung", 9, 2547), ("Pitchee", 9, 2548), ("Song", 6, 2548), ("Sound", 6, 2548), ("Tangmo", 2, 2550)] students # ถ้าจะเรียงลำดับตามเดือนเกิด เราก็ควรสร้างฟังก์ชั่นที่บอกเดือนเกิดของนักเรียนแต่ละคนได้ ในที่นี้ฟังก์ชั่นต้องเอาตัวเลขเดือนเกิดตรงกลางออกมาให้: # In[8]: def get_month(student_data): #student_data อยู่ในรูป (ชื่อ, เดือนเกิด 1-12, ปีเกิดเป็นพ.ศ.) #student_data[0] คือชื่อ #student_data[1] คือเดือนเกิด #student_data[2] คือปีเกิด return (student_data[1]) #เอาเดือนเกิดมาให้ # เราจะเรียก `sorted()` โดยใส่ `key = get_month` เพื่อจัดเรียงข้อมูลตามเดือนเกิด: # In[9]: sorted(students, key = get_month) # ถ้าจะเรียงตามเดือนเกิดจากมากไปน้อยก็สามารถใส่ `reverse = True` เข้าไปด้วย: # In[10]: sorted(students, key = get_month, reverse = True) # ถ้าจะเรียงลำดับตามความยาวของชื่อ เราก็ควรสร้างฟังก์ชั่นที่บอกความยาวของชื่อมาเพื่อเปรียบเทียบดังนี้: # In[11]: def name_length(data): #student_data อยู่ในรูป (ชื่อ, เดือนเกิด 1-12, ปีเกิดเป็นพ.ศ.) #student_data[0] คือชื่อ #student_data[1] คือเดือนเกิด #student_data[2] คือปีเกิด return(len(data[0])) #เอาความยาวชื่อมาให้ # แล้วเราก็เรียก `sorted()` โดยใส่ `key = name_length` ดังนี้: # In[12]: sorted(students, key = name_length) # เราตรวจคำตอบเราได้โดยการพิมพ์ข้อมูลนักเรียนมาพร้อมๆกับความยาวชื่อได้แบบนี้: # In[13]: for data in sorted(students, key = name_length): print(data, name_length(data)) #พิมพ์ข้อมูลนักเรียน ตามด้วยความยาวชื่อ # บางครั้งเราไม่อยากสร้างฟังก์ชั่นใหม่ๆแยกขึ้นมาต่างหากเพื่อไปใส่ `key = ...` เราสามารถใช้ฟังก์ชั่นนิรนามที่เรียกว่า `lambda` ได้ # # ฟังก์ชั่นนิรนาม `lambda` จะมีรูปแบบอย่างนี้: # # ```python # lambda ตัวแปร: ผลลัพธ์ # ``` # # เราสามารถใช้ `lambda` แทนฟังก์ชั่น `name_length` ได้แบบนี้: # ```python # lambda data: len(data[0]) # ``` # มาแทน # ```python # def name_length(data): # #student_data อยู่ในรูป (ชื่อ, เดือนเกิด 1-12, ปีเกิดเป็นพ.ศ.) # #student_data[0] คือชื่อ # #student_data[1] คือเดือนเกิด # #student_data[2] คือปีเกิด # # return(len(data[0])) #เอาความยาวชื่อมาให้ # ``` # # โดยใส่เข้าไปใน `sorted()` อย่างนี้: # In[14]: sorted(students, key = lambda data: len(data[0])) # เราสามารถจัดเรียงตามเดือนเกิดได้โดยใช้ `lambda` แบบนี้: # In[15]: sorted(students, key = lambda data: data[1]) # หรือถ้าจะจัดเรียงตามผลคูณของเดือนเกิดกับปีเกิดก็สามารถทำแบบนี้ได้: # In[16]: sorted(students, key = lambda data: data[1]*data[2] ) # ## แบบฝึกหัด # ### แบบฝึกหัดที่ 1: หาคำในภาษาอังกฤษที่มีสระครบ 5 ตัว (a, e, i, o, u) # # เราสามารถเปิดไฟล์ที่มีคำภาษาอังกฤษ แล้วตรวจดูว่าคำไหนมีสระครบทั้ง 5 ตัว เราสร้างฟังก์ชั่นไว้ตรวจเช็คดังนี้: # In[17]: def has_all_vowels(word): """ จะให้ค่า True ถ้า word มีสระครบทั้ง a, e, i, o, u จะให้ค่า False ถ้าไม่มีสระครบ """ for vowel in "aeiou": if not vowel in word.lower(): #เรามี .lower() เพื่อเปลี่ยนตัวอักษรเป็นอักษรตัวเล็กก่อนไปดูว่ามี a, e, i, o, u ไหม return(False) return(True) # In[18]: #ทดลองใช้ print(has_all_vowels("environment")) #False print(has_all_vowels("facetious")) #True print(has_all_vowels("FACETIOUS")) #True # แล้วเราก็สามารถใช้ฟังก์ชั่น `has_all_vowels()` เช็คคำในรายการคำของเราดังนี้ # In[ ]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด inputfile = open(wordlist) for word in inputfile: if has_all_vowels(word): print(word, end = "") #เราใส่ end = "" เพื่อบอก print() ว่าไม่ต้องขึ้นบรรทัดใหม่ เพราะคำทุกคำมีตัวขึ้นบรรทัดใหม่ (\n) ติดมาด้วยแล้ว inputfile.close() # ถ้าต้องการเก็บผลลัพธ์ไว้ในไฟล์ก็ใช้ `.write(...)` เขียนไฟล์ ในที่นี้เราเก็บไว้ในไฟล์ชื่อ `allvowels.txt` # In[19]: wordlist = '/usr/share/dict/words' #ไฟล์ที่มีคำภาษาอังกฤษ 1 คำต่อบรรทัด allvowels = 'allvowels.txt' #เราเก็บผลลัพธ์เราไว้ในไฟล์นี้ inputfile = open(wordlist) #เปิดไฟล์เพื่ออ่าน outputfile = open(allvowels, "w") #เปิดไฟล์เพื่อเขียน สังเกตว่ามี "w" for word in inputfile: if has_all_vowels(word): outputfile.write(word) outputfile.close() inputfile.close() # ### แบบฝึกหัดที่ 2: หาคำภาษาอังกฤษที่มีสระครบ 5 ตัวและเรียงกันตั้งแต่ a ไปถึง u # # เราสามารถแก้ปัญหาโดยเขียนฟังก์ชั่นที่ตรวจสอบว่าคำภาษาอังกฤษแต่ละคำมีสระครบห้าตัวไหม และเรียงกันไหม มีหลายวิธี วิธีหนึ่งก็อาจใช้ `find()` เพื่อหาดูตำแหน่งสระในคำ: # In[20]: #ตัวอย่างการใช้ find() หาตำแหน่งตัวอักษร word = "facetious" print(word.find("a")) #ตำแหน่งของ a คือ 1 (อย่าลืมว่าตัวแรกคือตำแหน่งที่ 0) print(word.find("e")) #ตำแหน่งของ e คือ 3 print(word.find("i")) #ตำแหน่งของ i คือ 5 print(word.find("o")) #ตำแหน่งของ o คือ 6 print(word.find("u")) #ตำแหน่งของ u คือ 7 print(word.find("x")) #ไม่มี x ในคำว่า facetious ดังนั้น find() จะส่งค่ามา = -1 # ถ้าคำที่เราสนใจมีสระครบห้าตัว และตำแหน่งของ a, e, i, o, u เพิ่มขึ้นเรื่อยๆ ก็แสดงว่าคำๆนั้นมีสระทุกตัวและอยู่ในลำดับเรียงกัน ถ้าเราเอาตำแหน่งทั้งหลายไปใส่ไว้ในลิสต์ ลิสต์นั้นเมื่อ `sorted()` แล้วจะเท่าตัวมันเอง # # เราเขียนเป็นฟังก์ชั่นตรวจสอบได้ดังนี้: # # In[21]: def has_aeiou_in_order(word): """ ตรวจว่า word มีสระ a, e, i, o, u ครบทั้งห้าตัว และ a มาก่อน e, e มาก่อน i, i มาก่อน o, และ o มาก่อน u จะให้ค่า True ถ้ามีสระครบและเรียงกัน ให้ค่า False ถ้าไม่ครบหรือไม่เรียง """ if not has_all_vowels(word): return(False) #ถ้ามีสระไม่ครบห้าตัวก็ให้ค่า False แล้วออกจากฟังก์ชั่นเลย vowel_list = [] #ลิสต์ที่จะใส่ตำแหน่งสระแต่ละตัว for c in "aeiou": vowel_list.append(word.find(c)) #ใส่ตำแหน่งเข้าไปในลิสต์ที่เก็บ #print(vowel_list) return (sorted(vowel_list) == vowel_list) #ถ้าตำแหน่งเมื่อจัดเรียงจากมากไปน้อยแล้วยังเหมือนเดิมแสดงว่าสระเรียงถูกต้องแล้ว # In[22]: #ทดสอบ has_aeiou_in_order() print(has_aeiou_in_order("hello")) #False print(has_aeiou_in_order("aliferous")) #False print(has_aeiou_in_order("facetious")) #True # เราสามารถใช้ `has_aeiou_in_order` หาคำที่มีสระครบและเรียงกันได้อย่างนี้ครับ: # In[23]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด inputfile = open(wordlist) for word in inputfile: if has_aeiou_in_order(word): print(word, end = "") #เราใส่ end = "" เพื่อบอก print() ว่าไม่ต้องขึ้นบรรทัดใหม่ เพราะคำทุกคำมีตัวขึ้นบรรทัดใหม่ (\n) ติดมาด้วยแล้ว inputfile.close() # เราพบว่าคำบางคำข้างบนมีสระซ้ำๆกันมากกว่าหนึ่งตัว ถ้าเราต้องการสระห้าตัวที่ไม่ซ้ำเท่านั้นเราก็ต้องหาวิธีตรวจสอบใหม่ วิธีแบบหนึ่งก็คือ เอาตัวอักษรสระในคำออกมาเรียงกันในลิสต์ แล้วดูว่าลิสต์นั้นต้องหน้าตาเป็น ['a','e','i','o','u'] เท่านั้น # In[24]: def has_aeiou_in_order_no_repeat(word): """ ตรวจว่า word มีสระ a, e, i, o, u ครบทั้งห้าตัว และ a มาก่อน e, e มาก่อน i, i มาก่อน o, และ o มาก่อน u และสระแต่ละตัวเกิดขึ้นครั้งเดียวในคำ ไม่ซ้ำ จะให้ค่า True ถ้ามีสระครบ ไม่ซ้ำ และเรียงกัน ให้ค่า False ถ้าไม่ครบ หรือซ้ำ หรือไม่เรียง """ if not has_all_vowels(word): return(False) #ถ้ามีสระไม่ครบห้าตัวก็ให้ค่า False แล้วออกจากฟังก์ชั่นเลย vowel_list = [] #ลิสต์ที่จะเก็บสระที่พบในคำที่จะตรวจ for c in word: #ดูแต่ละตัวอักษรในคำที่จะตรวจ if c in 'aeiou': #ถ้าอักษรนั้นเป็นสระ ให้เก็บเข้าไปในลิสต์ vowel_list.append(c) #print(vowel_list) return (vowel_list == ['a','e','i','o','u']) #ถ้าสระมี 5 ตัวและเป็น a, e , i, o, u ก็ให้ค่า True ถ้าไม่ใช่ก็ให้ค่า False # In[25]: #ทดสอบ has_aeiou_in_order_no_repeat() print(has_aeiou_in_order_no_repeat("hello")) #False print(has_aeiou_in_order_no_repeat("aliferous")) #False print(has_aeiou_in_order_no_repeat("facetious")) #True print(has_aeiou_in_order_no_repeat("valerianaceous")) #False # เราหาคำที่มีสระห้าตัว เรียงกัน และไม่ซ้ำ ด้วย `has_aeiou_in_order_no_repeat()` แบบนี้: # In[26]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด inputfile = open(wordlist) for word in inputfile: if has_aeiou_in_order_no_repeat(word): print(word, end = "") #เราใส่ end = "" เพื่อบอก print() ว่าไม่ต้องขึ้นบรรทัดใหม่ เพราะคำทุกคำมีตัวขึ้นบรรทัดใหม่ (\n) ติดมาด้วยแล้ว inputfile.close() # ### แบบฝึกหัดที่ 3: หา anagram หรือคำที่ประกอบด้วยอักษรชุดเดียวกัน # # Anagram (อนาแกรม) คือคำที่ใช้อักษรชุดเดียวกันมาสะกดให้เป็นคำต่างๆกัน เช่น bat กับ tab เป็นอนาแกรมกัน brainy กับ binary เป็นอนาแกรมกัน # # วิธีง่ายสุดที่จะจัดชุดอนาแกรมก็คือทำการจัดเรียงตัวอักษรในคำต่างๆให้เรียงเป็นลำดับ แล้วเปรียบเทียบชุดอักษรที่จัดเรียงแล้วนั้น เช่น `sorted('bat')` และ `sorted('tab')` จะได้ค่า ['a', 'b', 't'] เหมือนกัน # # เราสามารถเก็บคำที่เป็นอนาแกรมกันไว้ด้วยกันด้วยสิ่งที่เรียกว่า dictionary ซึ่งคล้ายๆลิสต์แต่สามารถอ้างอิงตำแหน่งต่างๆได้ด้วยข้อความ (string) ได้ # # เด็กๆดูเพิ่มเติมเกี่ยวกับ dictionary ได้ที่ [https://docs.python.org/3.7/tutorial/datastructures.html#dictionaries] และ [https://snakify.org/en/lessons/dictionaries_dicts/] ครับ # # วิธีหนึงของการหาอนาแกรมทำได้ดังนี้: # In[ ]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด inputfile = open(wordlist) anagrams = {} #dictionary ที่เราจะเก็บคำที่เป็นอนาแกรมกัน for word in inputfile: word = word.strip() #เราลบ \n (newline หรือตัวขึ้นบรรทัดใหม่) ที่อยู่ท้ายแต่ละคำ if len(word) < 2: #เราจะดูเฉพาะคำที่มีสองตัวอักษรขึินไปเท่านั้น continue key = "".join(sorted(word)) #สร้างข้อความอ้างอิงโดยจัดเรียงอักษรในคำเป็นลำดับด้วย sorted(word) การใช้ "".join(...) คือเอาแต่ละตัวอักษรในลิสต์มารวมกันเป็นข้อความอ้างอิงคำเดียว key = key.lower() #ทำข้อความอ้างอิงเป็นอักษรตัวเล็ก if key in anagrams: #ถ้าข้อความอ้างอิงเคยถูกใส่่ไว้ใน dictionary แล้ว ให้เพิ่มเติมคำ word ต่อเข้าไปในลิสต์ของคำที่เป็นอนาแกรมกัน anagrams[key].append(word) else: anagrams[key] = [] #ถ้าข้อความอ้างอิงไม่เคยมีใน dictionary เอาใส่่คำ word เป็นคำแรกของลิสต์ของคำที่เป็นอนาแกรมกัน anagrams[key].append(word) inputfile.close() for key, values in anagrams.items(): #วิธีไล่ว่าใน dictionary มีข้อมูลอะไรบ้าง ทำอย่างนี้ key คือข้อความอ้างอิง values คือลิสต์ของคำที่เป็นอนาแกรมกัน if len(values) > 1: #เราแสดงผลเฉพาะตั้งแต่สองคำขึ้นไปที่เป็นอนาแกรมกัน print(", ".join(values)) # ", ".join(values) คือเอาคำต่างๆที่อยู่ในลิสต์ values มาต่อๆกันโดยมีคอมม่าคั่น # ถ้าต้องการเก็บรายการคำที่เป็นอนาแกรมไว้ในไฟล์ ก็สามารถใช้ `file.write()` ได้ # In[27]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด anagramlist = 'anagrams.txt' #ชื่อไฟล์ที่จะเก็บอนาแกรมต่างๆไว้ inputfile = open(wordlist) outputfile = open(anagramlist, "w") #เปิดไฟล์เพื่อเขียนโดยการใส่ "w" ไว้ใน open() anagrams = {} #dictionary ที่เราจะเก็บคำที่เป็นอนาแกรมกัน for word in inputfile: word = word.strip() #เราลบ \n (newline หรือตัวขึ้นบรรทัดใหม่) ที่อยู่ท้ายแต่ละคำ if len(word) < 2: #เราจะดูเฉพาะคำที่มีสองตัวอักษรขึินไปเท่านั้น continue key = "".join(sorted(word)) #สร้างข้อความอ้างอิงโดยจัดเรียงอักษรในคำเป็นลำดับด้วย sorted(word) การใช้ "".join(...) คือเอาแต่ละตัวอักษรในลิสต์มารวมกันเป็นข้อความอ้างอิงคำเดียว key = key.lower() #ทำข้อความอ้างอิงเป็นอักษรตัวเล็ก if key in anagrams: #ถ้าข้อความอ้างอิงเคยถูกใส่่ไว้ใน dictionary แล้ว ให้เพิ่มเติมคำ word ต่อเข้าไปในลิสต์ของคำที่เป็นอนาแกรมกัน anagrams[key].append(word) else: anagrams[key] = [] #ถ้าข้อความอ้างอิงไม่เคยมีใน dictionary เอาใส่่คำ word เป็นคำแรกของลิสต์ของคำที่เป็นอนาแกรมกัน anagrams[key].append(word) inputfile.close() for key, values in anagrams.items(): #วิธีไล่ว่าใน dictionary มีข้อมูลอะไรบ้าง ทำอย่างนี้ key คือข้อความอ้างอิง values คือลิสต์ของคำที่เป็นอนาแกรมกัน if len(values) > 1: #เราแสดงผลเฉพาะตั้งแต่สองคำขึ้นไปที่เป็นอนาแกรมกัน outputfile.write(", ".join(values)) # ", ".join(values) คือเอาคำต่างๆที่อยู่ในลิสต์ values มาต่อๆกันโดยมีคอมม่าคั่น outputfile.write("\n") #ขึ้นบรรทัดใหม่ด้วย outputfile.close() # ถ้าไม่ใช้ dictionary เราสามารถเก็บข้อมูลด้วยลิสต์ก็ได้ครับ ทำได้แบบนี้: # In[30]: wordlist = '/usr/share/dict/words' #รายการคำภาษาอังกฤษ หนึ่งคำต่อหนึ่งบรรทัด anagramlist = 'anagrams_list.txt' #ชื่อไฟล์ที่จะเก็บอนาแกรมต่างๆไว้ inputfile = open(wordlist) outputfile = open(anagramlist, "w") #เปิดไฟล์เพื่อเขียนโดยการใส่ "w" ไว้ใน open() anagrams = [] #ลิสต์ที่เราจะเก็บคำที่เป็นอนาแกรมกัน for word in inputfile: word = word.strip() #เราลบ \n (newline หรือตัวขึ้นบรรทัดใหม่) ที่อยู่ท้ายแต่ละคำ if len(word) < 2: #เราจะดูเฉพาะคำที่มีสองตัวอักษรขึิ้นไปเท่านั้น continue key = "".join(sorted(word)) #สร้างข้อความอ้างอิงโดยจัดเรียงอักษรในคำเป็นลำดับด้วย sorted(word) การใช้ "".join(...) คือเอาแต่ละตัวอักษรในลิสต์มารวมกันเป็นข้อความอ้างอิงคำเดียว key = key.lower() #ทำข้อความอ้างอิงเป็นอักษรตัวเล็ก anagrams.append((key, word)) #เก็บคำและข้อความอ่้างอิงไว้ด้วยกันเป็นคู่ลำดับ ตอนหลังเราจะมาดูว่าคำไหนมีข้อความอ้างอิงเหมือนกันก็จะเป็นอนาแกรมกัน inputfile.close() anagrams = sorted(anagrams) #ทำการจัดเรียงข้อมูลตามข้อความอ้างอิง คราวนี้คำที่เป็นอนาแกรมกันจะอยู่ติดๆกันในลิสต์ anagrams current_key = "" #ข้อความอ้างอิงปัจจุบันคืออะไร word_list = [] #ลิสต์ที่จะเก็บคำที่เป็นอนาแกรมไว้ด้วยกัน for item in anagrams: key, word = item #ดูคู่ลำดับ (ข้อความอ้างอิง, คำ) ที่ละอัน if current_key == key: #ถ้าข้อความอ้างอิงยังไม่เปลี่ยนก็เพิ่มคำเข้าไปในกลุ่มอนาแกรมเดียวกัน word_list.append(word) else: #แต่ถ้าข้อความอ้างอิงเปลี่ยนแล้ว ก็บันทึกกลุ่มอนาแกรม(ถ้ามี)ลงไฟล์ แล้วเตรียมรวบรวมกลุ่มอนาแกรมสำหรับข้อความอ้างอิงอันใหม่ if len(word_list) > 1: outputfile.write(", ".join(word_list)) outputfile.write("\n") current_key = key word_list = [] word_list.append(word) outputfile.close() # In[ ]: # In[ ]: