Попытка найти элегантный способ поиска смежного элемента в 2d-массиве в нескольких направлениях

#c #multidimensional-array

#c #многомерный массив

Вопрос:

Я хочу найти соседний элемент, который окружает p . приведенная ниже программа не выводит соседей

Я знаю, что мы можем использовать подход грубой силы, например:

 array[i-1][i]
array[i-1][i-1]
array[i][i-1]
array[i 1][i]
 

и так далее…
Но я беспокоюсь, что было бы утомительно проверять все возможные места этого элемента p . Я пытаюсь найти элегантный способ сделать это.

 #include <stdio.h>
#include <stdlib.h>

void draw_board();
int row_number, column_number;
char mark_board(char mark, int row_num, int column_num);
char find_neighborhood(char board[6][6], int i, int j);

char board[6][6] = {
    { '0','0','0','0','0','0' },
    { '0','1','2','3','0','0' },
    { '0','4','P','5','0','0' },
    { '0','6','7','8','0','0' },
    { '0','0','0','0','0','0' },
    { '0','0','0','0','0','0' }
};

int main() {
    draw_board();

    find_neighborhood(board[6][6], 3, 3); // trying to find neighbor of char p 
    
    return 0;
}

void draw_board() {
    printf("   1 2 3 4 5 6n");
    printf("   -----------n");
    for (int i = 0; i < 6; i  ) { // rows
        printf("%i| ", i   1);// row number to be printed
        for (int j = 0; j < 6; j  ) { // columns
            printf("%c ", board[i][j]);
        }
        printf("n");
    } 
}

char find_neighborhood(char board[6][6], int row_num, int col_num) {
    int rows = sizeof(board); // row limit 
    int columns = sizeof(board[0]) - 1; // column limit
  
    for (int j = row_num - 1; j <= row_num   1; j  ) {
        for (int i = col_num - 1; i <= col_num   1; i  ) {
            if (i >= 0 amp;amp; j >= 0 amp;amp; i < columns amp;amp; j < rows amp;amp; !(j == row_num amp;amp; i == col_num)) {
                printf("The neighbor of p is: %cn", board[j][i]);
            }
        }
    }
}
 

Комментарии:

1. Ничего плохого в утомительном. Программирование — это не литература. Просто зацикливайте строки и столбцы цикла и защищайте края. Иногда грубая сила не является неправильным способом.

Ответ №1:

В дополнение к хорошим замечаниям, сделанным @chqlie, у вас есть и другие области, которые создают значительные «анти-паттеры» или просто «кодовые запахи». Наиболее неприятным является ваше смешивание строк и столбцов на основе 1 с индексацией массива на основе 0. В C вся индексация основана на 0. Когда вы пишете функции, манипулирующие массивами, вся индексация должна быть основана на 0. Это не только упрощает отслеживание и поддержку вашего кода, но и устраняет риск случайной ошибки, связанной со смешиванием индексации на основе 1 и 0.

Если вам необходимо получить от пользователя координаты на основе 1, затем сопоставьте индексацию на основе 1 с индексацией на основе 0, прежде чем вызывать функцию управления массивом. Если вы не сделаете этого в момент ввода, тогда дайте понять любому, кто поддерживает ваш код, что происходят изменения в индексации. Подойдет простая функция с комментариями, например

 /* convert 1-based rows/cols to 0-based indexes */
int toindex (int rc)
{
    return rc ? rc - 1 : rc;
}
 

и вызовите:

     /* DANGER mixing 1-based amp; 0-based indexes !!! */
    find_neighborhood (board, toindex (3), toindex (3));
 

Не используйте MagicNumbers в своем коде. (например 6 ). Это ограничивает ваш код одним размером массивов, требующим выбора во всех объявлениях циклов и массивов и перекомпиляции вашего кода только для обработки изменения размера массива. Вместо:

 #define ROWS 6          /* if you need a constant, #define one (or more) */
#define COLS ROWS

void draw_board (const char board[ROWS][COLS]);
void find_neighborhood (const char board[ROWS][COLS], int row, int col);
 

Это также влияет на то, как вы записываете, что вы читаете, draw_board() например

 void draw_board(const char board[ROWS][COLS])
{
    fputs ("  ", stdout);               /* don't use MagicNumbers */
    for (int i = 0; i < ROWS; i  )      /* create your headings   */
        printf ("-", i   1);          /* from defined constants */
    fputs ("n   ", stdout);
    for (int i = 0; i < 2 * COLS - 1; i  )
        putchar ('-');
    putchar ('n');
    
    for (int i = 0; i < ROWS; i  ) {
        printf (" %d|", i);
        for (int j = 0; j < COLS; j  ) {
            printf (j ? " %c" : "%c", board[i][j]);
        }
        putchar ('n');
    } 
}
 

У вас есть еще одна тонкая проблема с вашим объявлением board as char board[6][6] , а затем с использованием const квалификатора в списках параметров вашей функции. Указатели на массивы с разными квалификаторами несовместимы в стандарте ISO C. C11 — 6.7.6.1 Деклараторы указателей (p2). Это результат преобразования массива / указателя в 2D-массиве, в результате чего получается указатель на массив фактического типа char (*)[6] . Попробуйте, включите полные предупреждения с -Wall -Wextra -pedantic помощью (или /W3 на VS)

Что касается «более элегантных» способов написания find_neighborhood() вложенных циклов и проверок границ по краям, то они так же хороши, как и любой другой подход. Вы начали в этом направлении, и, кроме написания набора if...else if...else if...else условных выражений, это, вероятно, хороший выбор. Устраняя вашу проблему на основе 1 / 0, ее можно записать как:

 /* all array manipulation functions should use 0-based indexes */
void find_neighborhood (const char board[ROWS][COLS], int row, int col)
{
    printf ("nThe neighbors of '%c' are:nn", board[row][col]);
    
    for (int i = row ? row - 1 : row; i <= (row < ROWS - 1 ? row   1 : row); i  ) {
        for (int j = col ? col - 1 : col; j <= (col < COLS - 1 ? col   1 : col); j  ) {
            if (i == row amp;amp; j == col)
                fputs ("  ", stdout);
            else
                printf (" %c", board[i][j]);
        }
        putchar ('n');
    }
}
 

Положив его в целом, вы бы:

 #include <stdio.h>

#define ROWS 6          /* if you need a constant, #define one (or more) */
#define COLS ROWS

void draw_board (const char board[ROWS][COLS]);
void find_neighborhood (const char board[ROWS][COLS], int row, int col);

/* convert 1-based rows/cols to 0-based indexes */
int toindex (int rc)
{
    return rc ? rc - 1 : rc;
}


int main() {
    
    const char board[ROWS][COLS] = {    /* avoid global variables   */
        { '0','0','0','0','0','0' },
        { '0','1','2','3','0','0' },    /* pointers to arrays with  */
        { '0','4','P','5','0','0' },    /* different qualifiers are */
        { '0','6','7','8','0','0' },    /* incompatible in ISO C    */
        { '0','0','0','0','0','0' },
        { '0','0','0','0','0','0' },
    };
    draw_board(board);
    
    /* DANGER mixing 1-based amp; 0-based indexes !!! */
    find_neighborhood (board, toindex (3), toindex (3));
}

void draw_board(const char board[ROWS][COLS])
{
    fputs ("  ", stdout);               /* don't use MagicNumbers */
    for (int i = 0; i < ROWS; i  )      /* create your headings   */
        printf ("-", i   1);          /* from defined constants */
    fputs ("n   ", stdout);
    for (int i = 0; i < 2 * COLS - 1; i  )
        putchar ('-');
    putchar ('n');
    
    for (int i = 0; i < ROWS; i  ) {
        printf (" %d|", i);
        for (int j = 0; j < COLS; j  ) {
            printf (j ? " %c" : "%c", board[i][j]);
        }
        putchar ('n');
    } 
}

/* all array manipulation functions should use 0-based indexes */
void find_neighborhood (const char board[ROWS][COLS], int row, int col)
{
    printf ("nThe neighbors of '%c' are:nn", board[row][col]);
    
    for (int i = row ? row - 1 : row; i <= (row < ROWS - 1 ? row   1 : row); i  ) {
        for (int j = col ? col - 1 : col; j <= (col < COLS - 1 ? col   1 : col); j  ) {
            if (i == row amp;amp; j == col)
                fputs ("  ", stdout);
            else
                printf (" %c", board[i][j]);
        }
        putchar ('n');
    }
}
 

Пример использования / вывода

 $ ./bin/board_neighbors
   1 2 3 4 5 6
   -----------
 0|0 0 0 0 0 0
 1|0 1 2 3 0 0
 2|0 4 P 5 0 0
 3|0 6 7 8 0 0
 4|0 0 0 0 0 0
 5|0 0 0 0 0 0

The neighbors of 'P' are:

 1 2 3
 4   5
 6 7 8
 

Когда вы ищете «более элегантный» способ сделать что-либо, в конце концов, вы получите достаточное количество встроенных мнений. Дайте мне знать, если у вас возникнут дополнительные вопросы.

Комментарии:

1. это довольно хороший способ реализовать то, что я новичок в языке C, у меня обязательно будут ошибки новичков и запахи кода.

2. Как включить полное предупреждение в коде Visual Studio? это то же самое, что и в Visual Studio community?

3. Это зависит от того, какой компилятор вы используете с Visual Studio code. Если вы используете обычный VS compile ( cl.exe ) , /W3 он включит практически все полезные предупреждения ( /W4 предоставляет дополнительные предупреждения, не относящиеся к коду). Если gcc / clang, то -Wall -Wextra -pedantic (и подумайте -Wshadow о том, чтобы перехватить затененные переменные). Для всех других компиляторов вам просто нужно проверить справочную страницу или справку, чтобы увидеть, какие параметры они используют. Все компиляторы будут иметь похожие флаги, которые позволяют вам включать предупреждения.

Ответ №2:

В коде есть несколько проблем:

  • find_neighborhood(board[6][6], 3, 3); неверно: вы должны написать это вместо:
       find_neighborhood(board, 3, 3);
     
  • в find_neighborhood этом случае определение int rows = sizeof(board); инициализируется rows размером указателя, а не количеством строк в матрице. Вы должны использовать явные константы или передавать измерения в качестве дополнительных аргументов.
  • find_neighborhood() выполняет множество тестов… Но в этом и заключается суть вопроса.
  • row_number column_number и board не должны быть глобальными переменными. Сбивает с толку то, что некоторые функции используют глобальные переменные, а другие принимают их в качестве аргументов (с тем же именем).

Вот модифицированная версия:

 void find_neighborhood(char board[6][6], int row, int col) {
    for (int j = max(0, row - 1); j <= min(row   1, 6); j  ) {
        for (int i = max(0, col - 1); i <= min(col   1, 6); i  ) {
            if (j != row || i != col) {
                printf("The neighbor of p is: %cn", board[j][i]);
            }
        }
    }
}
 

Существует элегантный способ решения вашей задачи: вы можете определить board как массив размером 8×8, где первая и последняя строки и столбцы всегда пусты. Активная часть платы имеет значения индекса в диапазоне 1..6 .

Вот модифицированная версия с таким подходом:

 #include <stdio.h>
#include <stdlib.h>

void draw_board(const char board[8][8]);
char mark_board(char board[8][8], char mark, int row, int col);
void find_neighborhood(char board[8][8], int row, int col);

int main() {
    int row_number, column_number;
    char board[8][8] = {
        { 0,  0,  0,  0,  0,  0,  0,  0 },
        { 0, '0','0','0','0','0','0', 0 },
        { 0, '0','1','2','3','0','0', 0 },
        { 0, '0','4','P','5','0','0', 0 },
        { 0, '0','6','7','8','0','0', 0 },
        { 0, '0','0','0','0','0','0', 0 },
        { 0, '0','0','0','0','0','0', 0 },
        { 0,  0,  0,  0,  0,  0,  0,  0 },
    };
    draw_board(board);

    find_neighborhood(board, 4, 4); // trying to find neighbors of char p 
    
    return 0;
}

void draw_board(const char board[8][8]) {
    printf("   1 2 3 4 5 6n");
    printf("   -----------n");
    for (int i = 1; i <= 6; i  ) { // rows
        printf("%i| ", i);  // row number to be printed
        for (int j = 1; j <= 6; j  ) { // columns
            printf("%c ", board[i][j]);
        }
        printf("n");
    } 
}

void find_neighborhood(char board[8][8], int row, int col) {
    char save = board[row][col];
    board[row][col] = 0;
    for (int j = row - 1; j <= row   1; j  ) {
        for (int i = col - 1; i <= col   1; i  ) {
            if (board[j][i] != 0) {
                printf("The neighbor of p is: %cn", board[j][i]);
            }
        }
    }
    board[row][col] = save;
}