Avatar of jsylvanus

jsylvanus's solution

to Bob in the C++ Track

Published at Jul 13 2018 · 3 comments
Instructions
Test suite
Solution

Note:

This solution was written on an old version of Exercism. The tests below might not correspond to the solution code, and the exercise may have changed since this code was written.

Bob is a lackadaisical teenager. In conversation, his responses are very limited.

Bob answers 'Sure.' if you ask him a question.

He answers 'Whoa, chill out!' if you yell at him.

He says 'Fine. Be that way!' if you address him without actually saying anything.

He answers 'Whatever.' to anything else.

Getting Started

Make sure you have read the C++ page on exercism.io. This covers the basic information on setting up the development environment expected by the exercises.

Passing the Tests

Get the first test compiling, linking and passing by following the three rules of test-driven development. Create just enough structure by declaring namespaces, functions, classes, etc., to satisfy any compiler errors and get the test to fail. Then write just enough code to get the test to pass. Once you've done that, uncomment the next test by moving the following line past the next test.

#if defined(EXERCISM_RUN_ALL_TESTS)

This may result in compile errors as new constructs may be invoked that you haven't yet declared or defined. Again, fix the compile errors minimally to get a failing test, then change the code minimally to pass the test, refactor your implementation for readability and expressiveness and then go on to the next test.

Try to use standard C++11 facilities in preference to writing your own low-level algorithms or facilities by hand. CppReference is a wiki reference to the C++ language and standard library. If you are new to C++, but have programmed in C, beware of C traps and pitfalls.

Source

Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial. http://pine.fm/LearnToProgram/?Chapter=06

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

bob_test.cpp

#include "bob.h"
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(stating_something)
{
    BOOST_REQUIRE_EQUAL("Whatever.", bob::hey("Tom-ay-to, tom-aaaah-to."));
}

#if defined(EXERCISM_RUN_ALL_TESTS)
BOOST_AUTO_TEST_CASE(shouting)
{
    BOOST_REQUIRE_EQUAL("Whoa, chill out!", bob::hey("WATCH OUT!"));
}

BOOST_AUTO_TEST_CASE(asking_a_question)
{
    BOOST_REQUIRE_EQUAL("Sure.", bob::hey("Does this cryogenic chamber make me look fat?"));
}

BOOST_AUTO_TEST_CASE(talking_forcefully)
{
    BOOST_REQUIRE_EQUAL("Whatever.", bob::hey("Let's go make out behind the gym!"));
}

BOOST_AUTO_TEST_CASE(using_acronyms_in_regular_speech)
{
    BOOST_REQUIRE_EQUAL("Whatever.", bob::hey("It's OK if you don't want to go to the DMV."));
}

BOOST_AUTO_TEST_CASE(forceful_questions)
{
    BOOST_REQUIRE_EQUAL("Whoa, chill out!", bob::hey("WHAT THE HELL WERE YOU THINKING?"));
}

BOOST_AUTO_TEST_CASE(shouting_numbers)
{
    BOOST_REQUIRE_EQUAL("Whoa, chill out!", bob::hey("1, 2, 3 GO!"));
}

BOOST_AUTO_TEST_CASE(only_numbers)
{
    BOOST_REQUIRE_EQUAL("Whatever.", bob::hey("1, 2, 3"));
}

BOOST_AUTO_TEST_CASE(question_with_only_numbers)
{
    BOOST_REQUIRE_EQUAL("Sure.", bob::hey("4?"));
}

BOOST_AUTO_TEST_CASE(shouting_with_special_characters)
{
    BOOST_REQUIRE_EQUAL("Whoa, chill out!", bob::hey("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"));
}

BOOST_AUTO_TEST_CASE(shouting_with_no_exclamation_mark)
{
    BOOST_REQUIRE_EQUAL("Whoa, chill out!", bob::hey("I HATE YOU"));
}

BOOST_AUTO_TEST_CASE(statement_containing_question_mark)
{
    BOOST_REQUIRE_EQUAL("Whatever.", bob::hey("Ending with a ? means a question."));
}

BOOST_AUTO_TEST_CASE(prattling_on)
{
    BOOST_REQUIRE_EQUAL("Sure.", bob::hey("Wait! Hang on.  Are you going to be OK?"));
}

BOOST_AUTO_TEST_CASE(question_with_trailing_whitespace)
{
    BOOST_REQUIRE_EQUAL("Sure.", bob::hey("Are you ok? "));
}

BOOST_AUTO_TEST_CASE(silence)
{
    BOOST_REQUIRE_EQUAL("Fine. Be that way!", bob::hey(""));
}

BOOST_AUTO_TEST_CASE(prolonged_silence)
{
    BOOST_REQUIRE_EQUAL("Fine. Be that way!", bob::hey("   "));
}

BOOST_AUTO_TEST_CASE(not_all_silence)                                                                         
{                                                                                                             
	BOOST_REQUIRE_EQUAL("Whatever.", bob::hey(" A bit of silence can be nice.  "));                           
}
#endif

StringView.cpp

#include "StringView.h"

using namespace std;

namespace jsylvanus {

	StringView::StringView(const string& ref) 
		: referenced_string{ref}, begin{ref.begin()}, end{ref.end()-1} { }

	bool StringView::is_empty() {
		return begin >= end;
	}

	void StringView::trim() {
		trim_left();
		trim_right();
	}

	void StringView::trim_left() {
		while(isspace(*begin) && begin < end) begin++;
	}

	void StringView::trim_right() {
		while(isspace(*end) && end > begin) end--;
	}

}

StringView.h

#ifndef __STRINGVIEW_H
#define __STRINGVIEW_H

#include <string>

namespace jsylvanus {

	struct StringView {
		const std::string& referenced_string;
		std::string::const_iterator begin;
		std::string::const_iterator end;
		
		StringView(const std::string& ref);
		bool is_empty();
		void trim();
		void trim_left();
		void trim_right();
	};

}

#endif

bob.cpp

#include "bob.h"
#include "StringView.h"

using namespace std;
using namespace jsylvanus;

namespace bob {

	struct BobStringView : StringView {
		BobStringView(const string& ref) : StringView(ref) {
			trim();
		};
		bool is_upper();
		bool has_letter();
		bool is_shouty();
		bool is_question();
	};

	auto RESPONSE_TO_SILENCE   = "Fine. Be that way!";
	auto RESPONSE_TO_YELLING   = "Whoa, chill out!";
	auto RESPONSE_TO_QUESTIONS = "Sure.";
	auto DEFAULT_RESPONSE      = "Whatever.";
	
	const char* hey(const string &query) {
		BobStringView view{query};

		if (view.is_empty()) {
			return RESPONSE_TO_SILENCE;
		}

		if (view.is_shouty()) {
			return RESPONSE_TO_YELLING;
		}

		if (view.is_question()) {
			return RESPONSE_TO_QUESTIONS;
		}

		return DEFAULT_RESPONSE;
	}

	bool BobStringView::is_upper() {
		return !is_empty() && all_of(begin, end+1, [](char c) {
			return !isalpha(c) || isupper(c);
		});
	}

	bool BobStringView::is_shouty() {
		return !is_empty() && has_letter() && is_upper();
	}

	bool BobStringView::has_letter() {
		return !is_empty() && any_of(begin, end+1, ::isalpha);
	}

	bool BobStringView::is_question() {
		return !is_empty() && *end == '?';
	}
}

bob.h

#ifndef BOB_H
#define BOB_H

#include <string>

namespace bob {

	const char* hey(const std::string &query);

}

#endif

Community comments

Find this solution interesting? Ask the author a question to learn more.
Avatar of slepasteur

Nice improvements. I prefer the StringView being in its own file. The trim functions are now safe with the checks. What happened before was that the trim_right read the memory preceding the string buffer until it found a character that was not a space (probably the first byte before the buffer). It's never a good idea to read outside of your own memory.

I still think that you should not keep a reference over the input string inside the StringView. It is not usefull.

I also disagree with the check functions beeing part of a StringView. These functions are just algorithms characterizing the content of a range of char. You could easily use them on a std::sting or a cont char * if they are not part of the StringView class.

Avatar of jsylvanus

Keeping the reference around has potential uses if I were to expand on the StringView as a utility struct, for example reset, reset_(left|right) for resetting the trim operations. Worth keeping around, IMO. So, situational.

I think since the check functions aren't part of the interface and are implemented only in a local struct that expands on StringView's capabilities, it's perfectly valid. If I were exposing is_shouty et.al. to the world, it would certainly be more appropriate to make them less dependent upon a struct. So, I agree, but again it's situational.

Have learned a lot with this simple exercise, thanks for the nits.

Avatar of slepasteur

Well you pay a prize for keeping that reference. Each StringView object will include the size of a reference. It is not much indeed but still it is premature pessimization in my opinion.

As for the check functions, it would make changes from StringView to std::string for example easier.

Anyway, it is also a matter of preference at this point ;). I am glad if I have helped you.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?