Развернуть вкладки до пробелов в C? - PullRequest
2 голосов
/ 20 сентября 2009

Мне нужно развернуть вкладки во входной строке, чтобы они были пробелами (шириной 8 столбцов). Я попробовал это с предыдущим кодом, который я заменял последним пробелом в каждой строке больше 10 символов на '\ n', чтобы создать новую строку. Есть ли способ в C сделать вкладки на 8 пробелов для их расширения? Я имею в виду, я уверен, что это просто, я просто не могу этого понять.

Вот мой код:

int v = 0;
int w = 0;
int tab;
extern char line[];

while (v < length) {

   if(line[v] == '\t')
      tab = v;

   if (w == MAXCHARS) {
      // THIS IS WHERE I GET STUCK
      line[tab] = ' ';
      // set y to 0, so loop starts over
      w = 0;
   }
   ++v;
   ++w;
}

Ответы [ 7 ]

4 голосов
/ 20 сентября 2009

Вам нужен отдельный буфер для записи вывода, так как он обычно будет длиннее ввода:

void detab(char* in, char* out, size_t max_len) {
    size_t i = 0;
    while (*in && i < max_len - 1) {
        if (*in == '\t') {
            in++;
            out[i++] = ' ';
            while (i % 8 && i < max_len - 1) {
                out[i++] = ' ';
            }
        } else {
            out[i++] = *in++;
        }
    }

    out[i] = 0;
}

Вы должны предварительно выделить достаточно места для out (в худшем случае это может быть 8 * strlen(in) + 1), и out не может совпадать с in.

РЕДАКТИРОВАТЬ: Как предположил Джонатан Леффлер, параметр max_len теперь гарантирует, что мы избежим переполнения буфера. Результирующая строка всегда будет заканчиваться нулем, даже если она обрезается во избежание такого переполнения. (Я также переименовал функцию и изменил int на size_t для дополнительной корректности:).)

4 голосов
/ 20 сентября 2009

Это не совсем вопрос о языке Си; вопрос в поиске правильного алгоритма - вы можете использовать этот алгоритм на любом языке.

Во всяком случае, вы не можете сделать это вообще без перераспределения line[], чтобы он указывал на больший буфер (если это не большая фиксированная длина, в этом случае вам нужно беспокоиться о переполнениях); когда вы расширяете вкладки, вам нужно больше памяти для хранения новых, более крупных строк, поэтому замена символов, которую вы пытаетесь сделать, просто не сработает.

Мое предложение: вместо того, чтобы пытаться работать на месте (или пытаться работать в памяти, даже), я бы предложил написать это как фильтр - чтение из стандартного ввода и запись в стандартный вывод по одному символу за раз; Таким образом, вам не нужно беспокоиться о выделении или освобождении памяти или изменении длины строки [].

Если контекст, в котором этот код используется в , требует, чтобы он работал в памяти, рассмотрите реализацию API, аналогичную realloc(), в которой вы возвращаете новый указатель; если вам не нужно изменять длину обрабатываемой строки, вы можете просто сохранить исходную область памяти, но если вам do необходимо изменить ее размер, опция доступна.

2 голосов
/ 21 сентября 2009

Вот реентерабельная рекурсивная версия, которая автоматически выделяет буфер правильного размера:

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

struct state
{
    char *dest;
    const char *src;
    size_t tab_size;
    size_t size;
    _Bool expand;
};

static void recexp(struct state *state, size_t di, size_t si)
{
    size_t start = si;
    size_t pos = si;

    for(; state->src[pos]; ++pos)
    {
        if(state->src[pos] == '\n') start = pos + 1;
        else if(state->src[pos] == '\t')
        {
            size_t str_len = pos - si;
            size_t tab_len = state->tab_size - (pos - start) % state->tab_size;

            recexp(state, di + str_len + tab_len, pos + 1);
            if(state->dest)
            {
                memcpy(state->dest + di, state->src + si, str_len);
                memset(state->dest + di + str_len, ' ', tab_len);
            }

            return;
        }
    }

    state->size = di + pos - si + 1;
    if(state->expand && !state->dest) state->dest = malloc(state->size);
    if(state->dest)
    {
        memcpy(state->dest + di, state->src + si, pos - si);
        state->dest[state->size - 1] = 0;
    }
}

size_t expand_tabs(char **dest, const char *src, size_t tab_size)
{
    struct state state = { dest ? *dest : NULL, src, tab_size, 0, dest };
    recexp(&state, 0, 0);
    if(dest) *dest = state.dest;
    return state.size;
}

int main(void)
{
    char *expansion = NULL; // must be `NULL` for automatic allocation
    size_t size = expand_tabs(&expansion,
        "spam\teggs\tfoo\tbar\nfoobar\tquux", 4);
    printf("expanded size: %lu\n", (unsigned long)size);
    puts(expansion);
}

Если expand_tabs() вызывается с dest == NULL, функция вернет размер развернутой строки, но расширение фактически не выполняется; если dest != NULL, но *dest == NULL, буфер правильного размера будет выделен и должен быть освобожден программистом; если dest != NULL и *dest != NULL, расширенная строка будет помещена в *dest, поэтому убедитесь, что предоставленный буфер достаточно большой.

2 голосов
/ 20 сентября 2009

Я бы, наверное, сделал что-то вроде этого:

  1. Выполните итерацию по строке один раз, считая только вкладки (и длину строки, если вы этого еще не знаете).
  2. Выделить original_size + 7 * number_of_tabs байтов памяти (где original_size считает нулевой байт).
  3. Итерация по строке в другой раз, копирование каждого байта без табуляции в новую память и вставка 8 пробелов для каждой табуляции.

Если вы хотите сделать замену на месте вместо создания новой строки, вам нужно убедиться, что переданный указатель указывает на место с достаточным объемом памяти для хранения новой строки (которая будет длиннее чем оригинал, потому что 8 пробелов или 7 байтов больше, чем одна вкладка).

1 голос
/ 20 сентября 2009

Не проверено, но что-то вроде этого должно работать:

int v = 0;
int tab;
extern char line[];

while (v < length){
  if (line[v] == '\t') {
    tab = (v % TAB_WIDTH) || TAB_WIDTH;
    /* I'm assuming MAXCHARS is the size of your array. You either need
     * to bail, or resize the array if the expanding the tab would make
     * the string too long. */
    assert((length + tab) < MAXCHARS);
    if (tab != 1) {
      memmove(line + v + tab - 1, line + v, length - v + 1);
    }
    memset(line + v, ' ', tab);
    length += tab - 1;
    v += tab;
  } else {
    ++v;
  }
}

Обратите внимание, что это O (n * m), где n - размер строки, а m - количество вкладок. Это, вероятно, не проблема на практике.

0 голосов
/ 21 сентября 2009

Вот тот, который malloc(3) увеличит буфер точно нужного размера и вернет расширенную строку Это не деление или модуль операций. Это даже идет с тестовым водителем. Безопасно с -Wall -Wno-скобками, если используется gcc.

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

static char *expand_tabs(const char *s) {
  int i, j, extra_space;
  char *r, *result = NULL;

  for(i = 0; i < 2; ++i) {
    for(j = extra_space = 0; s[j]; ++j) {
      if (s[j] == '\t') {
        int es0 = 8 - (j + extra_space & 7);
        if (result != NULL) {
          strncpy(r, "        ", es0);
          r += es0;
        }
        extra_space += es0 - 1;
      } else if (result != NULL)
        *r++ = s[j];
    }
    if (result == NULL)
      if ((r = result = malloc(j + extra_space + 1)) == NULL)
        return NULL;
  }
  *r = 0;
  return result;
}

#include <stdio.h>

int main(int ac, char **av) {
  char space[1000];
  while (fgets(space, sizeof space, stdin) != NULL) {
    char *s = expand_tabs(space);
    fputs(s, stdout);
    free(s);
  }
  return 0;
}
0 голосов
/ 20 сентября 2009

Существует множество способов преобразования вкладок в строке в 1-8 пробелов. Существуют неэффективные способы сделать раскрытие на месте, но самый простой способ справиться с ним - это иметь функцию, которая принимает входную строку, и отдельный буфер вывода, который достаточно велик для расширенной строки. Если входные данные состоят из 6 вкладок, плюс X и новая строка (8 символов + завершающий ноль), выходные данные будут состоять из 48 пробелов, X и новой строки (50 символов + завершающий ноль) - поэтому вам может потребоваться гораздо больший выходной буфер, чем входной буфер.

#include <stddef.h>
#include <assert.h>

static int detab(const char *str, char *buffer, size_t buflen)
{
    char *end = buffer + buflen;
    char *dst = buffer;
    const char *src = str;
    char c;

    assert(buflen > 0);
    while ((c = *src++) != '\0' && dst < end)
    {
         if (c != '\t')
             *dst++ = c;
         else
         {
             do
             {
                 *dst++ = ' ';
             } while (dst < end && (dst - buffer) % 8 != 0);
         }
    }
    if (dst < end)
    {
        *dst = '\0';
        return(dst - buffer);
    }
    else
        return -1;
}

#ifdef TEST
#include <stdio.h>
#include <string.h>

#ifndef TEST_INPUT_BUFFERSIZE
#define TEST_INPUT_BUFFERSIZE 4096
#endif /* TEST_INPUT_BUFFERSIZE */
#ifndef TEST_OUTPUT_BUFFERSIZE
#define TEST_OUTPUT_BUFFERSIZE (8 * TEST_INPUT_BUFFERSIZE)
#endif /* TEST_OUTPUT_BUFFERSIZE */

int main(void)
{
     char ibuff[TEST_INPUT_BUFFERSIZE];
     char obuff[TEST_OUTPUT_BUFFERSIZE];

     while (fgets(ibuff, sizeof(ibuff), stdin) != 0)
     {
          if (detab(ibuff, obuff, sizeof(obuff)) >= 0)
              fputs(obuff, stdout);
          else
              fprintf(stderr, "Failed to detab input line: <<%.*s>>\n",
                      (int)(strlen(ibuff) - 1), ibuff);
     }
     return(0);
 }
 #endif /* TEST */

Самая большая проблема этого теста заключается в том, что трудно доказать, что он правильно обрабатывает переполнения в выходном буфере. Вот почему есть две последовательности «#define» для размеров буфера - с очень большими значениями по умолчанию для реальной работы и независимо настраиваемыми размерами буфера для стресс-тестирования. Если исходный файл dt.c, используйте компиляцию, подобную этой:

 make CFLAGS="-DTEST -DTEST_INPUT_BUFFERSIZE=32 -DTEST_OUTPUT_BUFFERSIZE=32" dt

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

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