[0]zero-index clytie0x/zero-index

7.3 ตารางตัวอักษร

โจทย์กริดใน TOI Zero ส่วนใหญ่ไม่ได้ให้ตัวเลข แต่ให้ แผนที่ ที่วาดด้วยตัวอักษร เช่น # คือกำแพงหรือต้นไม้ . คือทางว่าง S คือจุดเริ่ม หน้านี้จะเก็บแผนที่แบบนี้ไว้ในอาเรย์สองมิติของ char อ่านทีละแถวด้วย %s แล้วไล่ดูทีละช่อง

ข้อมูลเข้าที่เป็นแผนที่

รูปแบบที่เจอบ่อยที่สุด บรรทัดแรกคือ R กับ C จากนั้นอีก R บรรทัด บรรทัดละ C ตัวอักษรติดกันไม่มีช่องว่าง

4 6
#..#..
.##...
....#.
#....#

แต่ละบรรทัดคือข้อความหนึ่งข้อความแบบในบทที่ 6 ทั้งแผนที่จึงเป็นข้อความหลายข้อความเรียงกัน เก็บใน char map[R][C + 1] หนึ่งแถวของอาเรย์คือหนึ่งข้อความ ต้องจองเกินไปหนึ่งช่องสำหรับตัวปิดท้าย '\0' ที่ scanf เติมให้

# 0 . 1 . 2 # 3 \0 4 C = 4 ต้องจองอย่างน้อย 5 ช่อง

อ่านและนับในแผนที่

สวนแห่งหนึ่งวาดแผนที่ไว้ # คือต้นไม้ . คือพื้นว่าง ให้นับว่ามีต้นไม้กี่ต้น 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;
}
  1. 100 แถว แถวละ 101 ช่อง 100 ช่องสำหรับตัวอักษร อีก 1 ช่องสำหรับ '\0'
  2. map[r] ที่มีวงเล็บแค่ชุดเดียวคือทั้งแถวที่ r ซึ่งเป็นอาเรย์ของ char หนึ่งมิติ ใช้กับ %s ได้เหมือนข้อความในบทที่ 6 และไม่ต้องมี & เหมือนกัน
  3. 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"