Разбор только чисел из istream в C++

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

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

Мне нужно написать функцию, которая анализирует эти входные данные по одному числу за раз, поэтому мне нужно иметь возможность разделить ввод по числам, например: 8, затем 7, затем 15, затем 0, еще один 0 и так далее.

Единственный способ, о котором я до сих пор думал, - это использовать istream.get(), который возвращает код ASCII следующего символа, который я могу преобразовать обратно в его символьный формат, приведя его к char. Затем я бы проверил, был ли символ числом или нет (поэтому скобки игнорируются), но таким образом любые двузначные (или трехзначные) числа читаются только по одной цифре за раз.

Каков наилучший способ добиться этого?

Кстати, я должен использовать istream. Это часть спецификации, которую я не могу изменить.

Спасибо


person Arvin    schedule 19.08.2011    source источник
comment
Есть ли что-то неправильное в чтении двузначных или трехзначных чисел по одному символу за раз? Все, что вам нужно сделать, это умножить прочитанное число на 10, а затем добавить значение следующей цифры. Поместите это в цикл, и все готово.   -  person john    schedule 19.08.2011
comment
Спасибо, Джон, это в основном ручной способ сделать это, я надеялся, что где-то в STL найдется что-то, что поможет мне сделать это намного лучше!   -  person Arvin    schedule 19.08.2011
comment
Ну, вы могли бы возиться с istream::unget, который возвращает последний прочитанный символ в строку. Таким образом, вы можете удалить первую цифру, а затем использовать ››. Но, честно говоря, ручной способ - хороший способ.   -  person john    schedule 19.08.2011
comment
Или, поскольку ваш ввод кажется довольно регулярным, вы можете прочитать пунктуацию в фиктивных переменных. Что-то вроде in >> lparen >> num1 >> comma1 >> num2 >> comma2 >> num3 >> rparen;, где lparen и т. д. объявлены как char. Но такой код довольно ломкий, я бы сделал это вручную.   -  person john    schedule 19.08.2011


Ответы (4)


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

for (;;)
{
  int ch = in.get();
  if (ch == EOF)
    break;
  if (isdigit(ch))
  {
    int val = ch - '0';
    for (;;)
    {
      ch = in.get();
      if (!isdigit(ch))
        break;
      val *= 10;
      val += ch - '0';
    }
    // do something with val
  }
}

Это непроверенный код.

person john    schedule 19.08.2011
comment
Спасибо, Джон! Этот код выглядит немного лучше, чем решение Nawaz, я думаю, что мне это нравится немного больше, но мне придется протестировать обе версии, чтобы принять решение. - person Arvin; 19.08.2011
comment
На самом деле я думаю, что они очень похожи. Оба они по существу игнорируют пунктуацию. Решение, которое вы должны принять, заключается в том, собираетесь ли вы пытаться читать и проверять пунктуацию. Это явно сложнее. Я разместил приведенный выше код, потому что думал, что вы не знаете, как вычислить целочисленное значение, читая по одному символу за раз. Но теперь я вижу, что ошибался в этом. - person john; 19.08.2011

Это одно решение:

struct integer_only: std::ctype<char> 
{
    integer_only(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table()
    {
        static std::vector<std::ctype_base::mask> 
            rc(std::ctype<char>::table_size,std::ctype_base::space);

        std::fill(&rc['0'], &rc['9'+1], std::ctype_base::digit);
        return &rc[0];
    }
};

int main() {
        std::cin.imbue(std::locale(std::locale(), new integer_only()));
        std::istream_iterator<int> begin(std::cin);
        std::istream_iterator<int> end;
        std::vector<int> vints(begin, end);
        std::copy(vints.begin(), vints.end(), std::ostream_iterator<int>(std::cout, "\n"));
        return 0;
}

Вход:

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

Вывод:

8 7 15 0 0 1 0 3 2 0 6 3 1 0 4 1 1 5 

Онлайн-демонстрация: http://ideone.com/Lwx9y

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

 std::ifstream file("file.txt");
 file.imbue(std::locale(std::locale(), new integer_only()));
 std::istream_iterator<int> begin(file);
 std::istream_iterator<int> end;
 std::vector<int> vints(begin, end); //container of integers!

Здесь vints — это вектор, содержащий все целые числа. Вы хотели бы работать с vints, чтобы сделать что-то полезное. Кроме того, вы можете использовать его там, где ожидается int*, как:

void f(int *integers, size_t count) {}

f(&vints[0], vints.size()); //call a function which expects `int*`.

Аналогичный прием можно применить и при чтении из файла только слов. Вот пример:

person Nawaz    schedule 19.08.2011
comment
Этот код изменяет поток, так что он считает все нецифровые символы пробелами! Я не уверен, впечатлен я или нет. Мне кажется немного быстрым и грязным. - person john; 19.08.2011
comment
Спасибо за это! скоро проверю. Кстати, файл читается как стандартный ввод, поэтому открывать файл не нужно :) - person Arvin; 19.08.2011
comment
На самом деле я думаю, что согласен с Джоном в этом, я не уверен, нравится ли мне идея изменения потока, и необходимость объявлять структуру только для выполнения этой относительно простой задачи кажется немного излишним по сравнению с методом Джона! - person Arvin; 19.08.2011
comment
@Arvin: Что плохого в том, чтобы указать свою локаль для потока? Зачем вообще существует функция imbue(), если она не позволяет программистам менять локаль? Кроме того, мне интересно, если проблема была настолько simple task, то почему вы задали ее в первую очередь? :-/ - person Nawaz; 19.08.2011
comment
@Nawaz: Быстрый вопрос. Вы можете адаптировать приведенный выше код для обработки чисел со знаком? Предположительно, вы бы добавили '+' и '-' в свою таблицу как ctype_base::punct. Или код istream автоматически обрабатывает подписание? - person john; 19.08.2011
comment
Примечание. В некоторых системах, если вы наполняете файловый поток после его открытия, наполнение игнорируется. Таким образом, вы должны наполнить поток, прежде чем открывать его. - person Martin York; 19.08.2011
comment
@john: У этого решения, конечно, есть некоторые ограничения: оно будет работать только с неотрицательными целыми числами. - person Nawaz; 19.08.2011
comment
@Мартин: Да. Я знал это, но не упомянул в своем ответе, чтобы сделать решение простым, и потому что я еще не видел ни одной системы, в которой это могло бы дать сбой. - person Nawaz; 19.08.2011
comment
Хм, да, вы правы, я полагаю, в этом нет ничего плохого, кроме того, что мне неудобно использовать локали, поскольку я с ними не знаком. Причина, по которой я спрашиваю, заключалась в том, что я новичок в С++, поэтому я еще не могу справляться с простыми задачами, подобными этим (это просто для моих целей, поскольку мне не нужно ничего проверять). Вскоре я рассмотрю локали и imbue(), чтобы убедиться, что они достаточно просты для понимания! - person Arvin; 19.08.2011

попробуйте прочитать число. если это не удается, очистите состояние ошибки и попытайтесь прочитать char (и проигнорируйте его). повторяйте эти два шага до тех пор, пока не произойдет сбой чтения символа, и в этом случае вы находитесь в EOF или истинном сбое.

его можно оптимизировать, распознав ')', а затем прочитав до '('.

но думаю оно того не стоит.

ура и чт.,

person Cheers and hth. - Alf    schedule 19.08.2011

Другое решение:

#include <string>
#include <ostream>
#include <fstream>
#include <iostream>

struct triple
{
    long a;
    long b;
    long c;
};

std::ostream& operator << (std::ostream& os, const triple& value)
{
    return os << value.a << "/" << value.b << "/" << value.c;
}

int main()
{
    std::ifstream stream("Test.txt");
    if (!stream)
    {
        std::cout << "could not open the file" << std::endl;
    }

    std::string dummy;
    triple value;
    while (std::getline(stream, dummy, '(') >> value.a &&
           std::getline(stream, dummy, ',') >> value.b &&
           std::getline(stream, dummy, ',') >> value.c)
    {
        std::cout << value << std::endl;
    }
}
person Simon    schedule 19.08.2011