7.3 ตารางตัวอักษร
โจทย์กริดใน TOI Zero ส่วนใหญ่ไม่ได้ให้ตัวเลข แต่ให้ แผนที่ ที่วาดด้วยตัวอักษร เช่น # คือกำแพงหรือต้นไม้ . คือทางว่าง S คือจุดเริ่ม หน้านี้จะเก็บแผนที่แบบนี้ไว้ในอาเรย์สองมิติของ char อ่านทีละแถวด้วย %s แล้วไล่ดูทีละช่อง
ข้อมูลเข้าที่เป็นแผนที่
รูปแบบที่เจอบ่อยที่สุด บรรทัดแรกคือ R กับ C จากนั้นอีก R บรรทัด บรรทัดละ C ตัวอักษรติดกันไม่มีช่องว่าง
4 6
#..#..
.##...
....#.
#....#
แต่ละบรรทัดคือข้อความหนึ่งข้อความแบบในบทที่ 6 ทั้งแผนที่จึงเป็นข้อความหลายข้อความเรียงกัน เก็บใน char map[R][C + 1] หนึ่งแถวของอาเรย์คือหนึ่งข้อความ ต้องจองเกินไปหนึ่งช่องสำหรับตัวปิดท้าย '\0' ที่ scanf เติมให้
อ่านและนับในแผนที่
สวนแห่งหนึ่งวาดแผนที่ไว้ # คือต้นไม้ . คือพื้นว่าง ให้นับว่ามีต้นไม้กี่ต้น R และ C ไม่เกิน 100
#include <stdio.h>
char map[100][101]; // (1)!
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
scanf("%s", map[r]); // (2)!
}
int trees = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (map[r][c] == '#') { // (3)!
trees++;
}
}
}
for (int r = 0; r < rows; r++) {
printf("%s\n", map[r]);
}
printf("trees = %d\n", trees);
return 0;
}
- 100 แถว แถวละ 101 ช่อง 100 ช่องสำหรับตัวอักษร อีก 1 ช่องสำหรับ
'\0' map[r]ที่มีวงเล็บแค่ชุดเดียวคือทั้งแถวที่ r ซึ่งเป็นอาเรย์ของcharหนึ่งมิติ ใช้กับ%sได้เหมือนข้อความในบทที่ 6 และไม่ต้องมี&เหมือนกันmap[r][c]ที่มีวงเล็บสองชุดคือตัวอักษรหนึ่งตัว เทียบกับ'#'ที่ใช้เครื่องหมายคำพูดเดี่ยว
4 6
#..#..
.##...
....#.
#....#
#..#..
.##...
....#.
#....#
trees = 7
อ่านทีละแถว แต่ใช้งานทีละช่องได้ตามปกติ map[1][2] คือ '#' ตัวที่สองของแถวที่มีเลข 1 พิมพ์ทั้งแถวด้วย printf("%s\n", map[r]) ก็ได้ เพราะทุกแถวปิดท้ายด้วย '\0'
ถ้าอยากใส่แผนที่เล็กๆ ไว้ในโค้ดเลยโดยไม่อ่านจากข้อมูลเข้า ให้ใส่ทีละแถวเป็นข้อความคั่นด้วยจุลภาคในปีกกา แบบเดียวกับ char name[8] = "Somchai"; ในหน้า 6.2 แต่ละแถวยังต้องจองเผื่อช่อง '\0' แผนที่กว้าง 4 จึงประกาศแถวละ 5 ช่อง
char map[3][5] = {
"#..#",
"..#.",
"#..."
};
หาตำแหน่งของตัวอักษร
โจทย์เขาวงกตมักบอกจุดเริ่ม S กับจุดหมาย E ไว้ในแผนที่ ไม่ได้บอกพิกัดมาตรงๆ ต้องไล่หาเอง
#include <stdio.h>
char map[100][101];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
scanf("%s", map[r]);
}
int start_r = -1, start_c = -1, end_r = -1, end_c = -1;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (map[r][c] == 'S') {
start_r = r;
start_c = c;
} else if (map[r][c] == 'E') {
end_r = r;
end_c = c;
}
}
}
printf("S at row %d column %d\n", start_r + 1, start_c + 1);
printf("E at row %d column %d\n", end_r + 1, end_c + 1);
return 0;
}
5 7
#######
#S..#.#
#.#.#.#
#...E.#
#######
S at row 2 column 2
E at row 4 column 5
ตั้งค่าเริ่มต้นเป็น -1 ไว้ก่อน ถ้าแผนที่ไม่มี S เลย ตำแหน่งจะยังเป็น -1 เอาไปเช็กต่อได้ว่าหาไม่เจอ การหาทางเดินจาก S ไป E ให้สั้นที่สุดต้องใช้ BFS ซึ่งจะเรียนในบทที่ 27 บทนี้ฝึกแค่อ่านและเดินดูแผนที่ให้คล่องก่อน
พิมพ์แผนที่แบบกลับด้าน
อยากได้ภาพสะท้อนในกระจกซ้ายขวา ให้แต่ละแถวพิมพ์ตั้งแต่คอลัมน์สุดท้ายย้อนกลับมาคอลัมน์แรก ทีละตัวด้วย %c
#include <stdio.h>
char map[100][101];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
scanf("%s", map[r]);
}
for (int r = 0; r < rows; r++) {
for (int c = cols - 1; c >= 0; c--) {
printf("%c", map[r][c]);
}
printf("\n");
}
return 0;
}
4 6
#..#..
.##...
....#.
#....#
..#..#
...##.
.#....
#....#
ถ้าอยากกลับบนลงล่าง ให้ลูปแถวเดินจาก rows - 1 ลงมาแทน ส่วนลูปคอลัมน์เดินตามปกติ ลองเขียนดู
แผนที่ที่เป็นตัวเลขหลักเดียว
บางโจทย์ให้ตัวเลข 0 ถึง 9 ติดกันเป็นบรรทัด เช่น ผลผลิตของแปลงนาแต่ละแปลงเป็นตัน บรรทัด 3051 คือแปลงที่ผลผลิต 3, 0, 5 และ 1 ตัน อ่านด้วย %d ไม่ได้ เพราะ scanf จะอ่านเป็นเลขสามพันห้าสิบเอ็ดตัวเดียว ให้อ่านเป็นข้อความด้วย %s แล้วแปลงทีละตัวด้วย - '0' ตามหน้า 1.4
#include <stdio.h>
char line[100][101];
int yield[100][100];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
scanf("%s", line[r]);
for (int c = 0; c < cols; c++) {
yield[r][c] = line[r][c] - '0';
}
}
int total = 0;
for (int r = 0; r < rows; r++) {
int row_total = 0;
for (int c = 0; c < cols; c++) {
row_total += yield[r][c];
}
printf("row %d: %d\n", r + 1, row_total);
total += row_total;
}
printf("total = %d\n", total);
return 0;
}
3 4
3051
1207
4419
row 1: 9
row 2: 10
row 3: 18
total = 37
หลังแปลงแล้ว ตาราง yield ใช้งานเหมือนตารางตัวเลขในหน้า 7.1 และ 7.2 ทุกอย่าง
ลองทำให้พัง
แบบที่ 1 อ่านทีละตัวด้วย %c
แผนที่เป็นตัวอักษร หลายคนจึงอ่านทีละตัวด้วย %c แทนที่จะอ่านทีละแถว
#include <stdio.h>
char map[100][101];
int main()
{
int rows, cols;
scanf("%d %d", &rows, &cols);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
scanf("%c", &map[r][c]);
}
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
printf("%c", map[r][c]);
}
printf("\n");
}
return 0;
}
3 4
#..#
.##.
#..#
#..
#
.#
#.
#
บรรทัดแรกของผลลัพธ์ว่าง แล้วแผนที่ก็เละ เพราะ %c อ่านทุกตัวอักษรจริงๆ รวมถึงการขึ้นบรรทัด '\n' ด้วย ตัวแรกที่อ่านได้คือ '\n' ท้ายบรรทัด 3 4 ไม่ใช่ # ทุกช่องหลังจากนั้นจึงเลื่อนไปหนึ่งตัว และมี '\n' ของบรรทัดอื่นแทรกเข้ามาอีก
แก้ได้สองวิธี วิธีแรกคืออ่านทีละแถวด้วย %s แบบในหน้านี้ ซึ่งข้ามช่องว่างและการขึ้นบรรทัดให้เอง วิธีที่สองคือเติมช่องว่างหน้า %c เป็น scanf(" %c", &map[r][c]); แบบในบทที่ 2 ช่องว่างนั้นสั่งให้ข้ามช่องว่างและการขึ้นบรรทัดทั้งหมดก่อนอ่าน วิธีที่สองจำเป็นเมื่อโจทย์ให้ตัวอักษรคั่นด้วยช่องว่าง เช่น # . . #
แบบที่ 2 ไม่เผื่อที่ให้ '\0'
แผนที่ 3 × 4 ประกาศเป็น char map[3][4] พอดีเป๊ะ แล้วอ่านด้วย %s และพิมพ์ทีละแถวด้วย %s คอมไพล์ผ่านโดยไม่มีคำเตือน ใช้ข้อมูลเข้าเดียวกับแบบที่ 1 บนเครื่องที่เขียนเอกสารนี้ได้
#..#.##.#..#
.##.#..#
#..#
แถวละ 4 ตัวพอดีกับที่จอง '\0' ของแถว 0 จึงล้นไปลงช่องแรกของแถว 1 แล้วถูกตัวอักษรของแถว 1 เขียนทับตอนอ่านแถวถัดไป ตอนพิมพ์ %s จึงหา '\0' ไม่เจอจนสุดแถว และพิมพ์เลยไปถึงแถวอื่นด้วย ส่วน '\0' ของแถวสุดท้ายล้นออกนอกอาเรย์ไปเลย ซึ่งอาจไปทับตัวแปรอื่น ประกาศ C + 1 ทุกครั้งที่เก็บแถวเป็นข้อความ
สรุป
- แผนที่ R × C เก็บใน
char map[R][C + 1]เผื่อช่องให้'\0' - อ่านทีละแถวด้วย
scanf("%s", map[r])ไม่ต้องมี& map[r]คือทั้งแถวเป็นข้อความmap[r][c]คือตัวอักษรหนึ่งตัว เทียบด้วย'#'- ตัวเลขหลักเดียวติดกันให้อ่านเป็นข้อความ แล้วแปลงด้วย
- '0' %cอ่าน'\n'ด้วย ถ้าจะอ่านทีละตัวให้ใช้" %c"