φυβλαςのβλογ
phyblas的博客



ภาษา python เบื้องต้น บทที่ ๒๘: ฟังก์ชันบางตัวที่เกี่ยวข้องกับอิเทอเรเตอร์
เขียนเมื่อ 2016/04/27 09:56
แก้ไขล่าสุด 2024/02/22 11:08
 

หลังจากที่ในบทที่ ๒๖ และ บทที่ ๒๗ ได้แนะนำถึงสิ่งที่เรียกว่าอิเทอเรเตอร์ไปแล้ว คราวนี้จะแนะนำฟังก์ชันที่ทำหน้าที่สร้างอิเทอเรเตอร์ที่น่าจะมีประโยชน์และได้ใช้บ่อย

มีฟังก์ชันที่ถูกทำไว้สำเร็จรูปเพื่อใช้สร้างอิเทอเรเตอร์ที่จำเป็น ส่วนใหญ่อยู่ในมอดูลชื่อ itertools เวลาใช้จึงต้องเรียกใช้

ก่อนอื่นให้ทำการ import เข้ามาก่อนเลย
import itertools

แต่นอกจากนี้แล้วก็มีบางส่วนที่อยู่ในกลุ่มฟังก์ชันหลักสามารถใช้งานได้ทันที เช่น map, filter และ reversed ซึ่งได้กล่าวถึงไปแล้ว และในบทนี้จะแนะนำเพิ่มอีก ๒ ตัว คือ zip และ enumerate

ฟังก์ชันเหล่านี้จะสร้างอิเทอเรเตอร์ใหม่ขึ้นโดยใช้ชุดข้อมูลหรืออิเทอเรเตอร์ที่ใส่ เข้าไปเป็นอาร์กิวเมนต์เป็นพื้นฐาน สามารถสร้างอะไรออกมาได้มากมาย

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

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



enumerate

เป็นฟังก์ชันที่สร้างอิเทอเรเตอร์ของทูเพิลที่นำชุดข้อมูลมาผูกเข้ากับตัวเลข

ตัวอย่าง
print(list(enumerate('กขคงจ'))) # ได้ [(0, 'ก'), (1, 'ข'), (2, 'ค'), (3, 'ง'), (4, 'จ')]

จะเห็นว่าสมาชิกจากสายอักขระที่ใส่เป็นอาร์กิวเมนต์ถูกนำมาผูกเข้ากับทูเพิลทีละตัว

ค่าที่ได้จาก enumerate นั้นเป็นอิเทอเรเตอร์ดังนั้นต้องใช้ list เพื่อแปลงเป็นลิสต์อีกทีเพื่อแสดงสมาชิกทั้งหมด

หากใส่คีย์เวิร์ดไปอีกตัวคือ start จะกำหนดจุดเริ่มต้นของการนับได้ ถ้าไม่ใส่จะเริ่มจาก 0
print(list(enumerate('OBAFGKM',start=100))) # ได้ [(100, 'O'), (101, 'B'), (102, 'A'), (103, 'F'), (104, 'G'), (105, 'K'), (106, 'M')]

enumerate มีประโยชน์เวลาที่เราต้องการให้เวลาที่ใช้ for เพื่อวนซ้ำอยู่มีตัวเลขบางอย่างที่ช่วยนับจำนวนรอบ เวลาใช้ for สามารถนำตัวแปรมารับ ๒ ตัวได้ โดยตัวแรกจะรับค่าตัวเลข และตัวหลังจะรับสมาชิกของข้อมูล
for i,e in enumerate(['H','He','Li','Be','B'],start=1):
    print('%d:%s'%(i,e),end=', ')
# ได้ 1:H, 2:He, 3:Li, 4:Be, 5:B, 

การทำงานของฟังก์ชันนี้เราสามารถสร้างเจเนอเรเตอร์ที่ทำงานเหมือนกันขึ้นมาได้ง่าย
def genenumerate(ชุดข้อมูล,start=0):
    i = start
    for c in ชุดข้อมูล:
        yield (i,c)
        i += 1



zip

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

ตัวอย่าง
print(list(zip('กขค','abc'))) # ได้ [('ก', 'a'), ('ข', 'b'), ('ค', 'c')]

สามารถใช้ประโยชน์เช่นในเวลาที่เรามีชุดข้อมูลที่ต้องการวนซ้ำใน for ตั้งแต่ ๒ ลิสต์ขึ้นไป
a = ['ไก่','จิก','เด็ก','ตาย','เด็ก','ตาย','บน','ปาก','โอ่ง']
b = 'กจฎฏดตบปอ'
for x,y in zip(a,b):
    print(y+':'+x, end='|')
# ได้ ก:ไก่|จ:จิก|ฎ:เด็ก|ฏ:ตาย|ด:เด็ก|ต:ตาย|บ:บน|ป:ปาก|อ:โอ่ง|

หรือจะนำมาใช้สร้างดิกชันนารีก็ได้
print(dict(zip(b,a))) # ได้ {'จ': 'จิก', 'อ': 'โอ่ง', 'ต': 'ตาย', 'ฏ': 'ตาย', 'ด': 'เด็ก', 'ฎ': 'เด็ก', 'ป': 'ปาก', 'ก': 'ไก่', 'บ': 'บน'}

ลองเขียนเจเนอเรเตอร์ที่ทำงานเหมือนกับ enumerate ขึ้นมาดูได้
def genzip(*กลุ่มชุดข้อมูล):
    s = tuple(map(iter,กลุ่มชุดข้อมูล))
    while(1):
        y = tuple(map(next,s))
        if(len(y)<len(s)): break
        yield y

***ในไพธอน 2 นั้น zip จะคืนค่าเป็นลิสต์ไม่ใช่อิเทอเรเตอร์ ดังนั้นไม่ต้องมาแปลงเป็นลิสต์อีกทีเพื่อแสดงผล
>>> รายละเอียด



itertools.count

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

อาร์กิวเมนต์ที่ต้องใส่มี ๒ ตัวคือ เลขเริ่มต้น และระยะห่าง จำนวนที่ใส่อาจไม่ต้องเป็นจำนวนเต็มก็ได้
for i in itertools.count(10,10):
    if(i>=100): break
    print(i,end='.')
# ได้ 10.20.30.40.50.60.70.80.90.

หากไม่ใส่อาร์กิวเมนต์การนับจะเริ่มจาก 0 และไล่ไปทีละ 1

ลองสร้างเจเนอเรเตอร์เลียนแบบได้ดังนี้
def count(เริ่มต้น=0,ระยะห่าง=1):
    n = เริ่มต้น
    while(1):
        yield n
        n += ระยะห่าง



itertools.cycle

เป็นอิเทอเรเตอร์ที่นำข้อมูลจำนวนหนึ่งมาวนซ้ำไปเรื่อยๆอย่างไม่มีที่สิ้นสุด
cyc = itertools.cycle(range(2,7,2))
i = 0
while(i<30):
    print(next(cyc),end=';')
    i += 1
# ได้ 2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;2;4;6;

สร้างเจเนอเรเตอร์ที่ทำงานในลักษณะเดียวกันได้ตามนี้
def cycle(ชุดข้อมูล):
    s = tuple(ชุดข้อมูล)
    while(1):
        for x in s:
            yield x



itertools.repeat

เป็นอิเทอเรเตอร์ที่เอาข้อมูลตัวเดียวมาปล่อยซ้ำไปเรื่อยๆ

อาร์กิวเมนต์มี ๒ ตัว ตัวแรกคือออบเจ็กต์ที่ต้องการซ้ำ ส่วนตัวหลังคือจำนวนครั้งที่ต้องการซ้ำ แต่ถ้าไม่ใส่อาร์กิวเมนต์ตัวหลังจะเป็นการวนซ้ำไม่มีที่สิ้นสุด
print([list(itertools.repeat('o',7))]) # ได้ [['o', 'o', 'o', 'o', 'o', 'o', 'o']]

ลองสร้างเจเนอเรเตอร์จำลองการทำงานดูได้
def repeat(ออบเจ็กต์,จำนวนครั้ง='อนันต์'):
    if(จำนวนครั้ง != 'อนันต์'): # กรณีที่ใส่จำนวนครั้ง
        for i in range(จำนวนครั้ง):
            yield ออบเจ็กต์
    else: # กรณีที่ไม่ใส่จำนวนครั้ง ให้วนอนันต์ครั้ง
        while(1):
            yield ออบเจ็กต์ 



itertools.chain

เป็นอิเทอเรเตอร์ที่เอาชุดข้อมูลหลายๆชุดมาเชื่อมเรียงต่อกัน

อาร์กิวเมนต์ที่ต้องใส่คือข้อมูลชนิดกลุ่มหรืออิเทอเรเตอร์ จะใส่กี่ตัวก็ได้ ทั้งหมดจะถูกนำมาเรียงต่อกัน
print(list(itertools.chain([1,2],range(3,7),(7,8),{9,10}))) # ได้ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

การสร้างเจเนอเรเตอร์แสดงการทำงานต้องใช้ดอกจันในการรับอาร์กิวเมนต์ที่ไม่รู้ว่าจะมีกี่ตัว
def chain(*ชุดข้อมูลทั้งหมด):
    for ชุดข้อมูลแต่ละชุด in ชุดข้อมูลทั้งหมด:
        for ข้อมูลในชุด in ชุดข้อมูลแต่ละชุด:
            yield ข้อมูลในชุด



itertools.product

เป็นอิเทอเรเตอร์ที่เอาไว้สร้างทูเพิลที่เกิดจากการนำชุดข้อมูลหลายชุดมาแจกแจง

อาร์กิวเมนต์ที่ต้องใส่คือชุดข้อมูลทั้งหมดที่จะนำมาใช้แจกแจง และจำนวนทูเพิลที่ได้จะเท่ากับจำนวนสมาชิกของแต่ละชุดคูณกันทั้งหมด
x = ['x=1','x=2','x=3']
y = ['y=1','y=2']
print(list(itertools.product(x,y))) # [('x=1', 'y=1'), ('x=1', 'y=2'), ('x=2', 'y=1'), ('x=2', 'y=2'), ('x=3', 'y=1'), ('x=3', 'y=2')]
print(len(list(itertools.product(range(7),range(6),range(2))))) # ได้ 84

แต่นอกจากอาร์กิวเมนต์แล้วยังมีคีย์เวิร์ดตัวหนึ่งที่สามารถใช้ได้ คือ repeat ซึ่งใช้ระบุว่าต้องการให้ข้อมูลชุดหนึ่งถูกหยิบมาแจกแจงซ้ำกี่ครั้ง ถ้าไม่ใส่จะมีค่าเป็น 1 หมายความว่าข้อมูลแต่ละชุดถูกหยิบมาครั้งเดียว
print(list(itertools.product('+-',repeat=3))) # ได้ [('+', '+', '+'), ('+', '+', '-'), ('+', '-', '+'), ('+', '-', '-'), ('-', '+', '+'), ('-', '+', '-'), ('-', '-', '+'), ('-', '-', '-')]
print(len(list(itertools.product('+-',repeat=10)))) # ได้ 1024
print(' + '.join(map(''.join,itertools.product('xyz',repeat=3)))) # ได้ yzz + zxx + zxy + zxz + zyx + zyy + zyz + zzx + zzy + zzz

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

เริ่มจากถ้าหากมีข้อมูลอยู่แค่ ๒ ชุดแล้วต้องการมาแจกแจงก็แค่นำมาเข้าวังวน for ๒ ชั้น
def product2(ชุดข้อมูล1,ชุดข้อมูล2):
    s1 = tuple(ชุดข้อมูล1)
    s2 = tuple(ชุดข้อมูล2)
    for c1 in s1:
        for c2 in s2:
            yield (c1,c2)

แต่ว่าในความเป็นจริงแล้วอาจจะมีชุดข้อมูลกี่ชุดก็ได้ ไม่จำกัดแค่ ๒ ถ้าหาก ๓ ชั้นก็ใช้ for ซ้อนกัน ๓ ชั้น แต่ปัญหาคือเราต้องทำให้รองรับได้หมดไม่ว่าจะมีข้อมูลกี่ชุด

ในสถานการณ์ที่ไม่อาจรู้ได้ว่าจะมีวังวนซ้อนกันกี่ชั้นแบบนี้ สิ่งที่ใช้การได้ดีก็คือการเขียนฟังก์ชันเวียนเกิด (กล่าวถึงไปในบทที่ ๒๐)

ลองเขียนฟังก์ชันขึ้นมาใหม่โดยใช้ดอกจันแทนอาร์กิวเมนต์ไม่จำกัดจำนวน และมีการวัดจำนวนอาร์กิวเมนต์ ถ้าหากยาว ๒ ก็ทำการวนซ้ำด้วยข้อมูล ๒ ชุดนั้น แต่ถ้ายาวกว่าก็ให้ทำการเรียกตัวฟังก์ชันนี้เอง
def productn(*กลุ่มชุดข้อมูล):
    s = tuple(map(tuple,กลุ่มชุดข้อมูล))
    if(len(s)==2):
        for c1 in s[0]:
            for c2 in s[1]:
                yield (c1,c2)
    else:
        for c1 in productn(*s[:-1]): # เรียกใช้ตัวเอง
            for c2 in s[-1]:
                yield c1 + (c2,)

แต่ในตัวฟังก์ชันจริงๆต้องเพิ่มคีย์เวิร์ด repeat เข้าไปด้วย สุดท้ายแล้วก็เลยเขียนเป็นแบบนี้
def product(*กลุ่มชุดข้อมูล,repeat=1):
    s = tuple(map(tuple,กลุ่มชุดข้อมูล))*repeat
    if(len(s)==2):
        for c1 in s[0]:
            for c2 in s[1]:
                yield (c1,c2)
    else:
        for c1 in product(*s[:-1]):
            for c2 in s[-1]:
                yield c1 + (c2,)



itertools.permutations

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

อาร์กิวเมนต์มี ๒ ตัว ตัวแรกคือชุดข้อมูลที่ต้องการใช้ ส่วนตัวหลังคือจำนวนที่ต้องการเลือกจากในนั้น หากไม่ใส่ก็จะถือว่าเป็นการเลือกทั้งหมด ก็คือเลือกเป็นจำนวนเท่ากับจำนวนข้อมูลในชุด
print(list(itertools.permutations('xy'))) # ได้ [('x', 'y'), ('y', 'x')]
print(' + '.join(map(''.join,itertools.permutations('xyz')))) # ได้ xyz + xzy + yxz + yzx + zxy + zyx
print(' + '.join(map(''.join,itertools.permutations(['x','y','z'],2)))) # ได้ xy + xz + yx + yz + zx + zy 

การสร้างเจเนอเรเตอร์แบบนี้อาจเลือกใช้ itertools.product ให้เป็นประโยชน์ได้ โดยเอาชุดข้อมูลที่ป้อนเข้ามาเพียงอันเดียวนี้มาแจกแจงตามจำนวนที่ต้องการ เลือกด้วยคีย์เวิร์ด repeat

เพียงแต่ว่าจะต้องตัดกรณีที่เลือกอันซ้ำ กันทิ้งไป ซึ่งอาจใช้เงื่อนไขที่ว่าหากกรณีไหนที่มีเลขลำดับซ้ำกันก็ตัดกรณีนั้นทิ้ง การทำแบบนี้สามารถใช้ set ช่วยได้ เพราะเซ็ตมีคุณสมบัติที่ว่าสมาชิกห้ามซ้ำ หากตรวจสอบความยาวหลังแปลงเป็นเซ็ตแล้วพบว่าลดลงก็แสดงว่ามีตัวซ้ำ ก็ตัดกรณีนั้นทิ้ง
def permutations(ชุดข้อมูล,r='จำนวนข้อมูลทั้งหมด'):
    s = tuple(ชุดข้อมูล)    
    n = len(s)
    if(r=='จำนวนข้อมูลทั้งหมด'): r = len(n)
    for ii in product(range(n),repeat=r):
        if(len(set(ii)) == len(ii)):
            yield tuple(s[i] for i in ii)



itertools.combinations

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

combinations ต่างจาก permutations ตรงที่ไม่สนใจลำดับของการหยิบ ดังนั้นผลที่ได้จะมีจำนวนน้อยกว่า โดยตัดตัวที่มีสมาชิกซ้ำกันออกไป

อาร์กิวเมนต์มีสองตัวเช่นเดียวกับ permutations แต่อาร์กิวเมนต์ตัวหลังคือจำนวนที่เลือกนั้นจำเป็นต้องใส่ จะละไว้ไม่ได้
print(list(itertools.combinations('xyzt',4))) # ได้ [('x', 'y', 'z', 't')]
print(' '.join(map(''.join,itertools.combinations('xyzt',3)))) # ได้ xyz xyt xzt yzt
print(' '.join(map(''.join,itertools.combinations(['x','y','z','t'],2)))) # ได้ xy xz xt yz yt zt

การสร้างเจเนอเรเตอร์นั้นอาจใช้วิธีในทำนองเดียวกับ permutations แต่เพิ่มเงื่อนไขว่าจะต้องมีการจัดเรียงรูปแบบเดียว คือตามลำดับเดิมที่ถูกจัดวางในกลุ่ม ดังนั้นหมายความว่าหากใช้ฟังก์ชัน sorted เพื่อจัดเรียงแล้วจะต้องได้เท่าเดิม ตัวที่ไม่เหมือนเดิมแสดงว่าเรียงลำดับไม่ถูกต้องอยู่ก็คัดออกไป
def combinations(ชุดข้อมูล,r):
    s = tuple(ชุดข้อมูล)
    n = len(s)
    for ii in product(range(n),repeat=r):
        if(sorted(ii) == list(ii) and len(set(ii)) == len(ii)):
            yield tuple(s[i] for i in ii)



itertools.combinations_with_replacement

คล้ายกับ combinations แต่ว่าพิจารณาในกรณีที่สามารถเลือกซ้ำได้

ลองเปรียบเทียบระหว่าง product, permutations, combinations และ combinations_with_replacement ดูได้
print(list(itertools.product('กขค',repeat=2))) # ได้ [('ก', 'ก'), ('ก', 'ข'), ('ก', 'ค'), ('ข', 'ก'), ('ข', 'ข'), ('ข', 'ค'), ('ค', 'ก'), ('ค', 'ข'), ('ค', 'ค')]
print(list(itertools.permutations('กขค',2))) # ได้ [('ก', 'ข'), ('ก', 'ค'), ('ข', 'ก'), ('ข', 'ค'), ('ค', 'ก'), ('ค', 'ข')]
print(list(itertools.combinations('กขค',2))) # ได้ [('ก', 'ข'), ('ก', 'ค'), ('ข', 'ค')]
print(list(itertools.combinations_with_replacement(' กขค',2))) # ได้ [('ก', 'ก'), ('ก', 'ข'), ('ก', 'ค'), ('ข', 'ข'), ('ข', 'ค'), ('ค', 'ค')]

การสร้างก็คล้ายกับ combinations แต่ตัดเงื่อนไขที่ว่าต้องไม่เลือกซ้ำทิ้งไป
def combinations_with_replacement(ชุดข้อมูล,r):
    s = tuple(ชุดข้อมูล)
    n = len(s)
    for ii in product(range(n),repeat=r):
        if(sorted(ii) == list(ii)):
            yield tuple(s[i] for i in ii)



สรุปเนื้อหา

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



อ้างอิง




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

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

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ