Разбор само на числа от 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
Хм, да, прав си, предполагам, че няма нищо лошо в това, освен че ми е неудобно да използвам локали, тъй като не съм запознат с тях. Причината да попитам е, че съм начинаещ в C++, така че все още не мога да се справя с прости задачи като тези (просто е за моите цели, тъй като не е необходимо да проверявам нищо). Скоро ще разгледам локалите и imbue(), за да видя дали са достатъчно лесни за разбиране! - person Arvin; 19.08.2011

опитайте се да прочетете число. ако това не успее, изчистете състоянието на грешката и опитайте да прочетете char (и да го игнорирате). повторете тези две стъпки, докато четенето на char не успее, в който случай сте на EOF или истински отказ.

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

но не мисля, че си струва.

наздраве & hth.,

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