[TH] List Class Application Node: Count the frequency from a random value.

บทความนี้เป็นการประยุกต์ใช้โครงสร้างข้อมูลประเภทลิสต์ (list) ของภาษาไพธอนเพื่อเก็บการนับความถี่ของตัวเลขที่สุ่ม ซึ่งมีประโยชน์ต่อการนำไปใช้ทางสถิติต่อไป ซึ่งบทความนี้อาศัยความรู้เรื่องของการสุ่มตัวเลข และการใช้ตัวแปรประเภทลิสต์ โดยทดสอบการทำงานกับ Micropython บนไมโครคอนโทรลเลอร์ esp8266 และ esp32

ภาพที่ 1 การแสดงกราฟความถี่ของข้อมูล

ปัญหา

จะสร้างตารางความถี่ของตัวเลขที่สุ่มค่าใชนช่วง 0 ถึง 20 จำนวน 100 ชุดได้อย่างไร?

แนวคิดในการแก้ไขปัญหา

อุปกรณ์ประกอบการทดลองในครั้งนี้ทางทีมงานเราใช้บอร์ด dCore-miniML (ดังภาพที่ 1) ซึ่งเป็นบอร์ดขยายของ ESP32-CAM และบอร์ด ESP8266 ที่ต่อเชื่อมกับ ET-BASE NodeMCU เพื่อทดสอบการทำงาน

ก่อนอื่นต้องดูคุณสมบัติของคลาสลิสต์ที่ Micropython ของ esp32 รองรับเป็นดังภาพที่ 2 และของ esp8266 เป็นดังภาพที่ 3 จะพบว่ารองรับการทำงานที่เหมือนกันทำให้การเขียนโปรแกรมมีความสะดวกยิ่งขึ้น

ภาพที่ 2 คลาส list ของ esp32
ภาพที่ 3 คลาส list ของ esp8266

การสร้างวัตถุประเภทลิสต์สามารถสร้างได้หลายวิธี แต่ที่ทีมงานเรานิยมใช้เป็นดังนี้

วัตถุ = []

จากภาพที่ 2 และ 3 สามารถสรุปหน้าที่ของคำสั่งได้ดังนี้

  1. append( ข้อมูล ) เป็นการเพิ่มข้อมูลเข้าลิสต์
  2. clear( ) เป็นการล้างค่าที่เก็บทั้งหมด
  3. copy( ) เป็นการคัดลอกลิสต์ไปเป็นข้อมูลอีกชุดหนึ่ง
  4. count( ) นับจำนวนสมาชิกในลิสต์
  5. extend( ลิสต์ ) นำลิสต์ที่กำหนดมาต่อท้าย
  6. index( ข้อมูล ) หาลำดับของ ‘ข้อมูล’ ในลิสต์
  7. insert( ลำดับ, ข้อมูล ) แทรก ‘ข้อมูล’ เข้าไปยัง ‘ลำดับ’ ที่ระบุ
  8. pop( ) นำข้อมูลที่ท้ายลิสต์ออก และคืนค่าที่นำออกกลับมา
  9. remove( ข้อมูล ) ลบลิสต์ที่ต้องการออก ซึ่งถ้าพบข้อมูลหลายชุดจะลบลำดับแรกที่พบออก
  10. reverse() ทำการกลับลำดับข้อมูลภายในลิสต์
  11. sort( reverse=False ) ทำการเรียงข้อมูลในลิสต์ ถ้าต้องการเรียงจากมากไปหาน้อยให้ระบุ reverse=True

ตัวอย่างโปรแกรม

ทดลองเขียนแบบที่ 1

ตัวอย่างแรกเป็นการใช้วัตถุประเภทลิสต์ 2 ชุด ชื่อ data และ freq สำหรับเก็บข้อมูลและจำนวนของข้อมูล โดยการสุ่มนั้นใช้ getrandbits(5)%21 หมายถึงสุ่มค่าขนาด 5 บิต คือ 0 ถึง 31 แต่ต้องการแค่ 0 ถึง 20 จึงทำการหารแบบ % ด้วย 21 เพื่อให้ได้ค่าในช่วง 0 ถึง 20 หลังจากนั้นทำการตรวจสอบค่าที่สุ่มได้กับที่มีมาก่อน ถ้าพบ ให้เพิ่มค่าความถี่ขึ้น 1 ถ้าไม่พบจะเพิ่มรายการข้อมูลเข้าไปใน data พร้อมเพิ่มความถี่ 1 ใน freq เมื่อสุ่มจนครบ 100 ครั้งจึงเข้าสู่ส่วนสุดท้ายของโปรแกรม คือ การแสดงข้อมูลที่เก็บไว้ และตัวอย่างผลลัพธ์การทำงานเป็นดังภาพที่ 4

# ต้องการสุ่มค่า 100 ชุด โดยใช้ตัวเลข 0-20 และนับความถี่
import random

data = []
freq = []
for i in range(100):
    item = random.getrandbits(5)%21
    if (len(data) == 0):
        data.append(item) # เก็บตัวเลขที่สุ่มได้
        freq.append(1) # เก็บค่าความถี่
    else:
        if (item in data):
            idx = data.index(item)
            freq[idx] += 1
        else:
            data.append(item) # เก็บตัวเลขที่สุ่มได้
            freq.append(1) # เก็บค่าความถี่
print("-------------------------")
print("\tData\tFreq.")
print("-------------------------")
sum = 0
for i in range(len(data)):
    print("{}\t{}\t{}".format(i+1,data[i],freq[i]))
    sum += freq[i]
print("-------------------------")
print("Freq. sum = {}".format(sum))
print("-------------------------")

ภาพที่ 4 ตัวอย่างผลลัพธ์ของการทดลองเขียนแบบที่ 1

ทดลองเขียนแบบที่ 2

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

เมื่อได้ข้อมูลที่เรียงจากน้อยไปหามากจึงดำเนินการสร้างตารางสุ่มเก็บใน freqTable ด้วยการพิจารณาค่าของ data ทีละตัว ถ้าเป็นค่าใหม่จะเพิ่ม data นั้นเข้า freqTable พร้อมกำหนดค่าความถี่เป็น 1 ด้วย [ค่า, 1] เข้าไปในลิสต์ แต่ถ้าพบว่าซ้ำกับตัวก่อนหน้าที่เก็บใน freqTable จะทำการเพิ่มค่าความถี่ของข้อมูลนั้น สุดท้ายทำการแสดงข้อมูลออกมาดังตัวอย่างในภาพที่ 5

# ต้องการสุ่มค่า 100 ชุด โดยใช้ตัวเลข 0-20 และนับความถี่
import random

data = []
freqTable = []
# random 100 items
for i in range(100):
    data.append(random.getrandbits(5)%21)
# processing
data.sort()
freqTableIdx = 0
for i in range(100):
    if (i == 0): # first time
        freqTable.append([data[i],1])
    else:
        if (freqTable[freqTableIdx][0] == data[i]):
            freqTable[freqTableIdx][1] += 1
        else:
            freqTable.append([data[i],1])
            freqTableIdx += 1
print("-------------------------")
print("\tData\tFreq.")
print("-------------------------")
sum = 0
for i in range(len(freqTable)):
    print("{}\t{}\t{}".format(i+1, freqTable[i][0],freqTable[i][1]))
    sum += freqTable[i][1]
print("Freq. sum = {}".format(sum))
print("-------------------------")

ภาพที่ 5 ตัวอย่างผลลัพธ์ของการทดลองเขียนแบบที่ 2

ทดลองเขียนแบบที่ 3

เมื่อนำโปรแกรมการเขียนแบบที่ 2 มาเพิ่มส่วนของการแสดงผลกราฟบนจอ TFT ดังภาพที่ 1 จะได้โค้ดของโปรแกรมเป็นดังนี้

# ต้องการสุ่มค่า 100 ชุด โดยใช้ตัวเลข 0-20 และนับความถี่
import random
from st7735 import TFT
from sysfont import sysfont
from machine import SPI,Pin
import machine as mc
import time
import math

data = []
freqTable = []
# random 100 items
for i in range(100):
    data.append(random.getrandbits(5)%21)
# processing
data.sort()
freqTableIdx = 0
for i in range(100):
    if (i == 0): # first time
        freqTable.append([data[i],1])
    else:
        if (freqTable[freqTableIdx][0] == data[i]):
            freqTable[freqTableIdx][1] += 1
        else:
            freqTable.append([data[i],1])
            freqTableIdx += 1
            
print("-------------------------")
print("\tData\tFreq.")
print("-------------------------")
sum = 0
for i in range(len(freqTable)):
    print("{}\t{}\t{}".format(i+1, freqTable[i][0],freqTable[i][1]))
    sum += freqTable[i][1]
print("Freq. sum = {}".format(sum))
print("-------------------------")

mc.freq(240000000)
spi = SPI(2, baudrate=27000000,
          sck=Pin(14), mosi=Pin(12),
          polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,15,13,2)
tft.init_7735(tft.GREENTAB80x160)

tft.fill(tft.BLACK)
tft.text((10,0),"Freq. Graph",tft.YELLOW, sysfont)
for i in range(len(freqTable)):
    tft.fillrect((20+i*6,78-freqTable[i][1]*3),(3,freqTable[i][1]*3),tft.WHITE)
    

ทดลองแบบที่ 4

ในแบบนี้เพิ่มเรื่องของการหาช่วงค่า min และ max เพื่อนำมาใช้คำนวณหาสัดส่วนของการปรับความสูงของกราฟ โดยใช้สมการดังนี้

scale = math.fabs(maxY-minY)

หลังจากนั้นคำนวณค่าความสูงของกราฟซึ่งเป็นค่าความถี่ด้วยสมการดังนี้

data = int(ความสูงของกราฟ*(ความถี่ของข้อมูล/scale))

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

import random
from st7735 import TFT
from sysfont import sysfont
from machine import SPI,Pin
import machine as mc
import time
import math

maxData = const(10000)
data = []
freqTable = []
graphHeight = const(30)
minY = maxData
maxY = 1

mc.freq(240000000)
spi = SPI(2, baudrate=30000000,
          sck=Pin(14), mosi=Pin(12),
          polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,15,13,2)
tft.init_7735(tft.GREENTAB80x160)

tft.fill(tft.BLACK)
tft.text((10,36),"(C)2020-21",tft.YELLOW,sysfont)
tft.text((92,36),"JarutEx",tft.WHITE,sysfont)
tft.text((93,36),"JarutEx",tft.WHITE,sysfont)


time.sleep_ms(1000)
tft.fill(tft.BLACK)
tft.text((10,0), "Freq. Graph",tft.YELLOW,sysfont)

# random 100 items
for i in range(maxData):
    data.append(random.getrandbits(5)%21)
# processing
data.sort()
freqTableIdx = 0
for i in range(maxData):
    if (i == 0): # first time
        freqTable.append([data[i],1])
    else:
        if (freqTable[freqTableIdx][0] == data[i]):
            freqTable[freqTableIdx][1] += 1
        else:
            freqTable.append([data[i],1])
            freqTableIdx += 1

print("-------------------------")
print("\tData\tFreq.")
print("-------------------------")
sum = 0
for i in range(len(freqTable)):
    print("{}\t{}\t{}".format(i+1, freqTable[i][0],freqTable[i][1]))
    if minY > freqTable[i][1]:
        minY = freqTable[i][1]
    if maxY < freqTable[i][1]:
        maxY = freqTable[i][1]
    sum += freqTable[i][1]

print("Freq. sum = {}".format(sum))
print("-------------------------")

tft.text((10,10),"min={} max={}".format(minY,maxY),tft.WHITE,sysfont)
scale = math.fabs(maxY-minY) # หาความยาวของช่วงค่า
#print("Scale = {} [{}-{}]".format(scale,minY,maxY))
for i in range(len(freqTable)):
    data = int(graphHeight*(freqTable[i][1]/scale))
    gColor = tft.color(random.getrandbits(8),random.getrandbits(8),random.getrandbits(8))
    tft.fillrect((20+i*6,78-data),(3,data),gColor)
ภาพที่ 6 ผลการทำงานของการทดลองแบบที่ 4

สรุป

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

ท่านใดต้องการพูดคุยสามารถคอมเมนท์ได้เลยครับ

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-08-21, 2021-11-25