2

I have to write a function that prints out all vowels in a given string line including Y, unless Y is followed by another vowel.

For example:

"test phrase" returns 3
"yes no why not?" returns 4

I've tried using getline(), but I'm not sure if there's a way to use it without requiring some type of input from the user. I need it to use an already declared string.

Here's my code that somewhat works, but takes an input.

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

int numVowelsIncludingY(string line){
  int v = 0;
  getline(cin, line);

  for(int i = 0; i < line.length(); ++i){
    if(line[i] == 'A' || line[i] == 'E' || line[i] == 'I' || line[i] == 'O' || line[i] == 'U' || line[i] == 'Y' ||
       line[i] == 'a' || line[i] == 'e' || line[i] == 'i' || line[i] == 'o' || line[i] == 'u' || line[i] == 'y'){
        ++v;
    }
  }
  return v;
}

UPDATE

I've removed getline and reading the string works fine, but I'm still confused on how to count Y as a vowel only if it's not followed by another vowel.

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

int numVowelsIncludingY(string line){
  int v = 0;

  for(int i = 0; i < line.length(); ++i){
    if(line[i] == 'A' || line[i] == 'E' || line[i] == 'I' || line[i] == 'O' || line[i] == 'U' || line[i] == 'Y' ||
       line[i] == 'a' || line[i] == 'e' || line[i] == 'i' || line[i] == 'o' || line[i] == 'u' || line[i] == 'y'){
        ++v;
    }
  }
  return v;
}
efan
  • 99
  • 1
  • 8
  • Probably not useful to you at this time, but [`std::count_if`](https://en.cppreference.com/w/cpp/algorithm/count). Maybe you can steal a few ideas from the sample implementations. – user4581301 Apr 22 '21 at 17:02
  • If you get the input in `line` argument, then just use it. Remove line with `getline` completely. – Yksisarvinen Apr 22 '21 at 17:03
  • Writing a predicate for `count_if` that also checks the value of _adjacent_ elements seems like it'll be harder than just writing a loop manually. As for your implementation, I don't understand what you're using `getline` for, especially when your function already accepts a string as an argument. – Nathan Pierson Apr 22 '21 at 17:04
  • why do you want to use getline? `getline(cin, line);` does read from standard input, ie user input. – 463035818_is_not_an_ai Apr 22 '21 at 17:04
  • Maybe you need `getline` before you call the function, but I agree with my fellow commenters: You do not need `getline` in this function. – user4581301 Apr 22 '21 at 17:05
  • I removed getline and it worked fine. How about the logic for Y? I need it to count Y as a vowel, only if it's not followed by another vowel. – efan Apr 22 '21 at 17:06
  • 1
    Quick hack: if you convert each character to upper ([`std::toupper`](https://en.cppreference.com/w/cpp/string/byte/toupper)) or lower case ([`std::tolower`](https://en.cppreference.com/w/cpp/string/byte/tolower)) before comparing it, you can halve the number of comparisons required. – user4581301 Apr 22 '21 at 17:08
  • 2
    For the `Y` logic, if `i + 1 != line.length()`, then check `line[i+1]` to see if its also a vowel. – ChrisMM Apr 22 '21 at 17:11
  • You need a bit of smarter logic for the Y case. write down how you would do check for the Y case on a piece of paper. The logic should port almost directly to code. It might help if you think of it like explaining the job to a small child. – user4581301 Apr 22 '21 at 17:11
  • You can make it a bit cleaner by having all the vowels in a [set](https://www.cplusplus.com/reference/set/set/find/) – drum Apr 22 '21 at 17:12
  • 1
    Note about `set`, yes. The code will look a lot cleaner, but `set` has a lot of overhead that needs a fairly large set of data to overcome. For the paltry few vowels, you're actually better off with a dumb old linear search. [`strchr`](https://en.cppreference.com/w/cpp/string/byte/strchr) is a really quick way to get that linear search. – user4581301 Apr 22 '21 at 17:15
  • 1
    Reduce your comparisons by half! Use `tolower` or `toupper` before you compare! – Thomas Matthews Apr 22 '21 at 17:31
  • 1
    Those ‘if’ lines are really crying out to be converted to a switch block with a case for each letter :) – Jeremy Friesner Apr 22 '21 at 17:40

3 Answers3

2

You do not need to use getline inside the function when you pass the string as parameter. Though you can read the string outside of the function from some stream. For example to test with different input you could write:

void test(std::istream& input_stream) {
    std::string line;
    std::getline(input_stream,line);
    std::cout << line << ": " << numVowelsIncludingY(line) << "\n";
}

int main(){
    std::stringstream test1{"test phrase"};
    test(test1);
    std::stringstream test2{"yes no why not?"};
    test(test2);
    std::stringstream test3{"ya"};
    test(test3);
    std::stringstream test4("aa");
    test(test4);
}

And expect output:

test phrase: 3
yes no why not?: 4
ya: 1
aa: 2

test can as well be called with std::cin to read input from the user.

For your special condition of Y not followed by a vowel you need to be careful at the boundaries to avoid out-of-bounds access. I suggest to spend a line of code extra for the last character then we can assert for the main part that every character has a next character.

Because we need to check a character for being a vowel at least twice, I decided to use a lambda expression, and while I was at it, I also wrote one to check for Y or y.

There are many ways to check if a character is a vowel. I consider it simple to search for the character in the string "AEIOUaeio". std::string::find returns std::string::npos when the character cannot be found, otherwise we know its a vowel.

#include <iostream>
#include <string>
#include <sstream>

int numVowelsIncludingY(const std::string& line){
  if (line.size() == 0) return 0;
  auto is_vowel = [](char c){
    std::string vowels{"AEIOUaeiou"};
    return vowels.find(c) != std::string::npos;    
  };
  auto is_y = [](char c){ return c == 'Y' or c == 'y'; };

  size_t counter = 0;
  for (size_t i = 0; i < line.size() -1; ++i){
      auto& c = line[i];
      auto& next = line[i+1];
      if (is_vowel(c) or ( is_y(c) and !is_vowel(next) )) ++counter;
  }
  if (is_vowel(line.back()) or is_y(line.back())) ++counter;
  return counter;
}

Live Demo

^^ Try to experiment by calling test(std::cin) and type some input in godbolts stdin field.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

Here's a program that satisfies your test cases, and a couple others. I have two functions to this job. The first checks for a 'traditional' vowel. It was easier to do this than to try to add the conditions for 'Y' at this point.

Then, in count_vowels(), I check if I should count a 'Y' or not. There are two cases needed because the first case does not want to go out of bounds of the std::string. It can handle 'Y' anywhere in the word except the last position. The second case checks if 'Y' is the last letter, and counts it.

#include <cctype>
#include <iostream>
#include <string>

// Simple vowel check; the condition on 'Y' comes in count_vowels()
bool is_capital_vowel(char c) {
  return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}

int count_vowels(std::string str) {
  int count = 0;

  for (auto& c : str) {
    c = std::toupper(c);
  }

  for (std::size_t i = 0; i < str.length(); ++i) {
    if (str[i] == 'Y' && (i + 1) < str.length() &&
        !is_capital_vowel(str[i + 1])) {
      ++count;
    } else if (str[i] == 'Y' && (i + 1) == str.length()) {
      // The last character being 'Y' required some special consideration
      ++count;
    } else if (is_capital_vowel(str[i])) {
      ++count;
    }
  }

  return count;
}

int main() {
  std::string wordOne = "backyard";         // Should count 2
  std::string wordTwo = "cardigan";         // Should count 3
  std::string wordThr = "telephoney";       // Should count 5
  std::string testOne = "test phrase";      // Should return 3
  std::string testTwo = "yes no why not?";  // Should return 4

  std::cout << wordOne << ": " << count_vowels(wordOne) << " vowels.\n";
  std::cout << wordTwo << ": " << count_vowels(wordTwo) << " vowels.\n";
  std::cout << wordThr << ": " << count_vowels(wordThr) << " vowels.\n";
  std::cout << testOne << ": " << count_vowels(testOne) << " vowels.\n";
  std::cout << testTwo << ": " << count_vowels(testTwo) << " vowels.\n";
}

sweenish
  • 4,793
  • 3
  • 12
  • 23
  • I think `yy` should return `1` but it returns `0`. – Linux Geek Apr 22 '21 at 18:34
  • I like this, but I really needed it to be inside one function. Thanks, though! – efan Apr 22 '21 at 19:20
  • @efan similar as in my answer `is_capital_vowel` can be turned into a lambda expression. "inside one function" can always be done by some refactoring – 463035818_is_not_an_ai Apr 22 '21 at 19:24
  • @LinuxGeek It actually returns `2`; and given the rules, that might or might not be correct. – sweenish Apr 22 '21 at 19:37
  • @efan It wouldn't be difficult to make it one function. Trivial to the point that if you need it, you should be able to do it yourself. This answer exists to demonstrate the logic needed, not give you something to copy/paste and turn in. – sweenish Apr 22 '21 at 19:47
2

I deleted my last answer as I didn't understand your question correctly. But now here's the code that should work as intended. And yeah you should not use using namespace std, it is considered a bad practice. Look here: Why is "using namespace std;" considered bad practice?

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

int numVowelsIncludingY(std::string line) {
  int v = 0;

  std::vector<char> vovels{'a', 'e', 'i', 'o', 'u', 'y'};
  for (size_t i = 0; i < line.length(); ++i) {
    char ch = tolower(line[i]);
    if (ch == 'y' && find(vovels.begin(), vovels.end(), tolower(line[i + 1])) !=
                         vovels.end()) {
    } else if (find(vovels.begin(), vovels.end(), ch) != vovels.end()) {
      ++v;
    }
  }
  return v;
}
int main(void) {

  std::cout << numVowelsIncludingY("yy");

  return 0;
}
Linux Geek
  • 957
  • 1
  • 11
  • 19
  • This works perfectly! I do have one questions, though. Why didn't you put anything inside the if statement? Is that basically just saying "if this is true, move on, otherwise just end the loop"? – efan Apr 22 '21 at 19:21
  • If that condition is `true` then it means there exists a vowel after `y`, but we don't have to count it as a vowel as it was stated in the problem so it's left blank so it wouldn't go to `else if`. And we don't want it to go to `else if` because `else if` only checks if a character is a vowel or not (including `y`), so we have to check before `else if` to make sure there doesn't exist a vowel after `y` – Linux Geek Apr 22 '21 at 19:30
  • I assumed that `yy` should result in `2` , because only `y`s that are folllowed by a vowel are ignored while `y` followed by `y` counts. My initial interpretation was something else entirely. I guess OP knows what they want ;) – 463035818_is_not_an_ai Apr 22 '21 at 19:34
  • 1
    Interesting fun fact about `tolower`: It accepts an `int`, not a `char` as the input parameter. This solves quite a few problems with out-of-band characters like EOF, but can result in some really interesting problems documented (complete with solution) in the [Notes linked here](https://en.cppreference.com/w/cpp/string/byte/tolower#Notes). The same applies to `toupper`. – user4581301 Apr 22 '21 at 20:35