Подтвердить что ты не робот

Функция С++ для подсчета всех слов в строке

Меня спросили об этом во время собеседования и, видимо, это был простой вопрос, но это было не так и все еще не очевидно для меня.

Учитывая строку, подсчитайте все слова в ней. Не имеет значения, повторяются ли они. Просто общее количество, например, в текстовом файле. Слова - это что-то, разделенное пробелом, и пунктуация не имеет значения, если это часть слова.

Например: A very, very, very, very, very big dog ate my homework!!!! ==> 11 words

Мой "алгоритм" просто просматривает пробелы и увеличивает счетчик до тех пор, пока не ударит нуль. Поскольку я не получил работу, и меня попросили уйти после этого, я думаю, что Мое решение было нехорошо? У кого-нибудь есть более умное решение? Я что-то пропустил?

4b9b3361

Ответ 1

Менее умный, более очевидный для всех программистов способ сделать это.

#include <cctype>

int CountWords(const char* str)
{
   if (str == NULL)
      return error_condition;  // let the requirements define this...

   bool inSpaces = true;
   int numWords = 0;

   while (*str != '\0')
   {
      if (std::isspace(*str))
      {
         inSpaces = true;
      }
      else if (inSpaces)
      {
         numWords++;
         inSpaces = false;
      }

      ++str;
   }

   return numWords;
}

Ответ 2

Предполагая, что слова разделены пробелом:

unsigned int countWordsInString(std::string const& str)
{
    std::stringstream stream(str);
    return std::distance(std::istream_iterator<std::string>(stream), std::istream_iterator<std::string>());
}

Примечание. Между словами может быть несколько пробелов. Кроме того, это не захватывает другие символы пробела, такие как вкладка новой строки или возврат каретки. Поэтому подсчета пробелов недостаточно.

Оператор ввода потока → при использовании для чтения строки из потока. Читает одно слово, разделенное пробелом. Поэтому они, вероятно, искали, чтобы использовать это, чтобы идентифицировать слова.

std::stringstream  stream(str);
std::string        oneWord;

stream >> oneWord; // Reads one space separated word.

Когда это можно использовать для подсчета слов в строке.

std::stringstream  stream(str);
std::string        oneWord;
unsigned int       count = 0;

while(stream >> oneWord) { ++count;}
// count now has the number of words in the string.

Сложность:
Потоки можно обрабатывать так же, как и любой другой контейнер, и есть итераторы для их прогона std:: istream_iterator. Когда вы используете оператор ++ на istream_iterator, он просто считывает следующее значение из потока с помощью оператора → . В этом случае мы читаем std::string, поэтому читаем слово, разделенное пробелом.

std::stringstream  stream(str);
std::string        oneWord;
unsigned int       count = 0;

std::istream_iterator loop = std::istream_iterator<std::string>(stream);
std::istream_iterator end  = std::istream_iterator<std::string>();

for(;loop != end; ++count, ++loop) { *loop; }

Использование std:: distance просто обертывает все вышеперечисленное в аккуратном пакете, поскольку оно находит расстояние между двумя итераторами, делая ++ в первом, пока мы не достигнем второго.

Чтобы избежать копирования строки, мы можем быть подлыми:

unsigned int countWordsInString(std::string const& str)
{
    std::stringstream stream;

    // sneaky way to use the string as the buffer to avoid copy.
    stream.rdbuf()->pubsetbuf (str.c_str(), str.length() );
    return std::distance(std::istream_iterator<std::string>(stream), std::istream_iterator<std::string>());
}

Примечание: мы все еще копируем каждое слово из оригинала во временное. Но стоимость этого минимальна.

Ответ 3

Еще одно решение на основе повышения, которое может работать (не проверено):

vector<string> result;
split(result, "aaaa bbbb cccc", is_any_of(" \t\n\v\f\r"), token_compress_on);

Дополнительную информацию можно найти в библиотеке алгоритмов Boost String.

Ответ 4

Это можно сделать без ручного поиска каждого символа или копирования строки.

#include <boost/iterator/transform_iterator.hpp>
#include <cctype>

boost::transform_iterator
    < int (*)(int), std::string::const_iterator, bool const& >
    pen( str.begin(), std::isalnum ), end( str.end(), std::isalnum );

size_t word_cnt = 0;

while ( pen != end ) {
    word_cnt += * pen;
    pen = std::mismatch( pen+1, end, pen ).first;
}

return word_cnt;

Я воспользовался isalnum вместо isspace.

Это не то, что я сделал бы на собеседовании. (Не похоже, чтобы он был скомпилирован в первый раз.)

Или, для всех ненавистников Boost; v)

if ( str.empty() ) return 0;

size_t word_cnt = std::isalnum( * str.begin() );

for ( std::string::const_iterator pen = str.begin(); ++ pen != str.end(); ) {
    word_cnt += std::isalnum( pen[ 0 ] ) && ! std::isalnum( pen[ -1 ] );
}

return word_cnt;

Ответ 5

Вы можете использовать std :: count или std :: count_if для этого. Ниже приведен простой пример с std :: count:

//Count the number of words on string
#include <iostream>
#include <string>
#include <algorithm> //count and count_if is declared here

int main () {
    std::string sTEST("Text to verify how many words it has.");

    std::cout << std::count(sTEST.cbegin(), sTEST.cend(), ' ')+1;

    return 0;
}

Ответ 6

Решение O (N), которое также очень легко понять и реализовать:

(Я не проверял пустой ввод строки, но я уверен, что вы можете сделать это легко.)

#include <iostream>
#include <string>
using namespace std;

int countNumberOfWords(string sentence){
    int numberOfWords = 0;
    size_t i;

    if (isalpha(sentence[0])) {
        numberOfWords++;
    }

    for (i = 1; i < sentence.length(); i++) {
        if ((isalpha(sentence[i])) && (!isalpha(sentence[i-1]))) {
            numberOfWords++;
        }
    }

    return numberOfWords;
}

int main()
{
    string sentence;
    cout<<"Enter the sentence : ";
    getline(cin, sentence);

    int numberOfWords = countNumberOfWords(sentence);
    cout<<"The number of words in the sentence is : "<<numberOfWords<<endl;

    return 0;
}

Ответ 7

Вот один проходной, почти не ветвящийся (почти), учитывающий локали алгоритм, который обрабатывает случаи с более чем одним пробелом между словами:

  1. Если строка пуста, вернуть 0
  2. пусть переходы = количество соседних пар символов (c1, c2), где c1 == ' ' и c2 != ' '
  3. если предложение начинается с пробела, возвращаются transitions иначе возвращаются transitions + 1

Вот пример со строкой = "Очень, очень, очень, очень, очень большая собака съела мою домашнюю работу !!!!"

 i | 0123456789
c1 | A very, very, very, very, very big dog ate my homework!!!!
c2 |  A very, very, very, very, very big dog ate my homework!!!!
   |  x     x     x     x     x    x   x   x   x  x

объяснение

Let 'i' be the loop counter.

When i=0: c1='A' and c2=' ', the condition 'c1 == ' '' and 'c2 != ' '' is not met
When i=1: c1=' ' and c2='A', the condition is met
... and so on for the remaining characters

Вот 2 решения, которые я придумал

Наивное решение

size_t count_words_naive(const std::string_view& s)
{
    if (s.size() == 0) return 0;
    size_t count = 0;
    bool isspace1, isspace2 = true;
    for (auto c : s) {
        isspace1 = std::exchange(isspace2, isspace(c));
        count += (isspace1 && !isspace2);
    }
    return count;
}

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

Внутренний продукт решения

size_t count_words_using_inner_prod(const std::string_view& s)
{
    if (s.size() == 0) return 0;
    auto starts_with_space = isspace(s.front());
    auto num_transitions = std::inner_product(
            s.begin()+1, s.end(), s.begin(), 0, std::plus<>(),
            [](char c2, char c1) { return isspace(c1) && !isspace(c2); });
    return num_transitions + !starts_with_space;
}

Ответ 8

Очень сжатый подход O (N):

bool is_letter(char c) { return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; }

int count_words(const string& s) {
    int i = 0, N = s.size(), count = 0;
    while(i < N) {
        while(i < N && !is_letter(s[i])) i++;
        if(i == N) break;
        while(i < N && is_letter(s[i])) i++;
        count++;
    }
    return count;
}

Подход с разделением и победой, сложность также O (N):

int DC(const string& A, int low, int high) {
    if(low > high) return 0;
    int mid = low + (high - low) / 2;

    int count_left = DC(A, low, mid-1);
    int count_right = DC(A, mid+1, high);

    if(!is_letter(A[mid])) 
        return count_left + count_right;
    else {
        if(mid == low && mid == high) return 1;
        if(mid-1 < low) {
            if(is_letter(A[mid+1])) return count_right;
            else return count_right+1;
        } else if(mid+1 > high) {
            if(is_letter(A[mid-1])) return count_left;
            else return count_left+1;
        }
        else {
            if(!is_letter(A[mid-1]) && !is_letter(A[mid+1])) 
                return count_left + count_right + 1;
            else if(is_letter(A[mid-1]) && is_letter(A[mid+1]))
                return count_left + count_right - 1;
            else
                return count_left + count_right;
        }
    }
}

int count_words_divide_n_conquer(const string& s) {
    return DC(s, 0, s.size()-1);
}

Ответ 9

Я не понимаю, как сложно сделать простой подсчет слов в C++, когда это так просто на другом языке: bash, python, haskell,...

Это было бы так просто, если бы вы могли сделать что-то вроде:

for(auto word: string){compter++;}