python-data-structure
python-data-structure

เขียนบทความเกี่ยวกับ Python มาก็หลายเรื่องล่ะ (บทความ Python ทั้งหมด) แต่ไม่เคยที่จะลงในพื้นฐานของ Python จริงๆเลย เช่นพวก Data structure และ algorithm แบบต่างๆ

ไหนๆก็ไหนๆล่ะ วันนี้จะมาลงมือเล่นกับ Python จริงๆจังๆแล้วโดยจะเริ่มจาก Data structure ทั้งหมดเลย นอกจากนั้นจะมีเกร็ดความรู้เสริมต่างๆกับภาษาอื่นๆสรุปไว้ด้วย เพื่อที่จะให้เข้าใจความแตกต่างของแต่ละภาษาไป โดยมีพื้นฐานเดียวกันนั้นเอง 🙂

Python Data structure มีอะไรบ้าง?

เจ้า Python ไม่ได้มีรูปแบบการเก็บข้อมูลที่แยกออกมาเยอะแยะให้ปวดหัว มันมีหลักๆก็แค่ 4 ตัวนี้

  1. Lists
  2. Tuples (Immutable list)
  3. Sets
  4. Dictionaries 

ซึ่งเจ้าตัวเล็กๆน้อยอย่าง String กับ Integer & Floating point เราไม่พูดถึงล่ะกันน่ะ 🙂

Lists

สั้นๆง่ายเลย List เป็นการเก็บ data แบบ linear data ไล่ไปเป็นเส้นตรงๆไปเลย (คล้ายๆการเก็บข้อมูลแบบ array แหละเพียงแต่ไม่ fixed size ตายตัว ใส่เข้าไปได้เรื่อยๆ)

List Key point

  • Not specific type of list
  • Mutable

ใน python list จะมีความพิเศษอย่างนึง คือ มันเก็บทุกอย่างตั้งแต่สากกะเบือยันเรือรบเลย ไม่เหมือนในหลายๆภาษาที่จะต้องระบุ type ตายตัวของ list ที่ต้องการจะทําการเก็บข้อมูลลงไป เช่น


List<string> al=new ArrayList<string>(); # java

List<int> iList = new List<int>(); # c#

แต่ถ้า python มันใส่ลงไปได้เลย อยากใส่ไรก็ใส่ สบายใจเราก็พอ


myList = ['1',2,3.0,[4,5,6]] #python

จริงๆบางคนก็อาจจะแย้งน่ะว่าถ้า Java หรืออื่นๆก็ใส่ type object ลงไปสิ แค่นั้นก็ทําให้ใส่ทุกค่าลงไปได้เหมือนกัน List<object> แต่ลองคิดดูสิมันต้องมานั่ง cast object อีกก็ลําบากอยู่เหมือนกันน่ะ

วิธีการ Slicing ข้อมูล

การเอาข้อมูลจาก Lists/Arrays/Tuples มาใช้ใน python นอกจากการใช้ for loop ในการดึงข้อมูลออกมา เราสามารทําการ Slice ข้อมูลออกเป็นส่วนๆได้น่ะ เช่น


a = [1, 2, 3, 4, 5, 6, 7, 8]

print(a[1:4]) # [2, 3, 4] #basic
print(a[1:4:2]) # [2, 4] #advance with inceremental step size 2 แปลว่าจะตัดทุกๆ 2 ตัวนั้นเอง
print(a[::-1) # [8, 7, 6, 5, 4, 3, 2, 1] # แบบ reverse จากหน้ามาหลัง วิธีดูง่ายๆคือไม่ระบุเลขนั้นแหละ ?:?: แล้วเพิ่ม step size เป็นติดลบ
print(a[-1]) # 3 เอาจากตัวหลังสุดมา

ซึ่งการทําลักษณะนี้จะทําให้เราไม่ต้องไปยุ่งกับ for loop เลย และเร็วกว่ามากๆด้วยล่ะ ถ้าในภาษาอื่นอย่าง Java ก็จะมี


List inputA = input.subList(0, input.size()/2);

ซึ่งก็ดูยุ่งยากอยู่เลยไม่เหมือน slice ใน python 🙂

วิธีการ Looping & Access List

ถ้า access ทั่วไปก็เหมือนทุกภาษาเลย เราใช้ index

หรือ การใช้ enumurate function เพื่อให้มัน count index ให้เรานั้นเอง เราจะได้ไม่ต้องมาหา len แล้วสร้าง range ในการ loop เอง ซึ่งเราสามารถใช้ได้กับทุก data type ที่เป็น iterable ของ python


#access by index
list = [1,2,3]
print(list[1]) #output: 2

#old fashion
car_list = ['honda','toyota','benz']
for index in range(len(car_list)):
    print(car_list[index])

#enumerate
animal_list = ['cat','dog','bird']
for idx, value in enumerate(animal_list):
    print(idx, value) 

#output: 
#0 cat
#1 dog
#2 bird
#https://docs.python.org/2.3/whatsnew/section-enumerate.html
#https://www.programiz.com/python-programming/methods/built-in/enumerate

วิธีการ Create List

มีหลากหลายวิธีในการ create list โดยใจะใช้ […] หรือ elegant way ในการจัดการกับ create list/manipulate list โดย Comprehension = map,filter,reduce ก็ได้

โดย format จะเป็น


simple_list = [] # simple list creation
data_list = [1,'wording',3] #list with multiple type

list = [1,2,3]
newList = [item*2 for item in list] #list items: 2,4,6

ด้วยวิธีนี้มันสามารถจัดการ list ได้เร็วกว่าการมานั่งสร้าง function แยกเยอะ

list-comprehension
list-comprehension (https://www.python-course.eu/list_comprehension.php)

List function ที่น่าสนใจ

python เองก็มี list function ที่เตรียมไว้ให้เราแล้ว ซึ่งช่วยเหลือเวลาเราทํางานได้ระดับนึงเลย ได้แก่


list = [1,2,2,3]

#Get Index 

list.index(2)  #จะได้ค่าออกมาเป็น 1 น่ะ เพราะมันหาตําแหน่งของ element ไม่ใช่ access list

#Count Item

list.count(2) #จะได้ค่าเป็น 2 เพราะมี 2 อยู่ 2 ตัว 

#Remove first item

list.remove(2) # list = [1,2,3] remove แค่ exact value ตัวแรก

#Remove from index and return it
# return 2 
# list = [1,2,3]

list.pop(2) 

#Remove specific index
# list = [2,2,3]

del(list[0]) 
#Reverse list

list.reverse() # list = [3,2,2,1]

#Append list

list.append('horse') # list = [1,2,2,3,'horse']
list.append(['cat','dog'] # list = [1,2,2,3,['cat','dog']]

#Extend list 
#เพิ่มค่าใน list แบบ multiple 

list.extend(['cat','dog']) # list = [1,2,2,3,'cat','dog']

Python List Tip 1:

  • น่าสนใจกับ Copy List ใน Python คือมันจะ references value กันเมื่อเราใช้ b = a  คือเหมือนชี้ pointer ไปที่ list นั้นแทน แปลว่าถ้า a เปลี่ยน b ก็จะเปลี่ยนด้วย… ซึ่งในทางกลับกัน b เปลี่ยน a ก็เปลี่ยนด้วยเหมือนกัน
  • ดังนั้นการ copy list ใน python ที่ถูกคือการ slicing นั้นเอง ซึ่งจะออกมาเป็น b = a[:] เป็นต้น

List Collection Tip 1:

ในบางภาษาอย่าง Java จะมีสิ่งที่เรียกว่า ArrayList หรือ LinkedList ความแตกต่างของเจ้าสองตัวนี้ก็คือเรื่องของการ implementation น่ะ … อย่างที่เรารู้กัน Array เนี่ยมันมีความสามารถพิเศษคือการ read access ที่เร็วมากตาม position ได้ ถูกต้องมั้ย? เช่น System.out.println(a[5]) เป็นต้น ซึ่งเจ้า ArrayList มันก็มีความสามารถแบบนั้นแหละเพราะมันเป็น dymanic re-sizing array…. แต่เจ้า LinkedList มันไม่สามารถทํายังงั้นได้ มันจะต้อง traverse แต่ต้นจนจบเลย โดยช้าเร็วขึ้นอยู่กับ size ของ list แต่มีข้อดีตรงที่ว่าเราสามารถ insertion/deletion ได้เร็วกว่า ArrayList เพราะไม่ต้องทําการ moved (copied) ของข้อมูลไปมาตามหลักของ array นั้นเอง

รูปตัวอย่างของ array และ index ที่เข้าไปเรียกใช้ได้ เหมือนกับ ArrayList (การเพิ่มอะไรลงไปใหม่ไม่คุ้มเลย เพราะมันต้องจัดการ reindex ใหม่)

example-arrays
example-arrays (https://www.geeksforgeeks.org/introduction-to-arrays/)

รูปตัวอย่างของ linkedlist มันจะมี pointer ชี้ไปตัวถัดไป และเวลาอ่านต้องไปที่ล่ะ node จนจบ ทําให้ช้า แต่ง่ายในการ insertion/deletion

example-linkedlist
example-linkedlist (https://www.geeksforgeeks.org/linked-list-set-1-introduction/)

Tuples

Tuples คือ immutable list (แก้ไขไม่ได้) ที่เมื่อสร้างขึ้นมาแล้วจะไม่สามารถแก้ค่าใดๆได้เลย โดยเก็บข้อมูลเป็น linear เหมือน List ทั่วไปนั้นแหละ

โดยวิธีการสร้าง Tuples ขึ้นมาใช้งานตามตัวอย่างนี้เลย


#packing style
tup1 = ('physics','chemistry',1997,2000) #วิธีสร้าง tuples 
print(tup1) 
#output : ('physics','chemistry',1997,2000)

#comma style
tupX = 'accident',
print(type(tupX)) 
#output: tuple type

# เราสามารถนํามาประยุกต์ใช้กับ zip() 
# zip เป็น function ที่ใช้ในการรวม Array หลายๆตัว
tup2 = (1,2,3)
pairs = zip(tup1,tup2)
#output: ('physics','chemistry',1997,2000,1,2,3)

เหตุผลที่ใช้ Tuples แทนที่จะใช้ List ก็น่าจะเป็นเรื่อง ความเร็วของ Tuples เมื่อใช้ โดยหลักการณ์เลือกใช้ก็ง่ายๆเลย คือเมื่อเราไม่ต้องการจะเปลี่ยนแปลงค่าอะไรใน list นั้น จงเลือกใช้ tuples เพราะมันเร็วส์กว่า Lists นิดหน่อย ลองอ่านผลการทดสอบดูน่ะ 🙂

Python Built-in Function ที่น่าสนใจพวก Zip() ที่ merge iterate หลายๆตัวเข้าด้วยกัน เป็นต้น

วิธีการ Looping and Access Tuples

การเอาค่าจาก Tuples ทําได้ทั้งระบุ index หรือใช้ unpack ของ Tuple นั้นเอง



tup1 = ('physics','chemistry',1997,2000)

#index style

print(tup1[0]) #physics

#unpacking style
a,b,c,d = tup1

print(a) #physics
print(b) #chemistry
print(c) #1997
print(d) #2000

Sets

จุดเด่นของ set คือการที่มันจะ unique data ให้เหลือตัวเดียวนั้นเอง โดยเราเอามาใช้ร่วมกับ List ได้ เพื่อ remove dulpicate data ให้เป็น Unique

Set Key point

  • Unique
  • Unordered
  • Mutable

Creating Set

เราสามารถสร้าง set ตรงๆ หรือสร้างจาก list ก็ได้โดยใช้ built-in function set() หรือสร้างด้วย {…} เลยก็ได้น่ะ


game_list = ['Rov','Call of duty','Rov']
example_set = set(game_list) # {'Rov','Call of duty'}

#วิธีสร้าง set แบบ classic
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} 

#หรือแบบนี้ classic style
x = set()
#แล้วค่อยๆ add ลงไป
x.add(1) # {1} 

#add new list to set
new_list_for_add = ['cookie','ham']
x.update(new_list_for_add) #output - {1,'cookie','ham'}

Dictionaries

มาถึงตัวสุดท้ายแล้ววว dictionaries เป็นการเก็บข้อมูลแบบ Keys-Values ที่เราใช้กันโคตรบ่อย

Creating Dictionary 


# วิธีสร้าง dictionary
tel = {'jack': 4098, 'sape': 4139}

# วิธีการเพิ่ม dictionary 
tel['guido'] = 4127 

# show all key
print(tel.keys())

# show all value
print(tel.values())

# show all dictionary
print(tel)

#True วิธีการเช็คข้อมูลอยู่ใน dictionaries หรือไม่
'guido' in tel

#True
'Jacky' not in tel 

วิธีการ Looping and Access Dictionaries

เนื่องจากมากเป็น key,values เวลาเราเรียกค่าขึ้นมาใช้ เราก็ต้องเลือก key และ values มาใช้นั้นเอง


d = {'car':'honda','money':12345}

#iterate
for key, value in d.items():
    print(key,value)
#output
#car honda
#money 12345

#iterate with index
for idx, item in enumerate(d.items()):
    print(idx,item)
#output 
#1 (car,honda)
#2 (money,12345)
#https://www.programiz.com/python-programming/methods/built-in/enumerate

วิธีการสร้าง Dictionary จาก CSV file

รู้หรือไม่ว่าปกติแล้วการอ่าน csv ใน python จะออกมาเป็น list [] แต่เราสามารถอ่านออกมาเป็น Dictionary {} ได้เหมือนกันโดยเปลี่ยนแค่คําสั่ง แล้วจะได้ออกมาเป็น Tuples () พร้อม key,value ที่ต้องการ เช่น


import csv

# Basic csv file
csvfile = open('file.csv','r')

for row in csv.reader(csvfile):
    print(row)

#output: list [1,2,3]

# csv file with Dictionary
for row in csv.Dictreader(csvfile):
    print(row)

csvfile.close()

#output: dictionary [('column1',1),('column2',2),('column3',3)]

Dictionary function ที่น่าใช้


d = {'car':'honda','money':12345}

# Get Dict. Keys
#function ในการจัดการกับ keys
print(d.get('money','No money'))
#output: 12345

#ในทางกลับกันถ้าเราเลือก Key ที่ไม่มีมันจะ Thrown exception ให้เรา เช่น
print(d.get('house','No house'))
#output: No house

# remove key dictionary
del d['money']

#safety remove dictionary
d.pop('money','No key')

Collection Modules น่าสนใจ 

  1. Counter
  2. DefaultDict
  3. Nametuple

Counter เพื่อจัดการนับจํานวนข้อมูลใน list

ถ้าเราต้องการหาค่าของ Duplicate Value ใน List ให้นึกถึง Counter

from collections import Counter
a = [.....สมมุติว่าข้อมูลมหาศาลมาก....]
counter_a = Counter(a)
print(counter_a)

#output: tuple ของ dictionary ที่จะ count ให้ว่ามีข้อมูลอะไรบ้างที่ซํ้ากันใน list นั้น
# ({'a': 20, 'b': 35}) 

DefaultDict เพื่อลดความเสี่ยงจาก KeyError

Dictionary เป็น container type ที่เก็บข้อมูลแบบ Key-Value โดยมักจะ thrown error ออกมาเป็น KeyError ถ้าไม่มี Key นั้น

เจ้า Default Dictionary ลดความเสี่ยงนั้นโดยการตั้ง value ให้เป็น default สําหรับ dictionary นั้นๆ


from collections import defaultdict

somedict = {}

print(somedict[9999]) #keyerror

somedict = defaultdict(int)

print(somedict[9999]) #output > 0

เรายังสามารถนํา default dictionary มาประยุกต์ในการจัดการกับโครงสร้างของข้อมูลที่หน้าตาไม่รู้อีกด้วย เช่น เราต้องการจะสร้าง dictionary ของรายการอาหารที่คนชอบทานมากที่สุดแต่เราไม่รู้ว่า รายการอาหารนั้นถูกสร้างเป็น Key หรือยัง?

สิ่งที่เราต้องทําก็คงไม่พ้น … เราต้องนั่งเช็ค key รายการอาหารก่อนที่จะนับเพิ่มลงไปอีกตัว


food_list = {}

for menu,value in food_list:
   if menu not in food_counter:
      food_counter[menu] = []

   food_counter[menu].append[value]

# แต่ถ้าเราใช้ default dictionary น่ะ โลกเปล่ียนไปเลย
# เราสร้าง dictionary ที่มี default value เป็น list ไว้ แปลว่ามาถึงเรา append ได้เลย ไม่ต้องเช็คค่า
# โดยเราสามารถสร้าง default_factory เป็น int,list ก็ได้
from collections import defaultdict

food_counter = defaultdict(list)
for menu,value in food_list:
   food_counter[menu].append(value)

สรุปง่ายๆแล้ว สมมุติถ้าเราอยากใช้ dictionary ที่มี default value ด้วย เราควรจะใช้ defaultdic เป็นตัวหลักเลย

สร้าง Key ให้กับ Tuple ด้วย NamedTuple 

Tuple คือ immutable list เนอะ ซึ่งโดยปกติแล้วมันจะเก็บข้อมูลแบบ sequence ไปเรื่อยๆเลย โดยเราต้องดึงค่ามาจาก index ไม่ใช่ key

แต่ด้วย NamedTuple เราจะสามารถสร้าง Key ให้กับ Tuple ได้ 🙂 เพื่อที่จะเรียกใช้งานง่ายๆนั้นเอง


from collections import namedtuple

#สร้าง key ของ tuple ออกมา

DataDetails = namedtuple('DataDetails', ['data', 'time'])

data_entries = []

for data, time in entries_data:
    data = DataDetails(date,time)
    data_entries.append(data)

# access namedtuple
# เอาค่าเฉพาะ 5 ตัวแรกขึ้นมาโชว์

for data in data_entries[:5]:
    print(data.time)
    print(data.data)

เราควรจะใช้ namedtuple แทน tuple ธรรมดาทุกๆที่เลยใน code ของเราเพราะมันทําให้อ่านง่าย และ เป็น pythonic style ด้วย 🙂

อย่างที่เรารู้กัน namedtuple เป็น immutable เพราะฉะนั้นเราไม่สามารถเปลี่ยนแปลงค่าได้ แต่เรามีเทคนิคที่สามารถแปลงเป็น dictionary ได้ !


DataDetail = namedtuple('DataDetails', ['data', 'time'])
data = DataDetail('Test Data','20:00 PM')
type(data) 
#output - namedtuple

type(data._asDict())
#output - OrderedDict
print(data._asDict()['time'])
#output - 20:00 PM

แล้วไงต่อ?

หลังจากเรียนรู้เรื่องของ Data structure แบบต่างๆไปแล้ว การจะเป็น Software Engineer ที่ดีต้องรู้ว่าเราควรเลือกเครื่องมือแบบไหนมาใช้งาน ไม่ใช่รู้อันไหนเลยใช้อันนั้น เพราะฉะนั้นสิ่งที่ต้องกังวลคือเรื่องของ Performance issues นั้นเอง และ เจ้าปัญหาเรื่องของ Performance หลักๆนั้นก็มาจากผลของการ Algorithm ที่ไม่มีประสิทธิ์ภาพเพียงพอนั้นเอง โดยเราใช้วิธีวัดจากสิ่งที่เรียกว่า Big O นั้นเอง เพราะฉะนั้นไว้ต่อในบทความหน้าเรื่องของ Algorithm และ Big O น่ะครับ เพื่อให้เป็นพื้นฐานที่ดีของการเขียนโปรแกรมนั้นเอง 🙂

 

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.