Извлечь несколько слов в одну строковую переменную

std::stringstream convertor("Tom Scott 25");
std::string name;   
int age;

convertor >> name >> age;

if(convertor.fail())
{
    // it fails of course
}

Я хотел бы извлечь два или более слова в одну строковую переменную. Пока я читал, кажется, что это невозможно. Если да, то как еще это сделать? Я хотел бы, чтобы name получил все символы до числа (возраст).

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

Мне нужна возможность извлекать, например, все слова до age.


person kjagiello    schedule 22.01.2010    source источник


Ответы (8)


Большинство решений, опубликованных до сих пор, на самом деле не соответствуют спецификации - все данные до возраста рассматриваются как имя. Например, они потерпят неудачу с таким именем, как «Ричард Ван Де Ротстин».

Как заметил OP, с помощью scanf вы можете сделать что-то вроде: scanf("%[^0-9] %d", name, &age);, и он будет читать это очень хорошо. Предполагая, что это линейный ввод, я бы все равно сделал это:

std::string temp;
std::getline(infile, temp);

// technically "[^0-9]" isn't required to work right...
sscanf(temp.c_str(), "%[^0123456789] %d", name, &age);

К сожалению, iostreams не обеспечивает прямого аналога такого преобразования набора сканов — getline может читать до разделителя, но вы можете указать только один символ в качестве разделителя. Если вы действительно не можете использовать scanf и компанию, следующей остановкой будет либо кодирование вручную (начало эпохи будет temp.find_first_of("0123456789");), либо использование пакета RE (TR1, если ваш компилятор предоставляет его, в противном случае, вероятно, Boost).

person Jerry Coffin    schedule 22.01.2010
comment
Я уже говорил, что не могу использовать sscanf, потому что он не поддерживает std::string, до сих пор я все равно читал. Таким образом, в sscanf(temp.c_str(), "%[^0123456789] %d", name, &age); переменная name не может быть std::string. - person kjagiello; 22.01.2010
comment
@Balon: это правильно - вам нужно выделить временный буфер, прочитать его, а затем создать строку из буфера. В качестве альтернативы вы можете использовать строковый буфер со scanf, но это немного сложнее. - person Jerry Coffin; 22.01.2010

Что с этим не так?

std::stringstream convertor("Tom Scott 25");
std::string firstname;   
std::string surname;
int age;

convertor >> firstname >> surname >> age;
std::string name = firstname + " " + surname;
person Konrad Rudolph    schedule 22.01.2010
comment
Потому что это только пример. Я столкнусь со случаями, когда мне нужно извлечь неизвестное количество слов. - person kjagiello; 22.01.2010
comment
I'd like to extract two words or more to one string variable. В любом случае, это моя ошибка :) Я мог бы написать свой вопрос лучше. - person kjagiello; 22.01.2010
comment
@ Нил, он специально сказал, что ему нужно прочитать все до возраста, а не обязательно только два слова. - person Jerry Coffin; 22.01.2010

Что не так с этим?

std::stringstream convertor("Tom Scott 25");


std::string first, last;
int age;

convertor >> first >> last >> age

Если вы действительно хотите читать первым и последним за один раз, что-то вроде этого будет работать

class Name {
  std::string first, last;

 public:

  std::istream& read(std::istream& in) {
    return in >> first >> last;
  }

  operator std::string() const { return first + " " + last; }
};

std::istream& operator>>(std::istream& in, Name& name) {
  return name.read(in);
} 

/* ... */

Name name;
int age;

converter >> name >> age;
std::cout << (std::string)name; 

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

class Reader {
int numWords;
std::vector<std::string> words;
// ... 
std::istream& read(std::istream& in) {
  std::vector<std::string> tmp;
  std::string word;
  for (int i = 0; i < numWords; ++i) {
    if (!in >> word)
      return in;
    tmp.push_back(word);
  }

  // don't overwrite current words until success
  words = tmp;
  return in;
}
person meagar    schedule 22.01.2010
comment
Мне нравится твоя первая фраза. :-) - person Konrad Rudolph; 22.01.2010

Общий алгоритм, который вы могли бы реализовать:

read word into name
loop
   try reading integer
   if success then break loop
   else
      clear error flag
      read word and attach to name 
person David Rodríguez - dribeas    schedule 22.01.2010

Один из подходов — создать новый класс с перегруженным оператором>>.

class TwoWordString {
public:
    std::string str;
};

istream& operator>>(istream& os; TwoWordString& tws) {
    std::string s1, s2;
    os >> s1;
    os >> s2;
    tws.str = s1 + s2;
    return os;
}
person Christopher Bruns    schedule 22.01.2010
comment
У этого та же проблема - он по-прежнему читает только два слова. Кроме того, в нем также есть небольшая ошибка / опечатка (вы извлекаете из istream, а не из ostream). - person Jerry Coffin; 22.01.2010

Вот избыточный способ (используя Boost.Spirit< /а>) ›:D

#include <iostream>
#include <string>
#include <boost/format.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>

int main()
{
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;
    namespace ascii = boost::spirit::ascii;
    using ascii::char_; using ascii::digit; using ascii::blank;
    using qi::_1; using qi::int_; using phoenix::ref; using phoenix::at_c;

    std::string input("Sir  Buzz Killington, esq. 25");
    std::string name;
    int age = 0;

    qi::rule<std::string::const_iterator, std::string()> nameRule;
    nameRule %= (+(char_ - digit - blank));

    std::string::const_iterator begin = input.begin();
    std::string::const_iterator end = input.end();
    qi::parse(begin, end,
        (
                nameRule[ref(name) += _1]
            >> *( ((+blank) >> nameRule)[ref(name) += ' ']
                                        [ref(name) += at_c<1>(_1)] )
            >> *blank
            >>  int_[ref(age) = _1]
        )
    );

    std::cout << boost::format("Name: %1%\nAge: %2%\n") % name % age;
    return 0;
}

Выход:

Имя: сэр Базз Киллингтон, эсквайр.

Возраст: 25

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

person Emile Cormier    schedule 22.01.2010

Это домашнее задание, которое я только что сделал. Но типы int или double должны располагаться перед строкой. поэтому вы можете читать несколько слов разного размера. Надеюсь, это поможет вам немного.

string words;
sin>>day>>month>>year;
sin>>words;
watch = words;
while(sin>>words)
{
watch += " "+words;
}
person Bin Wei    schedule 28.06.2015

Вот решение с std::regex (любое количество имен):

auto extractNameAndAge(std::string const &s) -> std::tuple<std::string, int> {
  using namespace std::string_literals;

  static auto const r = std::regex{"(.*)\\s+(\\d+)\\s*$"s};

  auto match = std::smatch{};
  auto const matched = std::regex_search(s, match, r);
  if (!matched)
    throw std::invalid_argument{"Invalid input string \""s + s +
                                "\" in extractNameAndAge"s};

  return std::make_tuple(match[1], std::stoi(match[2]));
}

Тестовое задание:

auto main() -> int {
  using namespace std::string_literals;

  auto lines = std::vector<std::string>{"Jonathan Vincent Voight 76"s,
                                        "Donald McNichol Sutherland 79"s,
                                        "Scarlett Johansson 30"s};

  auto name = ""s;
  auto age = 0;

  for (auto cosnt &line : lines) {
    std::tie(name, age) = extractNameAndAge(line);
    cout << name << " - " << age << endl;
  }
}

Выход:

Jonathan Vincent Voight - 76
Donald McNichol Sutherland - 79
Scarlett Johansson - 30
person bolov    schedule 28.06.2015