Страницы

Поиск по вопросам

среда, 26 февраля 2020 г.

Функция для создания массивов разных размерностей с разным числом элементов

#c #массивы


Нужно на C написать более-менее универсальную функцию, которая позволяет создавать
массив произвольной (известной заранее) размерности с произвольным (известным заранее
числом элементов). Предполагается вот такая сигнатура (тип аргументов double и int
для длин): 

void grid_generator(double */*произвольное количество *, 1, 2, до 4*/ pgrid, const
int size_1, /* сколько-то размеров */, const double value /* инициализирующее значение */)


Как правильно это сделать, и вообще, можно ли?
    


Ответы

Ответ 1



Язык Си не предусматривает стандартных (синтаксических) средств для передачи массива с произвольным количеством размерностей в функцию и доступа там к его элементам способом, аналогичным тому, как мы обращаемся к массиву с известным числом размерностей. Поэтому, если все же хочется работать с такими массивами (разной размерности) в функции, то придется моделировать их, базируясь на доступе к элементам одномерного массива. Например, если у нас есть трехмерный массив double mdar[M][N][K], то для доступа к произвольному элементу мы пишем mdar[i][j][k]. Зная, что компилятор размещает в памяти все элементы такого массива последовательно, для моделирования доступа к тому же элементу через одномерный массив можно написать следующее: double *a = &mdar[0][0][0]; a[i * N * K + j * K + k]. Таким образом, в функцию, которая будет работать с массивом произвольной размерности нужно передать информацию о начале массива в памяти, количестве его размерностей, числе элементов в каждой размерности (на самом деле количество элементов "высшей размерности" не важно (см. a[i * N * K + j * K + k])) и массив индексов (размером в число размерностей многомерного массива). Содержимое этого массива является аналогом [i][j][k] в традиционной записи. Информацию, нужную для доступа к элементам многомерного массива (точнее всех массивов с одним и тем же количеством размерностей и одинаковым размером "низших размерностей") можно представить вот в такой структуре: struct nda_descr { size_t ndim; // количество размерностей массива size_t dim_size[]; // в реальной программе нужно обеспечить // массив размером `ndim - 1` в котором находятся // размеры (включающие размеры по размерностям следующих уровней) // по всем "низшим размерностям" }; (Если это непонятно (извините, согласен, сразу понять такой текст в комментарии сложно), то надеюсь, код функций nda_getix() и nda_fill_descr() прояснит ситуацию) Функция size_t nda_getix (struct nda_descr *d, size_t ijk[]) { size_t ix = 0; for (size_t i = 0; i < d->ndim - 1; i++) ix += d->dim_size[i] * ijk[i]; return ix + ijk[d->ndim - 1]; } для данного дескриптора многомерного массива и списка индексов по всем его размерностям (от "высшей" к "низшей") возвращает индекс для доступа к "одномерному аналогу". Для построения дескрипторов многомерных массивов, их создания в динамической памяти и заполнения можно предложить, например, следующий набор функций: // заполняет дескриптор массива информацией о его размерностях // поле .ndim дескриптора уже должно быть инициализировано // количеством размерностей struct nda_descr * nda_fill_descr (struct nda_descr *d, size_t dims[]) { size_t n_items = 1; for (ssize_t i = d->ndim - 1; i > 0; --i) { n_items *= dims[i]; d->dim_size[i - 1] = n_items; } return d; } // создает в динамической памяти дескриптор массива // размерности `ndim` с заданными в массиве `dims[]` // размерами (количеством элементов) по каждой размерности struct nda_descr * nda_create_descr (size_t ndim, size_t dims[]) { struct nda_descr *d = malloc(sizeof(*d) + (ndim - 1) * sizeof(size_t)); if (!d) return 0; d->ndim = ndim; return nda_fill_descr(d, dims); } // создает в динамической памяти многомерный массив // в соответствии с ранее заполненным дескриптором `d`, // размером `n` "высших размерностей" // для элементов размером `item_size` байт каждый // и копирует в него инициализирующие данные по указателю `data` // (если `data` == NULL, то инициализация не производится) void * nda_create_fill_array (struct nda_descr *d, size_t n, size_t item_size, void *data) { size_t n_items = d->dim_size[0] * n; void *array = malloc(n_items * item_size); return array ? (data ? memcpy(array, data, n_items * item_size) : array) : 0; } И на их основе написать вот такую, семантически эквивалентную функции в вопросе, функцию (вместо списка (типа va_list ) элементов инициализации я предлагаю передавать массив элементов). void * nda_create_fill_array_descr (struct nda_descr **pdscr, size_t ndim, size_t dims[], size_t item_size, void *fill_data) { if (!(*pdscr = nda_create_descr(ndim, dims))) return 0; void *array = nda_create_fill_array(*pdscr, dims[0], item_size, fill_data); if (!array) { free(*pdscr); *pdscr = 0; } return array; } и ряд вспомогательных, для удобства манипулирования такими массивами, скажем, для начала: size_t nda_items (struct nda_descr *d, size_t upper_dim_size) { return d->dim_size[0] * upper_dim_size; } Примеры использования: double mx[3][3][3] = { { {111., 112., 113.}, {121., 122., 123.}, {131., 132., 133.} }, { {211., 212., 213.}, {221., 222., 223.}, {231., 232., 233.} }, { {311., 312., 313.}, {321., 322., 323.}, {331., 332., 333.} } }; Это трехмерный массив, который используется для инициализации. Создаем трехмерный массив, который заполняем данными из mx struct nda_descr *a3d; double *a3 = nda_create_fill_array_descr(&a3d, 3, (size_t []){3, 3, 3}, sizeof(a3[0]), mx); Тело массива (данные) расположены по указателю a3, его дескриптор по указателю a3d. Теперь напечатаем количество элементов в созданном массиве и элемент a3[2][0][0] (понятно, что мы моделируем доступ к нему, прямо так написать синтаксически невозможно) вместе с индексом в a3[] по которому фактически происходит доступ: #define NDA_IJK (size_t []) printf("a3 n_items: %zd\n", nda_items(a3d, 3)); size_t ii = nda_getix(a3d, NDA_IJK{2, 0, 0}); printf("a3 [2][0][0] %f [%zd]\n", a3[ii], ii); Можно смоделировать трехмерный массив, аналогичный a[2][3][3], разместив в одной структуре (далее ее можно передавать в разные функции) дескриптор массива и указатель на данные, инициализировав их заданными непосредственно в списке аргументов величинами. struct { double *a32; size_t upper_dim_size; struct nda_descr d; size_t sz[2]; // обеспечим нужный для трехмерного массива размер поля .d.dim_size[] } adss; adss.d.ndim = 3; nda_fill_descr(&adss.d, NDA_IJK{2, 3, 3}); adss.a32 = nda_create_fill_array(&adss.d, adss.upper_dim_size = 2, sizeof(double), (double [][3][3]) { { {311., 312., 313.}, {321., 322., 323.}, {331., 332., 333.} }, { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }}); и напечатать элемент [1][1][1] (индексы в ранее заполненном массиве ijk[]), а также количество элементов (еще один пример, демонстрирующий использование не указателя на дескриптор, а самой структуры дескриптора) printf("a32 [1][1][1] %f\n", adss.a32[nda_getix(&adss.d, ijk)]); printf("a32 n_items: %zd\n", nda_items(&adss.d, adss.upper_dim_size));

Ответ 2



Как вариант: void FillArray(void *array, double value, size_t dim, ...) { va_list argumentList; va_start(argumentList, dim); /* Вычисляем количество элементов в массиве: */ size_t totalElementsCount = 1; for (size_t i = 0; i < dim; ++i) totalElementsCount *= va_arg(argumentList, size_t); /* Присваиваем каждому элементу значение аргумента value: */ double *arrayPointer = array; for (size_t i = 0; i < totalElementsCount; ++i) arrayPointer[i] = value; va_end(argumentList); } Предполагается что после параметра dim будут дано количество элементов в каждом измерении массива. Например: double a[3][4][5]; FillArray(a, 3.1415, 3, 3, 4, 5); Приведенный выше код заполняет уже существующий многомерный массив. Гораздо интереснее будет создать массив с нуля. Формально говоря, создать именно массив (в терминах C) невозможно. Создать получиться только т. н. разряжённый массив (англ. jagged array). Для начала попробуем сделать это вручную: const size_t dimensions[3] = {2, 2, 3}; const double value = 3.141593; double ***array = malloc(dimensions[0] * sizeof(*array)); for (size_t i = 0; i < dimensions[0]; ++i) { array[i] = malloc(dimensions[1] * sizeof(*array[i])); for (size_t j = 0; j < dimensions[1]; ++j) { array[i][j] = malloc(dimensions[2] * sizeof(*array[i][j])); for (size_t k = 0; k < dimensions[2]; ++k) array[i][j][k] = value; } } После недолгих раздумий можно прийти к такой функции: void *createAndFillDoubleArray(size_t n, const size_t dimensions[n], size_t dim, double value) { if (dim < n - 1) { void **array = malloc(dimensions[dim] * sizeof(double *)); for (size_t i = 0; i < dimensions[dim]; ++i) array[i] = createAndFillDoubleArray(n, dimensions, dim + 1, value); return array; } else { double *array = malloc(dimensions[dim] * sizeof(double)); for (size_t i = 0; i < dimensions[dim]; ++i) array[i] = value; return array; } } Использовать которую можно так: double ***array_1 = createAndFillArray(3, (size_t []) {3, 3, 4}, 0, 3.141593); double **array_2 = createAndFillArray(2, (size_t []) {1, 4}, 0, 3.141593); Параметр dim в данном случае служебный и используется в качестве счетчика в рекурсии. Не очень красиво, но над функцией все-равно еще нужно подумать. Имеет смысл отделить создание массива от его заполнения и реализовать функцию освобождения выделенной памяти. Надеюсь, память нигде не переинтерпретируется. Можно попробовать код в Ideone.

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

Отправить комментарий