5. โครงสร้างข้อมูล (Data Structure)

5.1 ลิสต์อีกที (More on Lists)
5.2 ประโยค del (The del statement)
5.3 ทูเปิล (Tuples and Sequences)
5.4 เซ็ต (Sets)
5.5 ดิกชันนารี (Dictionaries)
5.6 เทคนิกการวนรอบ (Looping Techniques)
5.7 เงื่อนไข (More on Conditions)
5.8 น้ำหนักของข้อมูลแบบลำดับ (Comparing Sequences and Other Types)


บทนี้จะอธิบายเทคนิกการใช้งานข้อมูล


5.1 ลิสต์อีกที (More on Lists)

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

append(x)
เติมสมาชิกต่อท้ายลิสต์ มีค่าเทียบเท่า a[len(a):] = [x]

>>> a=[1,2,3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
extend(L)
ยกลิสต์ L ทั้งยวง ไปขยายต่อท้าย a คือ a[len(a):] = L

>>> a=[1,2,3]
>>> L=[4,5,6]
>>> a.extend(L)
>>> a
[1, 2, 3, 4, 5, 6]
insert(i, x)
แทรกสมาชิก x ในตำแหน่ง i

  • a.insert(0, x) ก็คือการไปแทรกข้างหน้า
  • a.insert(len(a), x) ก็คือการไปต่อท้าย คือ a.append(x)
>>> a=[1,2,3]
>>> a.insert(2,0)
>>> a
[1, 2, 0, 3]
remove(x)
ลบสมาชิกตัวแรกที่มีค่าเท่ากับ x ถ้าไม่มีเท่าจะเกิดข้อผิดพลาด

>>> a=[1,2,3]
>>> a.remove(2)
>>> a
[1, 3]

>>> a.remove(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list
pop([i])
ถอดสมาชิกในตำแหน่ง i ออก ถ้าละเลยไม่ใส่ค่า i จะถอดตัวสุดท้ายออกแทน

>>> a=[1,2,3]
>>> a.pop(1)
2

>>> a
[1, 3]
index(x)
คืนค่าดัชนีของลิสต์ ตัวที่มีค่าเท่ากับ x ถ้าไม่มีค่าเท่าเลยจะแจ้งความผิดพลาด

>>> a=[1,2,3]
>>> a.index(2)
1
count(x)
นับจำนวนสมาชิกที่มีค่าเท่ากับ x

>>> a=[2,2,2,9,4,4,4,4]
>>> a.count(2)
3
sort()
จัดเรียงสมาชิก

>>> a=[1,5,4,2,3]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5]
reverse()
กลับตำแหน่งสมาชิก

>>> a=[1,5,4,2,3]
>>> a.reverse()
>>> a
[3, 2, 4, 5, 1]


ตัวอย่างรวมอีกทีนึง

>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.25), a.count('x')
2 1 0

>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]

>>> a.index(333)
1

>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]

>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]

>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
5.1.1 ทำลิสต์เป็นสแต็ค (Using Lists as Stacks)

ถ้าจะใช้งานลิสต์แบบสแต็คคือ เข้าก่อนออกหลัง ก็แค่ใช้เมธอดให้เหมาะสม คือเติมด้วย append() แล้วเอาออกด้วย pop()

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]

>>> stack.pop()
7

>>> stack
[3, 4, 5, 6]

>>> stack.pop()
6

>>> stack.pop()
5

>>> stack
[3, 4]
5.1.2 ใช้งานลิสต์แบบคิว (Using Lists as Queues)

คือ เข้าก่อนออกก่อน ก็ใช้ append() และ pop(0) ตามลำดับ

>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.pop(0)
'Eric'

>>> queue.pop(0)
'John'

>>> queue
['Michael', 'Terry', 'Graham']
5.1.3 ฟังก์ชั่นที่ใช้ในการโปรแกรมแบบฟังก์ชั่น (Functional Programming Tools)

มี 3 ตัว (ไม่นับคำสั่ง lambda)

filter(function, sequence)
filter จะกรองผลลัพธ์จากข้อมูลนำเข้าภายในช่วง sequence เฉพาะถ้า function คืนค่าที่เป็นจริง (ไม่ใช่ศูนย์หรือ None) งงนิดหน่อย ดูตัวอย่างดีกว่า เป็นการหาจำนวนเฉพาะ

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
map(function, sequence)
map จะง่ายกว่า คือจะเอาค่าจาก sequence ไปทำงานใน function แล้วคืนช่วงของผลลัพธ์ออกมาเป็นลิสต์

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

หาก function ต้องการอาร์กิวเมนต์มากกว่าหนึ่งตัว ก็ต้องใส่ sequence ด้วยจำนวนที่เท่ากัน

>>> seq = range(8)
>>> def add(x, y): return x+y
... 
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]

>>> s2 = range(10,18)
>>> map(add, seq, s2)
[10, 12, 14, 16, 18, 20, 22, 24]

>>> s3 = range(18, 10, -1)
>>> map(add, seq, s3)
[18, 18, 18, 18, 18, 18, 18, 18]
reduce(function, sequence)
เอาผลลัพธ์จาก sequence ชุดแรก ไปเป็นข้อมูลนำเข้าให้ function กับ sequence ชุดต่อ ๆ ไป จนหมดข้อมูล

>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55

ถ้าไม่มีข้อมูลจาก sequence จะแสดงข้อผิดพลาด

>>> reduce(add, range(0))
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: reduce() of empty sequence with no initial value

เพื่อเป็นการป้องการการผิดพลาดดังกล่าว อาจใส่อาร์กิวเมนต์ตัวที่สามซึ่งจะกลายเป็นค่าเริ่มต้นให้กับ function เพื่อป้องกันกรณีลิสต์ว่าง ดังนี้

>>> def xsum(seq):
...     def add(x,y): return x+y
...     return reduce(add, seq, 0)
... 

>>> xsum(range(1, 11))
55

>>> xsum([])
0

5.1.4 ลิสต์จากลิสต์ (List Comprehensions)

เป็นโครงสร้างเฉพาะตัวของไพธอนที่ยืมมาจากภาษา Haskell/ML ในการสร้างลิสต์ใหม่จากลิสต์ที่มีอยู่ ใช้มากในไพธอน มีรูปแบบเป็น

[f(x) for x in L [ if p(x) ] ]

แปลว่า ให้สร้างลิสต์ด้วยฟังก์ชั่น f จากลิสต์ L สมาชิกต่อสมาชิก โดยแต่ละสมาชิกจะต้องมีค่า p(x) ที่เป็นจริง

โครงสร้างส่วนหลัง ตรงที่เป็น if ... เป็นตัวเลือก อาจใส่หรือไม่ก็ได้

ตัวอย่าง

>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']

>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]

>>> [3*x for x in vec if x > 3]
[12, 18]

>>> [3*x for x in vec if x < 2]
[]

>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]

>>> [x, x**2 for x in vec]     # error - parens required for tuples
  File "<stdin>", line 1, in ?
    [x, x**2 for x in vec]
               ^
SyntaxError: invalid syntax

>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]

>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]

>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

>>> [str(round(355/113.0, i)) for i in range(1,6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.2 ประโยค del (The del statement)

ใช้งานคล้าย ๆ pop() แต่เลือกช่วงได้ด้วย จึงต้องระบุดัชนีเสมอ

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]

>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]

>>> del a[:]
>>> a
[]

สามารถใช้ del ในการลบตัวแปรได้ด้วย

>>> del a


5.3 ทูเปิล (Tuples and Sequences)

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

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345

>>> t
(12345, 54321, 'hello!')

>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

ตัวอย่างการละเลยการใส่วงเล็บ ซึ่งถ้าดูผ่าน ๆ อาจสับสนได้

>>> empty = ()
>>> singleton = 'hello',    # <-- note trailing comma
>>> len(empty)
0

>>> len(singleton)    # ได้ค่าเป็น 1 เพราะเป็นทูเปิลที่มีสมาชิก 1 ตัว  
1

>>> singleton
('hello',)

การละเลยการใส่วงเล็บในตอนกำหนดค่า ไพธอนเรียกว่า การอัดข้อมูลเป็นอนุกรม (sequence packing) ในกรณีนี้คือ tuple packing (ถ้าละเลยการใส่วงเล็บแล้ว จะถือว่าเป็นข้อมูล tuple เสมอ)

t = x, y, z

และยังสามารถกำหนดค่าแบบย้อนกลับได้ อันนี้เรียกว่า การแตกข้อมูลอนุกรม (sequence unpacking)

x, y, z = t

(ซึ่งถ้าเขียนให้ถูกจริง ๆ แล้วคือ (x, y, z) = t)

และแน่นอนว่าใช้กับลิสต์ได้เช่นเดียวกัน

[x, y, z] = t


5.4 เซ็ต (Sets)

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

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> fruit = set(basket)               # create a set without duplicates
>>> fruit
set(['orange', 'pear', 'apple', 'banana'])

>>> 'orange' in fruit                 # fast membership testing
True

>>> 'crabgrass' in fruit
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
set(['a', 'r', 'b', 'c', 'd'])

>>> a - b                              # letters in a but not in b
set(['r', 'd', 'b'])

>>> a | b                              # letters in either a or b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])

>>> a & b                              # letters in both a and b
set(['a', 'c'])

>>> a ^ b                              # letters in a or b but not both
set(['r', 'd', 'b', 'm', 'z', 'l'])


5.5 ดิกชันนารี (Dictionaries)

เป็นชนิดข้อมูลพิเศษที่อยู่ในรูป {key: value, ...}

  • เปลี่ยนแปลงค่าได้
  • key อาจเป็นข้อมูลชนิดสคริง ตัวเลข หรือทูเปิลที่ไม่ได้บรรจุ multable object ไว้
  • ลิสต์ใช้เป็นคีย์ไม่ได้ เพราะมันเปลี่ยนค่าภายในได้
  • ลบค่าภายในได้ด้วยประโยค del
  • ถ้าใส่ค่าคีย์ซ้ำ จะแทนที่ค่าเก่า
  • ถ้าค้นคีย์ที่ไม่มีอยู่ จะแสดงข้อผิดพลาด
  • ใช้เมธอด keys() ในการแสดงค่าคีย์ทั้งหมด และใช้เมธอด has_key() ในการค้นค่าคีย์
  • สมาชิกภายใน จะถูกเรียงลำดับด้วยค่าของคีย์โดยอัตโนมัติ
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}

>>> tel['jack']
4098

>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}

>>> tel.keys()
['guido', 'irv', 'jack']

>>> tel.has_key('guido')
True

>>> 'guido' in tel
True

แปลงทูเปิลในลิสต์มาเป็นดิกชันนารีด้วยฟังก์ชั่น dict()

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

>>> dict([(x, x**2) for x in (2, 4, 6)])     # use a list comprehension
{2: 4, 4: 16, 6: 36}

หรือหากค่าคีย์เป็นสตริงล้วน อาจกำหนดค่าแบบนี้ก็ได้ (เลียนแบบการส่งผ่านค่าไปยังฟังก์ชั่น)

>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}


5.6 เทคนิกการวนรอบ (Looping Techniques)

iteritems()
ใช้แตกค่าคู่ของดิกชันนารี

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.iteritems():
...     print k, v
...
gallahad the pure
robin the brave
enumerate()
ใช้แปลงจากลิสต์ (หรือทูเปิล) มาเป็นดิกชันนารี ที่มีค่าคีย์เป็นตัวเลข

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print i, v
...
0 tic
1 tac
2 toe
zip()
ใช้จับคู่สองอนุกรม (ลิสต์หรือทูเปิลหรือผสมกัน) ที่มีจำนวนสมาชิกเท่ากัน แปลงรูปมาใช้งานแบบดิกชันนารี

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print 'What is your %s?  It is %s.' % (q, a)
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.
reversed()
ใช้กลับค่าสมาชิก

>>> for i in reversed(xrange(1,10,2)):
...     print i
...
9
7
5
3
1
sorted()
ใช้จัดเรียงสมาชิก

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print f
...     
apple
banana
orange
pear


5.7 เงื่อนไข (More on Conditions)

  • เงื่อนไขในประโยค while และ if ไม่จำเป็นต้องเป็นการเปรียบเทียบค่าเสมอไป แต่จะเป็นอะไรก็ได้ที่ ถ้าคืนค่าที่ไม่เป็นศุนย์หรือ None จะถูกนับว่าเป็นจริง
  • in และ not in ใช้ดูว่ามีค่าอยู่ในลำดับข้อมูลหรือเปล่า
  • สำหรับลิสต์ is และ is not ใช้ดูว่าเป็นออปเจคต์เดียวกันหรือเปล่า
  • การเปรียบเทียบค่า มีลำดับความสำคัญน้อยกว่าการกระทำทางคณิตศาสตร์
  • ถ้ามีการเปรียบเทียบหลายค่าในคำสั่งเดียว จะเรียงลำดับจากขวาไปซ้าย เช่น a < b == c
    >>> True == 1
    True
    
    >>> 1 < 2 == 1
    False
    
    >>> 1 < 2 == 2
    True
  • ตัวเปรียบเทียบทางตรรกะ มีความสำคัญน้อยที่สุด โดย not สำคัญที่สุด และ or สำคัญน้อยที่สุด คือ
    A and not B or C เขียนได้เป็น (A and (not B)) or C
  • ตัวเปรียบเทียบทางตรรกะ จะเปรียบเทียบจากซ้ายไปขวา และถ้าจุดใดที่เมื่อเปรียบเทียบแล้วไม่เป็นจริง ประโยคส่วนที่เหลือจะไม่ถูกเปรียบเทียบต่อ ไพธอนเรียกการนี้ว่า ตัวกระทำลัดวงจร (short-circuit operators) และค่าที่คืนออกมาจากการเปรียบเทียบ จะเป็นค่าสุดท้ายที่ทำการเปรียบเทียบ
  • สามารถกำหนดค่าผลของการเปรียบเทียบ ให้กับตัวแปรได้
    >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
    >>> non_null = string1 or string2 or string3
    >>> non_null
    'Trondheim


5.8 น้ำหนักของข้อมูลแบบลำดับ (Comparing Sequences and Other Types)
  • ออปเจคต์แบบลำดับ สามารถนำไปเปรียบเทียบกับออปเจคต์อื่นได้ โดยลิสต์น้ำหนักน้อยกว่าสตริง สตริงน้อยกว่าทูเปิล (อย่าจำมาก อาจเปลี่ยนแปลงได้ ให้ทดสอบตามรุ่นไพธอนที่ใช้จริง)
  • การเปรียบเทียบยึดหลักจากซ้ายไปขวา และหยุดทันทีที่ผลออกมาไม่เป็นจริง
(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)