สัปดาห์ที่แล้ว เราหัดเขียนโปรแกรมทดลองแทนค่าตัวอักษรเพื่อแก้ปัญหาโจทย์ตัวอักษรกัน การบ้านระหว่างสัปดาห์ให้เด็กๆไปคิดโจทย์ประเภทนี้กัน และคิดว่าจะให้คอมพิวเตอร์ช่วยสร้างโจทย์ให้เราได้ไหม วันนี้เรามาดูตัวอย่างการสร้างโจทย์กัน
สมมุติว่าเราจะป้อนตัวเลขตัวตั้งเข้าไปสองตัว แล้วให้โปรแกรมสร้างโจทย์ที่บวกตัวตั้งทั้งสองให้ หน้าตาฟังก์ชั่นที่ใช้ทั้งหลายก็อาจจะเป็นแบบนี้ เรามีทั้ง to_digits/to_alphabets และ to_digits_loop/to_alphabets_loop เพื่อให้เห็นว่า list comprehension ใช้แทน for loop ได้อย่างไร สามารถเลือกใช้แบบ list comprehension หรือ loop ก็ได้:
def to_digits(x):
"""
แปลงจำนวนเต็มบวกเป็นลิสต์ตัวเลขโดดแต่ละหลัก
เช่น to_digits(3141) -> [3, 1, 4, 1]
ฟังก์ชั่นนี้ใช้ list comprehension แทน loop
ดูแบบ loop ใน to_digits_loop(x)
to_digits และ to_digits_loop ให้คำตอบเหมือนกัน
"""
digits = str(x)
return [int(k) for k in digits]
def to_alphabets(x):
"""
แปลงตัวเลขให้เป็นสตริงที่เลขโดด 0-9 ถูกแทนที่ด้วยอักษร A ถีง J
เช่น to_alphabets(3141592653) -> 'DBEBFJCGFD'
ฟังก์ชั่นนี้ใช้ list comprehension แทน loop
ดูแบบ loop ใน to_alphabets_loop(x)
"""
letters = "ABCDEFGHIJ"
digits = to_digits(x)
return "".join([letters[k] for k in digits])
def to_digits_loop(x):
"""
แปลงจำนวนเต็มบวกเป็นลิสต์ตัวเลขโดดแต่ละหลัก
เช่น to_digits_loop(3141) -> [3, 1, 4, 1]
ฟังก์ชั่นนี้ใช้ loop แทน list comprehension
ดูแบบ list comprehension ใน to_digits(x)
to_digits และ to_digits_loop ให้คำตอบเหมือนกัน
"""
digits = str(x)
result = []
for d in digits:
result.append(int(d))
return result
def to_alphabets_loop(x):
"""
แปลงตัวเลขให้เป็นสตริงที่เลขโดด 0-9 ถูกแทนที่ด้วยอักษร A ถีง J
เช่น to_alphabets(3141592653) -> 'DBEBFJCGFD'
ฟังก์ชั่นนี้ใช้ loop แทน list comprehension
ดูแบบ list comprehension ใน to_alphabets(x)
to_alphabets และ to_alphabets_loop ให้คำตอบเหมือนกัน
"""
letters = "ABCDEFGHIJ"
digits = to_digits(x)
result = []
for d in digits:
result.append(letters[d])
result = "".join(result)
return result
def create_plus_problem(x, y):
"""
สร้างโจทย์ตัวอักษรแทน x + y = z
"""
z = x + y
# แปลง x, y, z เป็นลิสต์ของเลขโดดอะไร
xdigits = to_digits(x)
ydigits = to_digits(y)
zdigits = to_digits(z)
# แปลง x, y เป็นตัวอักษร
x_a = to_alphabets(x)
y_a = to_alphabets(y)
# แปลง z เป็นตัวอักษร ถ้ามีตัวอักษรที่ไม่เคยปรากฎใน x, y
# ให้แสดงเป็นตัวเลขไว้
z_a = []
symbols = 'ABCDEFGHIJ'
for d in zdigits:
if (d not in xdigits) and (d not in ydigits):
z_a.append(str(d))
else:
z_a.append(symbols[d])
# เปลี่ยนลิสต์ของตัวอักษรเป็นข้อความด้วย "".join(list)
x_a = "".join(x_a)
y_a = "".join(y_a)
z_a = "".join(z_a)
# พิมพ์โจทย์แบบทั้งตัวเลขและตัวอักษร
result = f"{x} + {y} = {z}: {x_a} + {y_a} = {z_a}"
return(result)
ทดลองใช้ create_plus_problem(...)
create_plus_problem(22,33)
'22 + 33 = 55: CC + DD = 55'
create_plus_problem(123,312)
'123 + 312 = 435: BCD + DBC = 4D5'
create_plus_problem(373,363)
'373 + 363 = 736: DHD + DGD = HDG'
create_plus_problem(909,991)
'909 + 991 = 1900: JAJ + JJB = BJAA'
เราจะเห็นได้ว่าโจทย์ที่ออกมาจาก create_plus_problem เป็นโจทย์ที่เดาได้ง่ายเพราะเลข 0-9 จะถูกเรียงลำดับแทนที่ด้วย A-J ทำให้ที่ไหนเราเห็น J เราก็รู้ว่าเป็นเลข 9 ที่ไหนเห็น A ก็รู้ว่าเป็นเลข 0 ฯลฯ ดังนั้นเราควรดัดแปลง create_plus_problem ให้สลับตัวอักษรให้ทายยากๆด้วย
ก่อนอื่นมาดูวิธีสลับตัวอักษรด้วย random.shuffle ก่อน (อ่านเรื่อง random.shuffle ได้ที่ Python random’s shuffle function to randomizes the sequence items หรือที่ ฟังก์ชันที่เกี่ยวข้องกับการสุ่มใน python)
import random #เราต้องการฟังก์ชั่นที่เกี่ยวกับการสุ่มมาใช้สลับตัวอักษร จึงต้องใช้ random
x = "ABCD" #สตริง x ที่เราจะสลับตัวอักษร
# เราจะใช้ random.shuffle(x) เลยไม่ได้เพราะสตริงในภาษาไพธอนไม่่สามารถเปลี่ยนแปลงโดยตรงๆได้
# ต้องเปลี่ยนเป็นลิสต์ แล้วสลับที่ แล้วค่อยเปลี่ยนกลับมาเป็นสตริงอีกที
y = list(x) #เปลี่ยนสตริง x เป็นลิสต์
random.shuffle(y) #สลับที่ของในลิสต์
y = "".join(y) #เปลี่ยนลิสต์จากบรรทัดบนกลับมาเป็นสตริง
print(x,y) # พิมพ์สตริงเริ่มต้น และสตริงที่สลับ
ABCD DBCA
เราจะแปลงอักษรจาก create_plus_problem โดยการสลับ 'ABCDEFGHIJ' ด้วย random.shuffle แล้วแทนค่า A-J ด้วยค่าในลำดับที่สลับแล้วนั้น
ไพธอนมีวิธีแทนค่าตัวอักษรในสตริงแบบตรงไปตรงมาโดยเราไม่ต้องทำเองคือวิธีที่เรียกว่า translate (อ่านเพิ่มเติมได้ที่ Python String translate()) เช่นถ้าเราจะแทนค่า a,b,c,k ด้ย A,B,C,K เราก็อาจทำอย่างนี้ได้:
x = "abcdefghijklmnop" #สตริงเริ่มต้น
trans = x.maketrans("abck","ABCK") #บอกว่าจะแทนอะไรด้วยอะไร
y = x.translate(trans) #ทำการแทนค่า
print(x) #พิมพ์ข้อความดั้งเดิม
print(y) #พิมพ์ข้อความที่แทนค่าตัวอักษร
abcdefghijklmnop ABCdefghijKlmnop
ด้วย random.shuffle และ string translate เราก็มีเครื่องมือพร้อมแล้วสำหรับดัดแปลง create_plus_problem ให้สร้างปัญหาตัวอักษรที่เดายากขึ้น วิธีดัดแปลงก็อาจเป็นแบบนี้:
import random #เราต้องการฟังก์ชั่นที่เกี่ยวกับการสุ่มมาใช้สลับตัวอักษร จึงต้องใช้ random
def create_plus_problem_shuffle(x, y):
"""
สร้างโจทย์ตัวอักษรแทน x + y = z
มีการสลับตัวอักษรให้เดายากขึ้น
"""
z = x + y
# แปลง x, y, z เป็นลิสต์ของเลขโดดอะไร
xdigits = to_digits(x)
ydigits = to_digits(y)
zdigits = to_digits(z)
# แปลง x, y เป็นตัวอักษร
x_a = to_alphabets(x)
y_a = to_alphabets(y)
#สร้างลำดับที่สลับไปมาของ A-J เก็บไว้ใน new_symbols
symbols = 'ABCDEFGHIJ'
new_symbols = list(symbols)
random.shuffle(new_symbols)
new_symbols = "".join(new_symbols)
# แปลง z เป็นตัวอักษร ถ้ามีตัวอักษรที่ไม่เคยปรากฎใน x, y
# ให้แสดงเป็นตัวเลขไว้
z_a = []
for d in zdigits:
if (d not in xdigits) and (d not in ydigits):
z_a.append(str(d))
else:
z_a.append(symbols[d])
# เปลี่ยนลิสต์ของตัวอักษรเป็นข้อความด้วย "".join(list)
x_a = "".join(x_a)
y_a = "".join(y_a)
z_a = "".join(z_a)
#ทำการสลับตัวอักษร A-J ให้เป็นลำดับใน new_symbols
#เพื่อให้เดายาก
trans = x_a.maketrans(symbols, new_symbols)
x_a = x_a.translate(trans)
y_a = y_a.translate(trans)
z_a = z_a.translate(trans)
# พิมพ์โจทย์แบบทั้งตัวเลขและตัวอักษร
result = f"{x} + {y} = {z}: {x_a} + {y_a} = {z_a}"
return(result)
เปรียบเทียบ create_plus_problem และ create_plus_problem_shuffle:
print(create_plus_problem(22,33))
print(create_plus_problem_shuffle(22,33))
22 + 33 = 55: CC + DD = 55 22 + 33 = 55: GG + CC = 55
print(create_plus_problem(123,312))
print(create_plus_problem_shuffle(123,312))
123 + 312 = 435: BCD + DBC = 4D5 123 + 312 = 435: AIG + GAI = 4G5
print(create_plus_problem(373,363))
print(create_plus_problem_shuffle(373,363))
373 + 363 = 736: DHD + DGD = HDG 373 + 363 = 736: IDI + IBI = DIB
print(create_plus_problem(909,991))
print(create_plus_problem_shuffle(909,991))
909 + 991 = 1900: JAJ + JJB = BJAA 909 + 991 = 1900: GFG + GGC = CGFF
ถ้าเราสนใจปัญหาอื่นๆที่ไม่ใช่บวก เราจะทำอย่างไรดี? เราอาจจะเขียนฟังก์ชั่นคล้ายๆ create_plus_problem_shuffle เพิ่มขึ้นมาสำหรับลบ คูณ และหาร แต่นั่นแปลว่าเราจะมีฟังก์ชั่นหลายๆฟังก์ชั่นที่ซ้ำซ้อนกัน ทำเกือบทุกอย่างเหมือนๆกัน ถ้ามีการแก้ไขอะไรเราก็ต้องตามไปแก้ในทุกๆฟังก์ชั่น วิธีที่ดีกว่าในกรณีนี้คือสร้างฟังก์ชั่นเดียวที่รับตัวแปรสามตัว x, y, op โดยที่ x, y เป็นตัวตั้งและ op บอกว่าจะให้บวกลบคูณหรือหารดีกว่า
import random #เราต้องการฟังก์ชั่นที่เกี่ยวกับการสุ่มมาใช้สลับตัวอักษร จึงต้องใช้ random
def create_problem_shuffle(x, y, op = '+'):
"""
สร้างโจทย์ตัวอักษรแทน x op y = z
op ควรเป็นหนึ่งใน '+', '-', '*', '/'
ถ้าไม่ใส่ op เข้ามาจะกำหนดให้ op เป็น '+'
ถ้า op เป็น '-' จะทำให้เฉพาะ y <= x
ถ้า op เป็น '/' จะทำให้เฉพาะ x / y ไม่มีเศษเหลือ
มีการสลับตัวอักษรให้เดายากขึ้น
"""
#เลือกว่าจะบวกลบคูณหรือหาร
if op not in ('+', '-', '*', '/'):
return "รับเฉพาะ +, -, *, / เท่านั้น"
if op == '+':
z = x + y
if op == '-':
if x >= y:
z = x - y
else:
return "ตัวตั้งต้องไม่น้อยกว่าตัวลบ"
if op == '*':
z = x * y
if op == '/':
if x % y == 0:
z = x // y
else:
return "ทำเฉพาะกรณีหารลงตัวเท่านั้น"
# แปลง x, y, z เป็นลิสต์ของเลขโดดอะไร
xdigits = to_digits(x)
ydigits = to_digits(y)
zdigits = to_digits(z)
# แปลง x, y เป็นตัวอักษร
x_a = to_alphabets(x)
y_a = to_alphabets(y)
#สร้างลำดับที่สลับไปมาของ A-J เก็บไว้ใน new_symbols
symbols = 'ABCDEFGHIJ'
new_symbols = list(symbols)
random.shuffle(new_symbols)
new_symbols = "".join(new_symbols)
# แปลง z เป็นตัวอักษร ถ้ามีตัวอักษรที่ไม่เคยปรากฎใน x, y
# ให้แสดงเป็นตัวเลขไว้
z_a = []
for d in zdigits:
if (d not in xdigits) and (d not in ydigits):
z_a.append(str(d))
else:
z_a.append(symbols[d])
# เปลี่ยนลิสต์ของตัวอักษรเป็นข้อความด้วย "".join(list)
x_a = "".join(x_a)
y_a = "".join(y_a)
z_a = "".join(z_a)
#ทำการสลับตัวอักษร A-J ให้เป็นลำดับใน new_symbols
#เพื่อให้เดายาก
trans = x_a.maketrans(symbols, new_symbols)
x_a = x_a.translate(trans)
y_a = y_a.translate(trans)
z_a = z_a.translate(trans)
# พิมพ์โจทย์แบบทั้งตัวเลขและตัวอักษร
result = f"{x}{op}{y} = {z}: {x_a}{op}{y_a} = {z_a}"
return(result)
ทดลองใช้ create_problem_shuffle:
create_problem_shuffle(169,13,'*')
'169*13 = 2197: GEA*GJ = 2GA7'
create_problem_shuffle(13,113,'/')
'ทำเฉพาะกรณีหารลงตัวเท่านั้น'
create_problem_shuffle(156, 942, '*')
'156*942 = 146952: EFH*BDC = EDHBFC'
#สร้างโจทย์สุ่มๆมาสัก 20 ข้อ
import random
for n in range(0,20):
x = random.randint(0,10000) #สุ่ม x มาจาก 0 ถึง 10,000
y = random.randint(0,10000) #สุ่ม y มาจาก 0 ถึง 10,000
op = random.choice(('+', '-', '*', '/'))
print(create_problem_shuffle(x,y,op))
1487*6349 = 9440963: AIGE*CJID = DII0DCJ 2324*1300 = 3021200: AGAI*JGDD = GDAJADD 8305+547 = 8852: AIFJ+JDC = AAJ2 9671*3506 = 33906526: DAIG*CEFA = CCDFAE2A 6251-2975 = 3276: DBIF-BJEI = 3BED ทำเฉพาะกรณีหารลงตัวเท่านั้น 5622*4916 = 27637752: AEFF*CDIE = F7E377AF 6156+8678 = 14834: DHAD+JDEJ = H4J34 ทำเฉพาะกรณีหารลงตัวเท่านั้น 7524+554 = 8078: CJBH+JJH = 80C8 1068*8621 = 9207228: AJGI*IGHA = 9HJ7HHI ตัวตั้งต้องไม่น้อยกว่าตัวลบ ทำเฉพาะกรณีหารลงตัวเท่านั้น ทำเฉพาะกรณีหารลงตัวเท่านั้น 5598-2139 = 3459: BBGJ-HCAG = A4BG 8531*4639 = 39575309: EHGI*CJGB = GBH7HG0B ทำเฉพาะกรณีหารลงตัวเท่านั้น 7395+9298 = 16693: HBEJ+EDEC = 166EB 3508*8593 = 30144244: ECHI*ICJE = EH144244 2659+1129 = 3788: BJGA+HHBA = 3788
ฟังก์ชั่น create_problem_shuffle มีข้อจำกัดเกี่ยวกับโจทย์ลบและหาร ดังนั้นเราอาจต้องช่วยมันหน่อยโดยการเลือกค่า x, y ที่เหมาะสมใส่เข้าไป เช่น:
#สำหรับโจทย์ลบ เลือก y ให้ไม่เกิน x
for n in range(0,10):
x = random.randint(0,10000)
y = random.randint(0,x)
op = '-'
print(create_problem_shuffle(x,y,op))
3287-3093 = 194: BAID-BJFB = 1F4 4327-865 = 3462: DJCG-AIH = JDIC 7266-7253 = 13: JDAA-JDFB = 1B 2854-2199 = 655: BCED-BGAA = 6EE 1187-86 = 1101: FFJG-JA = FF0F 4286-4283 = 3: FEHI-FEHB = B 4744-2417 = 2327: CJCC-FCAJ = F3FJ 7078-5108 = 1970: CHCB-AGHB = G9CH 8245-2372 = 5873: HADI-ACBA = IHBC 3212-1638 = 1574: EJCJ-CFEH = C574
#สำหรับโจทย์หาร เลือก y ให้หาร x ลงตัว
for n in range(0,10):
x = random.randint(0,10000)
while True: #วนสุ่ม y ในช่วง 1 ถึง x//2 จนเจอ y ที่หาร x ลงตัว
y = random.randint(1,x//2)
if x % y == 0:
break
op = '/'
print(create_problem_shuffle(x,y,op))
5317/409 = 13: ICHA/BDG = HC 8044/4022 = 2: AIBB/BIHH = H 4541/19 = 239: AHAG/GD = 23D 9846/547 = 18: HIGF/CGJ = 1I 2937/3 = 979: CIDF/D = IFI 9344/64 = 146: DFHH/GH = 1HG 7340/367 = 20: BIED/ICB = 2D 420/42 = 10: DHI/DH = 1I 1399/1 = 1399: FEGG/F = FEGG 1095/5 = 219: ICAJ/J = 2IA