Реализация общей функции "map" над массивами в C

У меня возникают трудности с реализацией общей функции "map" над массивами. Я начал со следующего проекта:

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;
   void * temp = malloc(elem);

   for(i = 0; i<n, i++)
   {
      temp = (f)((char *) src) + i));
      for(j = 0; j < elem; j++)
      {
         *(((char *) dest) + i) = *(((char *) temp) + i);
      }
   }
   free(temp);
}

Я понимаю, почему это неверно. Я передаю его (char *) перед тем, как дать ему "f", но теперь я демонизирован и не могу найти решение. (Я делаю это в процессе обучения C)

Мое обоснование заключалось в том, чтобы получить результат "f" и, байт за байт, скопировать его в dest [i].

Можете ли вы дать мне подсказку?

Ответ 1

Ваша первая проблема заключается в том, что вы слишком много делаете в нескольких выражениях. Вам нужно сломать его.

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;
   void * temp = malloc(elem);

   char* csrc = (char*)src;
   char* cdest = (char*)dest;
   char* ctemp = (char*)temp;
   for(i = 0; i<n; i++)
   {
       csrc++;
       cdest++;
       ctemp++;
       temp = f(csrc);
       for(j = 0; j < elem; j++)
       {
           cdest[i] = ctemp[i];
       }
   }
   free(temp);
}

Теперь ваша вторая проблема. Вы malloc буфер, затем вы.. присваиваете этому указателю? Неоднократно? Тогда освободите только последний результат f-вызова? Это совершенно необязательно.

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;

   char* csrc = (char*)src;
   char* cdest = (char*)dest;
   for(i = 0; i<n; i++)
   {
       csrc++;
       cdest++;
       char* ctemp = (char*)f(csrc);
       for(j = 0; j < elem; j++)
       {
           cdest[i] = ctemp[i];
       }
   }
}

Теперь ваша третья проблема. Вы передаете указатель, но только до char. Вы не проходите в пустоте *. Это означает, что ваша функция не может быть общей - f не может быть применена ни к чему. Нам нужен массив void * s, так что функция может принимать любой тип в качестве аргумента. Нам также нужно взять размер типа в качестве аргумента, чтобы мы знали, как далеко двигаться по dest.

void MapArray(void ** src, void * dest, void * (f)(void *), size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        void* temp = f(src[n]);
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
    }
}

У нас еще есть другая проблема - память времен. Мы не освобождаем его. Мы также не передаем аргумент данных пользователя в f, что позволит ему возвращать выделенную кучу память, которую нам не нужно освобождать. Единственный способ, с помощью которого f может работать, - это вернуть статический буфер.

void MapArray(void ** src, void * dest, void * (f)(void *, void*), void* userdata, size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        void* temp = f(src[n], userdata);
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
    }
}

Теперь f может работать практически во всем, что ему нравится, и удерживать любое состояние, в котором оно нуждается. Но мы все равно не освобождаем буфер. Теперь f возвращает простую структуру, которая сообщает нам, нужно ли нам освобождать буфер. Это также позволяет нам освобождать или не освобождать буфер при разных вызовах f.

typedef struct {
    void* data;
    int free;
} freturn;

void MapArray(void ** src, void * dest, freturn (f)(void *, void*), void* userdata, size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        freturn thisreturn = f(src[n], userdata);
        void* temp = thisreturn.data;
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
        if (thisreturn.free)
            free(temp);
    }
}

Однако я все еще не понимаю цели этой функции. Все это заменит простой цикл? Код, который вы пытаетесь заменить, проще, чем код для вызова вашей функции и, вероятно, более эффективный и, безусловно, более мощный (например, они могут использовать continue/break).

Более того, C действительно отстой для такого рода работ. С++ намного лучше. Это довольно тривиально, чтобы применить функцию к каждому члену массива, например.

Ответ 2

Там есть причина, в которой нет ничего подобного в стандартной библиотеке C - это почти невозможно сделать так, чтобы вы делали это на C. Вы не можете скопировать результат "byte by byte" на dest[i] - так как вы 'cast dest до char *, он указывает только на один char (байт).

Предположительно, elem должен быть размером любого типа f, а n - количество элементов в src и dest. В этом случае ваш код не слишком далеко, но (как вы, по-видимому, предположили), как вы манипулируете указателями (особенно приведение к char *), не будет сокращать его.

Однако, если вы исправите это, у вас возникнет еще одна проблема: присвоение типа возврата из f, не зная, что тип будет действительно (действительно) сложным. Фактически, о единственном способе, о котором я могу думать, это превратить этот код в макрос:

#define MapArray(s, d, f, n) \
do {                         \
   unsigned i;               \
   for (i = 0; i<n; i++)     \
      d[i] = f(s[i]);        \
} while (0)

Вы бы использовали это примерно так:

int f(int a) { return a + 100; }

#define elements(array) (sizeof(array)/sizeof(array[0]))

int main() { 
    unsigned i;
    int x[] = { 0, 1, 2, 3};
    int y[elements(x)];

    MapArray(x, y, f, elements(x));

    for (i=0; i<elements(x); i++)
        printf("%d\n", y[i]);
    return 0;
}

Обратите внимание: я не рекомендую это. Это способ сделать то, о чем вас просят, но, как я уже сказал, это почти невозможно сделать хорошо в C. Этот код работает в некоторой степени, но IMO он не квалифицируется как хорошо работающий.

Ответ 3

  • Убедитесь, что функция f не возвращает указатель на локальную переменную
  • Я не уверен, что должен выполняться цикл j