Как читать пробелы с помощью scanf в c?

Проблема: мне нужно уметь определять, когда последовательно встречаются два пробела.

Я прочитал следующие вопросы:

как читать строку из файла с разделителями \ n

как читать scanf с пробелами

И мне известно о проблемах со сканированием: http://c-faq.com/stdio/scanfprobs.html

Ввод будет в следующем формате:

1 5 3 2  4 6 2  1 9  0

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

Хотя я могу использовать fgets и различные встроенные функции для решения этой проблемы, я нахожусь в той точке, где решение проблемы с помощью scanf на этом этапе, вероятно, будет проще. Однако, если это не так, использование fgets, strtok и atoi выполнит большую часть работы, но мне все равно нужно идентифицировать два пробела подряд.

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

while ( scanf ( "%d", &x ) == 1 )

Что мне нужно, так это читать пробелы, и если есть два последовательных пробела, я буду делать что-то другое со следующим набором данных.

И как только у меня появляется пробел, я не знаю, как сказать:

if ((input == "whitespace") && (previousInput == "whitespace"))
  ya da ya da
else (input == "whitespace")
  ya da ya da
else 
  ya da ya da

Я ценю ваше время и благодарю вас за вашу помощь.

Извлеченный урок: хотя решение для scanf опубликовано ниже Джонатаном Леффлером, решение было немного более простым с getc (поскольку требовалось менее глубокое знание внутреннего scanf, регулярных выражений и символов). В ретроспективе лучшее знание регулярных выражений, scanf и char упростили бы проблему и, конечно, зная, какие функции доступны и какую из них лучше всего использовать с самого начала.


person MykC    schedule 21.09.2010    source источник
comment
Это довольно ужасный формат ввода. Если вы отвечаете за это, измените его дизайн. Если, как я подозреваю, вам дали домашнее задание, невезение - это садисты, ваши учителя.   -  person Jonathan Leffler    schedule 22.09.2010
comment
Обратите внимание, что «пробел» отличается от «двух пробелов»; «пробел» обычно означает множество возможных символов, включая табуляцию и пробел (или пробел), а иногда и подачу формы, вертикальную табуляцию или новую строку; а иногда и backspace.   -  person Jonathan Leffler    schedule 22.09.2010
comment
@Jonathan Leffler: по крайней мере, он не пытается разбирать пробелы (compsoc.dur.ac.uk/whitespace < / а>)   -  person ninjalj    schedule 22.09.2010
comment
@ninjalj: Интересно! Вы, вероятно, знаете о предложениях Страуструпа в этой области! По крайней мере, этот вопрос касается только C, а не C ++.   -  person Jonathan Leffler    schedule 23.09.2010


Ответы (5)


getc и ungetc ваши друзья

#include <stdio.h>

int main(void) {
  int ch, spaces, x;
  while (1) {
    spaces = 0;
    while (((ch = getc(stdin)) != EOF) && (ch == ' ')) spaces++;
    if (ch == EOF) break;
    ungetc(ch, stdin);
    if (scanf("%d", &x) != 1) break;
    printf("%d was preceded by %d spaces\n", x, spaces);
  }
  return 0;
}

Демо на http://ideone.com/xipm1

Edit Rahhhhhhhhh ... Я загрузил это как C ++. Вот то же самое, но теперь C99 strict (http://ideone.com/mGeVk)

person pmg    schedule 21.09.2010
comment
scanf, sscanf, fscanf, fgets, gets, getc ... lol так много вариантов. Мне нужно будет прочитать о getc и ungetc. Спасибо за ответ. - person MykC; 22.09.2010
comment
+1, потому что getc() и ungetc() - лучший способ сделать это, чем пытаться использовать только scanf() - но это немного уклоняется от ответа на вопрос. - person Jonathan Leffler; 22.09.2010
comment
@MykC: Нет, НЕ получает! НИКОГДА НЕ ИСПОЛЬЗУЙТЕ, НИКОГДА - person pmg; 22.09.2010
comment
Да, плохо получается. Я не изучал это, но getc () отличается от get и неплохо? - person MykC; 22.09.2010
comment
getc это хорошо. Просто помните, что он возвращает int (не char), и вы не ошибетесь :) - person pmg; 22.09.2010
comment
@MykC: проблема с gets () в том, что это всегда уязвимость системы безопасности, использующая его с ненадежным вводом. - person ninjalj; 22.09.2010

while ( scanf ( "%c", &x ) == 1 )

Используя %c, вы можете читать символы пробела, вы должны только читать все данные и сохранять их в массиве. Затем выделите char* cptr и установите cptr в начало массива, затем вы проанализируете свой массив, и если вы хотите читать десятичные числа, вы можете просто использовать sscanf на cptr, в то время как вы хотите читать десятичные числа, но у вас должен быть указатель в хорошей позиции в массиве (на номер то, что вы хотите прочитать)

if (((*(cptr + 1)) == ' ') && ((*cptr)== ' '))
  ya da ya da
else ((*cptr)== ' '))
  ya da ya da
  sscanf(++cptr, "%d", &x);
else 
  ya da ya da
person Svisstack    schedule 21.09.2010
comment
Выглядит неплохо. По возможности я избегаю использования указателей и массивов. Примечание: я буду использовать указатели и массивы, когда это будет иметь смысл. - person MykC; 22.09.2010
comment
В чьих-то комментариях я заметил, что, похоже, если бы было одно или несколько пробелов, все они были бы сохранены в одном символе, чтобы остановить работу вашего вышеуказанного метода. - person MykC; 22.09.2010

Если вам действительно нужна функциональность типа scanf, вы можете использовать fgets и sscanf и использовать спецификатор %n, чтобы получить scanf, чтобы дать вашей программе смещения для начала и конца каждого промежутка пробелов, в то же время, когда она выполняет остальную работу.

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

person R.. GitHub STOP HELPING ICE    schedule 21.09.2010
comment
Это полезно, но в целом плохо. Если вы хотите добавить в программу ввод, эквивалентный отладочному оператору печати, то это прекрасно. Если вы хотите добавить простой ввод для тестовой или демонстрационной программы (где хорошие методы ввода - это не то, что вы демонстрируете), тогда это неплохо. Если вы хотите вводить производственный код, это действительно плохо. - person nategoose; 22.09.2010
comment
На самом деле есть одно применение для scanf: переносимая версия getline (или getdelim), включая чистую обработку встроенных символов NUL, может быть достигнута с помощью чего-то вроде scanf("%99[^\n]%n", buf, &cnt); (где 99 заменяется размером вашего буфера). - person R.. GitHub STOP HELPING ICE; 22.09.2010
comment
У scanf("%99[^\n]%n", buf, &cnt); есть проблема, заключающаяся в том, что он ничего не сохраняет в buf и cnt, если ввод начинается с '\n' и оставляет этот '\n' в стандартном вводе. Это не похоже на getline(). - person chux - Reinstate Monica; 03.02.2015
comment
@chux: чтобы использовать его для создания чего-то вроде getline, вам нужно обнаружить этот случай, и продолжать увеличивать буфер и повторять вызов, если он не может попасть на новую строку. fscanf - это всего лишь ингредиент, заставляющий его работать, а не все. - person R.. GitHub STOP HELPING ICE; 04.02.2015

Как вы определяете «пустое пространство»?

Честно говоря, я не думаю, что хотел бы попробовать использовать scanf() для определения двойных пробелов; почти любой другой метод был бы намного проще.

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

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

int main(void)
{
    int d;
    char sp[3] = "";
    int n;

    while ((n = scanf("%d%2[ \t]", &d, sp)) > 0)
    {
        printf("n = %d; d = %d; sp = <<%s>>", n, d, sp);
        if (n == 2 && strlen(sp) == 2)
            printf(" end of group");
        putchar('\n');
    }
    return 0;
}

Квадратные скобки заключают класс символов, а цифра 2 перед ним требует не более 2 символов из класса. Возможно, вам придется беспокоиться о том, что он читает новую строку и пытается получить больше данных для удовлетворения класса символов, что можно решить, удалив новую строку из класса символов. Но тогда это зависит от вашего определения пробела и от того, завершаются ли группы автоматически новой строкой или нет. Не помешает сбросить sp[0] = '\0'; в конце цикла.

Возможно, вам будет лучше перевернуть поля, чтобы обнаружить два пробела перед числом. Но в обычном случае это не сработает, поэтому вы должны вернуться к простому формату "%d" для чтения числа (и если это не удается, вы знаете, что у вас нет ни пробелов, ни числа - ошибка). Обратите внимание, что %d перебирает начальные пробелы (как определено стандартом) - все.

Чем больше я смотрю на это, тем меньше мне нравится только "scanf()". Напомните мне, пожалуйста, не ходить на занятия в вашем университете.

person Jonathan Leffler    schedule 21.09.2010
comment
Я считаю, что мне нужно только заботиться о том, чтобы пробел был одним пустым слотом символа или ''. Я не привязан к scanf, я привязан только к тому, чтобы сделать это самым простым способом, предполагая, что мне придется сделать это снова, а не просто выполнить работу. Просто хотел посмотреть, есть ли выражение регулярного выражения или трюк с scanf, который я, возможно, упустил из виду, который мог бы реально легко решить проблему, поскольку ввод сформирован. - person MykC; 22.09.2010
comment
Я смотрел ваш ответ, и мне кажется, что scanf в вашем примере всегда будет возвращать 2. В настоящее время я изучаю, какой диапазон значений может возвращать scanf и почему. - person MykC; 22.09.2010
comment
@MykC: scanf() возвращает количество успешных преобразований. %2[ \t\n] требует от одного до двух пробелов. Следовательно, в вашем коде он всегда может «работать», и вам нужно посмотреть, что находится в char sp[3];, чтобы увидеть, каков ваш разделитель. Фактически, в Unix, если кто-то наберет ^ D (EOF) на терминале, ваш ввод может увидеть «строку» без новой строки в конце и, следовательно, без белого символа, и, следовательно, вы получите возврат 1. Но , как я уже сказал, не думаю, что буду использовать scanf(); просто чертовски сложно заставить его работать так, как вы хотите. Вот почему я поддержал ответ @ pmg. - person Jonathan Leffler; 22.09.2010
comment
Да, в итоге я выбрал что-то похожее на то, что описано выше. scanf также брал один или несколько пробелов и сохранял их в символе, поэтому, если возникало '' (например, 10 пробелов), все это помещалось в один символ. Теперь я работаю в Visual Studio 2010, это то, что я вижу, когда смотрел переменную во время перехода. Также следующий оператор if ('' == '') вернет 1. Так что даже если бы я мог заставить scanf возвращать значение, которое я хотел, это не имело значения. Как я уже сказал выше, я отбросил scanf, потому что, очевидно, он не работал и заставить его работать не было моей целью. - person MykC; 22.09.2010
comment
Хорошо, я снова попытался реализовать ваш метод и смог получить результат, который будет работать. Итак, я не уверен, что делал раньше, но эта реализация работает. - person MykC; 22.09.2010

Вот решение, которое использует только функцию scanf (). В этом примере я использовал sscanf () примерно для тех же функций.

#include <stdio.h>


int p_1_cnt = 0, p_2_cnt = 0;

void process_1(int x)
{
    p_1_cnt++;
}


void process_2(int x)
{
    p_2_cnt++;
}


char * input_line = "1 5 3 2  4 6 2  1 9  0";

int main(void)
{
    char * ip = input_line;

    int x = 0, ws_0 = 0, ws_1 = 0, preceding_spaces = 1, fields = -2;

    while (sscanf (ip, "%d%n %n", &x, &ws_0, &ws_1) > 0)
    {
        ip += ws_0;

        if ((preceding_spaces) == 1)
            process_1(x);
        else
            process_2(x);

        preceding_spaces = ws_1 - ws_0;
    }

    printf("\np_1_cnt = %d, p_2_cnt = %d", p_1_cnt, p_2_cnt);
    _fgetchar();

    return 0;
}
person Indinfer    schedule 03.02.2015