Используя C, преобразуйте динамически размещенный массив int в строку, разделенную запятыми, насколько это возможно - PullRequest
7 голосов
/ 17 ноября 2009

Я гораздо менее опытен в C, чем в языках более высокого уровня. В Cisco мы используем C, и я иногда сталкиваюсь с чем-то, что было бы легко сделать в Java или Python, но очень трудно сделать в C. Сейчас один из таких случаев.

У меня есть динамически распределяемый массив целых чисел без знака, которые мне нужно преобразовать в разделенную запятыми строку для регистрации. Хотя целые числа вряд ли будут очень большими, концептуально они могут быть в диапазоне от 0 до 4 294 967 295. В Python это одна короткая строка.

my_str = ','.join(my_list)

Насколько элегантно люди могут делать это в Си? Я придумал способ, но он грубый. Если кто-нибудь знает хороший способ сделать это, пожалуйста, просветите меня.

Ответы [ 13 ]

9 голосов
/ 17 ноября 2009

Код теперь протестирован и собирается под gcc.

В отличие от других ответов, не обязывает C99.

Настоящая проблема здесь заключается в том, что вы не знаете длины строки, которая вам понадобится. Получить число так же просто, как и sprintf("%u", *num), используя num для обхода массива int с, но сколько места вам понадобится? Чтобы избежать переполнения буфера, вы должны отслеживать много целых чисел.

size_t join_integers(const unsigned int *num, size_t num_len, char *buf, size_t buf_len) {
    size_t i;
    unsigned int written = 0;

    for(i = 0; i < num_len; i++) {
        written += snprintf(buf + written, buf_len - written, (i != 0 ? ", %u" : "%u"),
            *(num + i));
        if(written == buf_len)
            break;
    }

    return written;
}

Обратите внимание, что я отслеживаю, сколько буфера я использовал, и использую snprintf, поэтому я не пересекаю конец. snprintf будет использовать \0, но, поскольку я использую buf + written, я начну с \0 предыдущего snprintf.

Используется:

int main() {
    size_t foo;
    char buf[512];

    unsigned int numbers[] = { 10, 20, 30, 40, 1024 };

    foo = join_integers(numbers, 5, buf, 512);
    printf("returned %u\n", foo);
    printf("numbers: %s\n", buf);
}

Выходы:

returned 20
numbers: 10, 20, 30, 40, 1024

Принудительная установка ограничения вместо превышения скорости:

char buf[15];    
foo = join_integers(numbers, 5, buf, 14);
buf[14] = '\0';

Выходы, как ожидается:

returned 14
numbers: 10, 20, 30, 4
2 голосов
/ 17 ноября 2009

На самом деле вы можете использовать библиотеку типа Glib , которая содержит такие функции, как

gchar * g_strjoin (const gchar * разделитель, ...);

Объединяет несколько строк в одну длинную строку с необязательным разделителем, вставленным между каждой из них. Возвращенная строка должна быть освобождена с помощью g_free ().

(вам все еще нужно использовать g_snprintf(), возможно, с g_printf_string_upper_bound(), чтобы обеспечить пространство)

2 голосов
/ 17 ноября 2009

Как насчет этого?

char *join_int_list(const unsigned int *list, size_t n_items)
{
     enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
     char *space = malloc(SIZEOF_INT_AS_STR * n_items);
     if (space != 0)
     {
         size_t i;
         char *pad = "";
         char *dst = space;
         char *end = space + SIZEOF_INT_AS_STR * n_items;
         for (i = 0; i < n_items; i++)
         {
              snprintf(dst, end - dst, "%s%u", pad, list[i]);
              pad = ",";
              dst += strlen(dst);
         }
         space = realloc(space, dst - space + 1);
     }
     return(space);
}

Ответственный за вызов должен освободить возвращенный указатель и убедиться, что он не равен нулю перед его использованием. Функция realloc () освобождает дополнительное пространство, если выделенное количество было слишком большим, чтобы оно того стоило. Этот код беспечно предполагает, что значения действительно являются 32-разрядными целыми числами без знака; если они могут быть больше, тогда enum нуждается в соответствующей корректировке.

Проверенный код:

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

char *join_int_list(const unsigned int *list, size_t n_items)
{
    enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
    char *space = malloc(SIZEOF_INT_AS_STR * n_items);
    if (space != 0)
    {
        size_t i;
        char *pad = "";
        char *dst = space;
        char *end = space + SIZEOF_INT_AS_STR * n_items;
        for (i = 0; i < n_items; i++)
        {
            snprintf(dst, end - dst, "%s%u", pad, list[i]);
            pad = ",";
            dst += strlen(dst);
        }
        space = realloc(space, dst - space + 1);
    }
    return(space);
}

int main(void)
{
    static unsigned int array[]= { 1, 2, 3, 49, 4294967295U, 0, 332233 };
    char *str = join_int_list(array, sizeof(array)/sizeof(array[0]));
    printf("join: %s\n", str);
    free(str);
    return(0);
}

Проверено с помощью valgrind - похоже, все в порядке.


Обсуждение преобразования INT_MAX или UINT_MAX в строку:

Вы можете использовать sizeof ("," STRINGIZE (INT_MAX)) вместо жесткого кодирования. Макрос stringize - это распространенный инструмент cpp, который можно определить как #define STRINGIZE_ (v) #v и #define STRINGIZE (v) STRINGIZE_ (v). - Р. Пэйт

@ R Пэйт: хорошая идея - да, вы могли бы сделать это довольно эффективно. На самом деле, здесь есть две интересные идеи: использование конкатенации строк с помощью sizeof () (скобки необходимы для ясности - но конкатенация строк происходит достаточно рано, чтобы компилятор не беспокоился) и использование операции stringize в INT_MAX , - Джонатан Леффлер

Использование операции stringize в INT_MAX не очень хорошая идея - это просто должно быть «константное выражение», а не обязательно последовательность цифр. Он может быть определен как ((1 << 32) -1) или даже что-то странное, например __int_max, если компилятор позволяет использовать его везде, где вы можете использовать константное выражение. - кафе </p>

И @caf прав. Считайте этот код:

#include <limits.h>
#include <stdio.h>

#undef INT_MAX
#define INT_MAX (INT_MIN-1 - 100 + 100)

#define QUOTER(x)   #x
#define STRINGIZER(x)   QUOTER(x)

enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
enum { SIZEOF_INT_AS_STR_1 = sizeof(STRINGIZER(INT_MAX) ",")-1 };

int main(void)
{
    printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1);
    printf("INT_MAX  = %d\n", INT_MAX);
    printf("UINT_MAX = %u\n", UINT_MAX);
    return(0);
}

Это даже не компилируется на MacOS X 10.5.8 с GCC 4.0.1 - потому что идентификатор INT_MAX не определен. Предварительная версия кода, который не печатал INT_MAX или UINT_MAX, сработала; оно показало, что значение SIZEOF_INT_AS_STR_1 было 31, поэтому @caf был верен. Добавление двойной проверки значений INT_MAX и UINT_MAX затем не удалось скомпилировать, что меня удивило. Взгляд на вывод из gcc -E показывает, почему:

enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
enum { SIZEOF_INT_AS_STR_1 = sizeof("((-INT_MAX - 1)-1 - 100 + 100)" ",")-1 };

int main(void)
{
 printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1);
 printf("INT_MAX  = %d\n", ((-INT_MAX - 1)-1 - 100 + 100));
 printf("UINT_MAX = %u\n", (((-INT_MAX - 1)-1 - 100 + 100) * 2U + 1U));
 return(0);
}

Как и предполагалось, строка для SIZEOF_IN_AS_STR_1 вообще не является цифрой. Препроцессор может вычислить выражение (столько, сколько ему нужно), но не должен создавать цифровую строку.

Расширение INT_MAX оказывается в терминах INT_MIN, а INT_MIN, в свою очередь, определяется в терминах INT_MAX, поэтому, когда переписанный макрос INT_MAX оценивается, ' рекурсивное расширение »предотвращается правилами работы препроцессора C, и INT_MAX появляется в предварительно обработанном выводе - до путаницы всех.

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

1 голос
/ 17 ноября 2009

Ребята, вам платят по линии?: -)


f () объявлен с параметром char * для целей прототипирования, просто измените char -> int.Я интерпретировал этот вопрос как требование строки в качестве вывода, а не просто кода для записи в файл.

#define PRINT(s, l, x, i) snprintf((s), (l), "%s%d", (i) ? ",":"", (x)[i]);

char *f(size_t len, char *x) {
  size_t  i, j = 0, k;

  for(i = 0; i < len; ++i)
      j += PRINT(NULL, 0, x, i);
  char *result = malloc(k = ++j);
  for(*result = i = j = 0; i < len; ++i)
      j += PRINT(result + j, k - j, x, i);
  return result;
}

Вот тестовая структура:

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

// put f() here

int main(int ac, char **av) {
    for(int i = 1; i < ac; ++i) { 
        printf("%s\n", f(strlen(av[i]), av[i]));
    }
    return 0;
}
1 голос
/ 17 ноября 2009

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

:)

#include <stdio.h>

char* toStr(int arr[], unsigned int arrSize, char buff[])
{
    if (arr && arrSize && buff)
    {
        int* currInt = arr;
        char* currStr = buff;
        while (currInt < (arr + arrSize))
        {
            currStr += sprintf(currStr, "%d,", *currInt++);
        }
        *--currStr = '\0';
    }
    return buff;
}

int main()
{
    int arr[] = {1234, 421, -125, 15251, 15251, 52};
    char buff[1000];

    printf("Arr is:%s\n", toStr(arr, 6, buff));    
}

Предполагается, что бафф достаточно большой, чтобы выделить его (длина наибольшего целого + 2) * arrSize). Вдохновил мой memcpy:)

Редактировать Я понял, что раньше у меня был брэйнфарт, и я мог просто увеличить значение, возвращаемое sprintf, вместо того, чтобы хранить temp. Очевидно, что другие ответы также делают это, отредактировав мой ответ, чтобы удалить 2 ненужные строки.

Edit2 Похоже, wrang-wrang избил меня до удара! Его ответ в значительной степени идентичен моему и был представлен ранее. Я смиренно предлагаю дать ему +1.

1 голос
/ 17 ноября 2009
#include <stdio.h>
#include <stdlib.h>

/* My approach is to count the length of the string required. And do a single alloc.
     Sure you can allocate more, but I don't know for how long this data will be retained.
*/ 

#define LEN(a) (sizeof a / sizeof *a)

int main(void) {

    unsigned a[] = {1, 23, 45, 523, 544};
    int i, str_len=0, t_written=0;
    char tmp[11]; /* enough to fit the biggest unsigned int */

    for(i = 0; i < LEN(a); i++) 
        str_len += sprintf(tmp, "%d", a[i]);

    /* total: we need LEN(a) - 1 more for the ',' and + 1 for '\0' */
    str_len += LEN(a);
    char *str = malloc(str_len); 
    if (!str) 
        return 0;

    if (LEN(a) > 1) {
        t_written += sprintf(str+t_written, "%d", a[0]);
        for(i = 1; i < LEN(a); i++)
            t_written += sprintf(str+t_written, ",%d", a[i]);
    } else if (LEN(a) == 1) 
        t_written += sprintf(str+t_written, "%d", a[0]);

    printf("%s\n", str);

    free(str);
    return 0;
}
1 голос
/ 17 ноября 2009
unsigned *a; /* your input a[N] */
unsigned i,N;
char *b,*m;
b=m=malloc(1+N*11); /* for each of N numbers: 10 digits plus comma (plus end of string) */
for (i=0;i<N;++i)
  b+=sprintf(b,"%u,",a[i]);
if (N>0) b[-1]=0; /* delete last trailing comma */
/* now use m */
free(m);

довольно, верно? :)

1 голос
/ 17 ноября 2009
char buf [11 * sizeof (my_list)];
for (int n = 0, int j = 0;  j < sizeof (my_list) / sizeof (my_list [0]);  ++j)
    n += sprintf (&buf [n], "%s%u",   (j > 0) ? "," : "",  my_list [j]);
0 голосов
/ 17 ноября 2009
void join(int arr[], int len, char* sep, char* result){
    if(len==0){
        *result='\0';
    } else {
        itoa(arr[0],result,10);
        if(len > 1){
            strcat(result,sep);
            join(arr+1,len-1,sep,result+strlen(result));
        }
    }
}
0 голосов
/ 17 ноября 2009

Если вы хотите файл, ответ Стивена Шланскера в порядке.

Однако, если вы хотите поместить это в строку, все усложняется. Вы можете использовать sprintf, но вам нужно быть осторожным, чтобы в вашей строке не осталось места. Если у вас есть совместимый с C99 snprintf (Linux, BSD, а не Windows), следующий (непроверенный, некомпилированный) код должен работать:

char *buf = malloc(1024); /* start with 1024 chars */
size_t len = 1024;
int pos = 0;
int rv;
int i;
for (i = 0; i < n; i++) {
    rv = snprintf(buf+pos, len - pos, "%s%d", i = 0 ? "" : ",", my_list[i]);
    if (rv < len - pos) {
        /* it fit */
        pos += rv;
    } else {
        len *= 2;
        buf = realloc(buf, len);
        if (!buf) abort();
        i--; /* decrement i to repeat the last iteration of the loop */
    }
}
return buf;

После этого звонящий должен освободить buf.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...