Функция С++ для подсчета всех слов в строке
Меня спросили об этом во время собеседования и, видимо, это был простой вопрос, но это было не так и все еще не очевидно для меня.
Учитывая строку, подсчитайте все слова в ней. Не имеет значения, повторяются ли они. Просто общее количество, например, в текстовом файле. Слова - это что-то, разделенное пробелом, и пунктуация не имеет значения, если это часть слова.
Например:
A very, very, very, very, very big dog ate my homework!!!! ==> 11 words
Мой "алгоритм" просто просматривает пробелы и увеличивает счетчик до тех пор, пока не ударит нуль. Поскольку я не получил работу, и меня попросили уйти после этого, я думаю, что Мое решение было нехорошо? У кого-нибудь есть более умное решение? Я что-то пропустил?
Ответы
Ответ 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
Вот один проходной, почти не ветвящийся (почти), учитывающий локали алгоритм, который обрабатывает случаи с более чем одним пробелом между словами:
- Если строка пуста, вернуть 0
- пусть переходы = количество соседних пар символов (c1, c2), где
c1 == ' '
и c2 != ' '
- если предложение начинается с пробела, возвращаются
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++;}