2.4 อ่านโจทย์แบบ grader
ในการสอบ TOI Zero และค่าย สอวน. ไม่มีคนนั่งอ่านโค้ดของเรา grader จะรันโค้ดกับข้อมูลชุดที่เราไม่เคยเห็น แล้วเทียบผลลัพธ์กับคำตอบ โค้ดที่คิดถูกแต่พิมพ์ผลเกินมาหนึ่งคำก็ได้ศูนย์ หน้านี้รวมทุกอย่างจากสามหน้าก่อน มาใช้อ่านโจทย์จริงหนึ่งข้อ เขียนทีละขั้นจนเสร็จ แล้วทดสอบแบบที่ grader ทดสอบ
grader ทำงานยังไง
- คอมไพล์โค้ดที่ส่ง ถ้าคอมไพล์ไม่ผ่าน ก็จบตรงนั้น
- รันโปรแกรมกับชุดทดสอบ (test case) ทีละชุด แต่ละชุดคือข้อมูลเข้าหนึ่งก้อน ป้อนให้โปรแกรมแบบเดียวกับที่เราพิมพ์ในหน้าต่างดำ
- เก็บทุกอย่างที่โปรแกรมพิมพ์ออกมา เอาไปเทียบกับคำตอบของชุดนั้นทีละตัวอักษร
- ให้ผลแต่ละชุด เช่น ผ่าน คำตอบผิด ใช้เวลาเกิน หรือโปรแกรมพังกลางทางอย่างหารด้วยศูนย์ในหน้า 1.2 คะแนนมักคิดตามจำนวนชุดที่ผ่าน
grader ไม่เห็นหน้าจอ ไม่เห็นบรรทัด Process returned ไม่อ่านคำอธิบาย และไม่เดาว่าเราตั้งใจพิมพ์อะไร มันเห็นแค่ตัวอักษรที่ printf พิมพ์ออกมาเท่านั้น
ส่วนประกอบของโจทย์
โจทย์ตัวอย่าง ยอดขายสหกรณ์
สหกรณ์โรงเรียนขายสมุดราคาเล่มละ P บาท วันหนึ่งขายได้ N เล่ม ให้พิมพ์รายงานยอดขายของวันนั้น
ข้อมูลเข้า บรรทัดแรกเป็นวันที่ขาย ในรูปแบบ D/M/Y เมื่อ Y เป็นปี พ.ศ. (1 ≤ D ≤ 31, 1 ≤ M ≤ 12, 2500 ≤ Y ≤ 2600) บรรทัดที่สองมีจำนวนเต็ม N และ P คั่นด้วยช่องว่าง (1 ≤ N, P ≤ 100000)
ข้อมูลออก บรรทัดแรกพิมพ์ Date ตามด้วยวันที่ในรูปแบบ DD/MM/YYYY ที่วันและเดือนเป็นเลขสองหลักเสมอ บรรทัดที่สองพิมพ์ Total ตามด้วยยอดขายและคำว่า baht ทุกส่วนคั่นด้วยช่องว่างหนึ่งช่อง
5/3/2569
120 25
Date 05/03/2569
Total 3000 baht
โจทย์แบบ grader เกือบทุกข้อมีห้าส่วนนี้ แต่ละส่วนตอบคำถามคนละข้อ
| ส่วน | ใช้ตัดสินใจเรื่อง |
|---|---|
| เรื่องราว | ต้องคำนวณอะไร |
| ข้อมูลเข้า | เขียน scanf ยังไง กี่ตัว ตัวคั่นคืออะไร |
| ขอบเขต | ใช้ int หรือ long long กล่อง %s กี่ช่อง ตารางกว้างเท่าไร |
| ข้อมูลออก | เขียน printf ยังไง ทุกตัวอักษร |
| ตัวอย่าง | เช็กว่าเข้าใจโจทย์ถูก และเป็นชุดทดสอบชุดแรกของเรา |
ขั้นที่ 1 อ่านข้อมูลเข้า
บรรทัดแรกเป็นวันที่คั่นด้วย / ใช้ "%d/%d/%d" แบบในหน้า 2.3 บรรทัดที่สองเป็นตัวเลขสองตัว เขียน scanf แยกตามบรรทัดจะอ่านง่าย ส่วนการขึ้นบรรทัดระหว่างสองบรรทัดนั้นไม่ต้องสนใจ %d ข้ามให้เอง
ขั้นที่ 2 ดูขอบเขต
ยอดขายคือ N × P ซึ่งมากสุดได้ 100000 × 100000 = 1010 เกิน 2 × 109 ของ int ตามตารางในหน้า 1.3 จึงต้องเก็บ N กับ P เป็น long long ตั้งแต่ตอนรับ ส่วนวัน เดือน ปี เล็กมาก ใช้ int ได้
ขั้นที่ 3 อ่านข้อมูลออกทีละตัวอักษร
เทียบกับผลลัพธ์ตัวอย่าง แล้วแปลงเป็นข้อความแม่แบบของ printf
Dateตัว D ใหญ่ ตามด้วยช่องว่างหนึ่งช่อง วันที่ต้องได้05/03ทั้งที่ข้อมูลเข้าเป็น5/3จึงใช้%02dจากหน้า 2.2 ส่วนปีมี 4 หลักอยู่แล้ว ใช้%dTotalตัว T ใหญ่ ช่องว่าง ตัวเลข ช่องว่าง แล้วbahtตัวเล็กทั้งคำ- สองบรรทัด จึงมี
\nท้ายทั้งสองบรรทัด
ขั้นที่ 4 เขียนโปรแกรม
#include <stdio.h>
int main()
{
int day, month, year;
long long books, price;
scanf("%d/%d/%d", &day, &month, &year);
scanf("%lld %lld", &books, &price);
printf("Date %02d/%02d/%d\n", day, month, year);
printf("Total %lld baht\n", books * price);
return 0;
}
5/3/2569
120 25
Date 05/03/2569
Total 3000 baht
ขั้นที่ 5 ทดสอบด้วยตัวอย่าง
ข้อมูลเข้าสองบรรทัดยังพิมพ์เองไหว แต่โจทย์บางข้อมีข้อมูลเข้าเป็นสิบบรรทัด พิมพ์เองทั้งช้าและพิมพ์ผิดง่าย ให้ คัดลอกข้อมูลเข้าทั้งก้อนไปวางในหน้าต่างดำทีเดียว
- คัดลอกข้อมูลเข้าตัวอย่าง ในเว็บนี้เลื่อนเมาส์ไปวางบนกล่องข้อมูลเข้า จะมีปุ่มคัดลอกที่มุมขวาบน ถ้าเป็นโจทย์ไฟล์ PDF ให้ลากคลุมตัวอย่างแล้วกด Ctrl+C
- กด F9 รอจนหน้าต่างดำขึ้นและว่างอยู่
- คลิกขวาในหน้าต่างดำ หรือกด Ctrl+V ถ้ามีกล่องถามยืนยันว่าจะวางข้อความหลายบรรทัด ให้กดยืนยัน
- ถ้าโปรแกรมยังนิ่งอยู่ แปลว่าบรรทัดสุดท้ายที่คัดลอกมาไม่มีการขึ้นบรรทัดปิดท้าย กด Enter อีกครั้ง
หน้าต่างดำจะแสดงข้อมูลเข้าที่วางไว้ก่อน ตามด้วยผลลัพธ์ หน้าตาแบบนี้
5/3/2569
120 25
Date 05/03/2569
Total 3000 baht
สองบรรทัดแรกคือสิ่งที่เราวาง หน้าต่างดำแสดงให้ดูเฉยๆ ไม่ใช่สิ่งที่โปรแกรมพิมพ์ grader ไม่เห็นสองบรรทัดนี้ เทียบเฉพาะสองบรรทัดหลังกับผลลัพธ์ที่ต้องได้ ทีละตัวอักษร
ขั้นที่ 6 ทดสอบค่าขอบ
ผ่านตัวอย่างยังไม่พอ ชุดทดสอบของ grader มีค่าที่โจทย์อนุญาตทุกแบบ โดยเฉพาะค่ามากสุดกับน้อยสุด ให้แต่งข้อมูลเข้าเองจากขอบเขต แล้วคิดคำตอบด้วยมือเทียบ ถ้าขั้นที่ 2 พลาดไปใช้ int ค่ามากสุดจะจับได้ทันที
#include <stdio.h>
int main()
{
int day, month, year;
int books, price;
scanf("%d/%d/%d", &day, &month, &year);
scanf("%d %d", &books, &price);
printf("Date %02d/%02d/%d\n", day, month, year);
printf("Total %d baht\n", books * price);
return 0;
}
31/12/2600
100000 100000
Date 31/12/2600
Total 1410065408 baht
คำตอบที่ถูกคือ 10000000000 ซึ่งโปรแกรมในขั้นที่ 4 พิมพ์ได้ถูก ส่วนแบบ int ได้เลขบวกที่ดูปกติ ไม่ติดลบเหมือนตัวอย่างในหน้า 1.3 ถ้าไม่ได้คิดคำตอบด้วยมือไว้ก่อน จะดูไม่ออกเลยว่าผิด
ชุดทดสอบที่ควรลองเองทุกข้อ
ตัวอย่างในโจทย์ ค่าน้อยสุดทุกตัว ค่ามากสุดทุกตัว และค่าที่ทำให้รูปแบบผลลัพธ์เปลี่ยน อย่างวันที่หลักเดียวกับสองหลักในข้อนี้
ผลลัพธ์ต้องตรงทุกตัวอักษร
ถ้าคำตอบที่ต้องได้คือ Total 3000 baht ผลเหล่านี้ไม่ผ่านทั้งหมด แม้ตัวเลขจะถูก
| โปรแกรมพิมพ์ | ผิดตรงไหน |
|---|---|
total 3000 baht | ตัวพิมพ์เล็กพิมพ์ใหญ่ต่างกัน |
Total 3000 Baht | เหมือนกัน |
Total 3000 baht | ช่องว่างสองช่อง |
Total 3000baht | ขาดช่องว่าง |
Total 3000.00 baht | คำตอบเป็นจำนวนเต็ม แต่พิมพ์ทศนิยม |
Total = 3000 baht | มีเครื่องหมายเกินมา |
เรื่องที่ตาเปล่ามองไม่เห็น
- ช่องว่างท้ายบรรทัด เช่น
printf("Total %lld baht \n", ...)มีช่องว่างหลังbahtgrader บางระบบตัดช่องว่างท้ายบรรทัดทิ้งก่อนเทียบ บางระบบไม่ตัด อย่าเสี่ยง ให้ไม่มีเลย - การขึ้นบรรทัด ทุกบรรทัดจบด้วย
\nรวมถึงบรรทัดสุดท้าย และไม่พิมพ์บรรทัดว่างเกินมา - ข้อความที่ใส่ไว้ตรวจเอง เช่น
[]จากหน้า 2.2 หรือการพิมพ์ค่าที่รับมาดู ต้องลบออกก่อนส่ง
ข้อมูลเข้าที่ไม่บอกจำนวน
โจทย์ในบทนี้บอกเสมอว่ามีข้อมูลกี่ตัว จึงเขียน scanf ได้ครบ แต่บางโจทย์ให้ "รับตัวเลขไปเรื่อยๆ จนจบข้อมูลเข้า" หรือให้จำนวน N มาก่อนแล้วตามด้วยตัวเลข N ตัว แบบนี้ต้องใช้วนซ้ำ ซึ่งจะเรียนในบทที่ 4
ลองทำให้พัง
แบบที่ 1 พิมพ์ข้อความบอกให้กรอก
โปรแกรมที่เขียนให้คนใช้ มักบอกก่อนว่าต้องพิมพ์อะไร ลองเพิ่มเข้าไปในโปรแกรม coop
#include <stdio.h>
int main()
{
int day, month, year;
long long books, price;
printf("Enter date: ");
scanf("%d/%d/%d", &day, &month, &year);
printf("Enter books and price: ");
scanf("%lld %lld", &books, &price);
printf("Date %02d/%02d/%d\n", day, month, year);
printf("Total %lld baht\n", books * price);
return 0;
}
5/3/2569
120 25
Enter date: Enter books and price: Date 05/03/2569
Total 3000 baht
นี่คือสิ่งที่ grader เห็น บรรทัดแรกไม่ตรงกับ Date 05/03/2569 ข้อนี้ได้ผิดทุกชุด ทั้งที่คำนวณถูกหมด ข้อความบอกให้กรอกมีประโยชน์กับคนเท่านั้น grader ไม่ต้องการ พิมพ์เฉพาะสิ่งที่ข้อมูลออกสั่งเท่านั้น
แบบที่ 2 ลืม \n
ลบ \n ออกจาก printf บรรทัด Date
#include <stdio.h>
int main()
{
int day, month, year;
long long books, price;
scanf("%d/%d/%d", &day, &month, &year);
scanf("%lld %lld", &books, &price);
printf("Date %02d/%02d/%d", day, month, year);
printf("Total %lld baht\n", books * price);
return 0;
}
5/3/2569
120 25
Date 05/03/2569Total 3000 baht
printf สองครั้งไม่ได้แปลว่าสองบรรทัด ข้อความจะต่อกันไปเรื่อยๆ จนกว่าจะเจอ \n ทั้งสองค่าถูก แต่รวมเป็นบรรทัดเดียว grader ให้ผิด
สรุป
- grader เห็นแค่สิ่งที่
printfพิมพ์ และเทียบทีละตัวอักษร - ข้อมูลเข้าบอกวิธีเขียน
scanfขอบเขตบอกชนิดตัวแปร ข้อมูลออกบอกข้อความแม่แบบของprintf - ทดสอบด้วยการคัดลอกข้อมูลเข้าทั้งก้อนไปวางในหน้าต่างดำ
- ทดสอบค่ามากสุดน้อยสุดเองเสมอ และคิดคำตอบด้วยมือไว้เทียบ
- ห้ามพิมพ์ข้อความบอกให้กรอก ห้ามมีช่องว่างเกิน และทุกบรรทัดจบด้วย
\n
กลับไปเช็กรายการ ผ่านบทนี้เมื่อ แล้วไปทำแบบฝึกหัด