The task was to create the parser for the string input, that would return the std::vector< int > of parsed numeric results and the type of given input (e.g. numbers, division by zero error, out of range and so on). The assumption was that only numeric characters, spaces, slashes and periods were in given string.
Single number input (range of numbers is <-7, 7>):
- "-1" - translates into {-1}, returns type "numbers"
List input (range the same as above, return type is "numbers"):
- "1 3 2" - translates into {1, 2, 3}
- "1, 3, 2" - like above (periods are changed into spaces)
- "<2" - translates into {-7, -6, -5, -4, -3, -2, -1, 0, 1}
- ">4" - translates into {5, 6, 7}
Divisor input, no range limit. Returns the vector and type "divisors".
- "/2" - translates into {2}
If input is invalid, corresponding error code will be returned as a type along with an empty vector.
The code below is not divided into .h and .cpp files for the sake of brevity, the expanded code is on github page.
Request.h
The output messages are defined here, along with the returned structure.
#ifndef REQUEST_H #define REQUEST_H #include <string> #include <vector> //request return codes namespace ReturnCodes { const std::string NOT_A_NUMBER = "Not a number!"; const std::string INVALID_INPUT = "Invalid input!"; const std::string SUCCESOR_NULL = "Succesor is null!"; const std::string OUT_OF_RANGE = "Out of range!"; const std::string DIVISION_BY_ZERO = "Division by zero!"; const std::string NUMBERS = "Numbers"; const std::string DIVISOR = "Divisor"; const std::string EMPTY = "Empty"; const std::string OUT_OF_INT_RANGE = "Out of range of int!"; //custom ones are also allowed } //returned structure struct RequestValue { std::vector<int> result; std::string message; bool isValid() const { return message == ReturnCodes::NUMBERS || message == ReturnCodes::DIVISOR || message == ReturnCodes::EMPTY; } }; #endif
Parser.h
Wrapper class for the chain of request handlers.
#ifndef PARSER_H #define PARSER_H #include "RequestHandlers.h" class Parser { RequestHandler* chain; public: Parser() { //a chain of handlers chain = new EmptyStringHandler( new DivisorStringHandler( new SingleNumberStringHandler( new InequalityStringHandler( new ListStringHandler( nullptr))))); } RequestValue parse(const std::string& value, bool preprocess = true) const { if (preprocess) { std::string text = RequestPreprocessor::changePeriodsToSpaces(value); text = RequestPreprocessor::removeTrailingSpaces(text); return chain->handle(text); } else { return chain->handle(value); } } ~Parser() { delete chain; } }; #endif
RequestPreprocessor.h
Helper class for string operations.
#ifndef STRING_SUBPROCESSOR_H #define STRING_SUBPROCESSOR_H #include <string> #include <algorithm> class RequestPreprocessor { public: //as the range where the numbers are valid is symmetrical, //store only one number static const int maxNumber = 7; //check if integer r is in range specified as a const class member static bool isInRangeInclusive(int r) { return r >= -maxNumber && r <= maxNumber; } //as periods doesn't matter, they can be changed into spaces static std::string changePeriodsToSpaces(std::string input) { std::replace(input.begin(), input.end(), ',', ' '); return input; } //remove unnecessary space prefixes and suffixes static std::string removeTrailingSpaces(const std::string& input) { if (input.empty()) return input; //seek start unsigned start = 0; while (start < input.size() && input[start] == ' ') start++; //seek end unsigned end = static_cast<unsigned int>(static_cast<int>(input.size()) - 1); while (end > start && input[end] == ' ') end--; return input.substr(start, end - start + 1); } //used in Division input Acase static bool hasSlash(const std::string& input) { return input.find('/') != std::string::npos; } //used in inequality input cases static bool hasInequalityCharacters(const std::string& input) { return input.find('>') != std::string::npos || input.find('<') != std::string::npos; } //check if a string contains only digits or '-' sign static bool isANumber(const std::string& input) { std::string text = removeTrailingSpaces(input); //check first character unsigned i = 0; if (text[0] == '-') { if (text.size() == 1) //check if input consists of only one '-' character { return false; //"-" is not a number! } i++; } //check digits, start from i-th character return !std::any_of(text.begin() + i, text.end(), [](char c) { return c < '0' || c>'9'; }); }; }; #endif
RequestHandlers.h
A collection of classes used as a links in parser chain.
#ifndef REQUEST_HANDLER_H #define REQUEST_HANDLER_H #include "Request.h" #include "RequestPreprocessor.h" #include <iterator> #include <sstream> //base class for handlers class RequestHandler { protected: //next handler RequestHandler * succesor = nullptr; //check if handler can try to process the request virtual bool canHandleRequest(const std::string &input) const = 0; //process the request - we can assume that no other Handler is able to do it virtual RequestValue handleImplementation(const std::string &input) const = 0; //give up and relay the input to next handler RequestValue passFurther(const std::string& input) const { if (succesor == nullptr) { return { {}, ReturnCodes::SUCCESOR_NULL }; } else { return succesor->handle(input); } } public: //constructor - if successor is nullptr, it means that it is the last element explicit RequestHandler(RequestHandler* succesor) { this->succesor = succesor; } //check if input can be handled ? handle it : pass further RequestValue handle(const std::string& input) const { if (canHandleRequest(input)) { return handleImplementation(input); } else { return passFurther(input); } } //take care for its successors virtual ~RequestHandler() { delete succesor; } }; //check is input is just "" class EmptyStringHandler : public RequestHandler { public: explicit EmptyStringHandler(RequestHandler *succesor) : RequestHandler(succesor) { } protected: bool canHandleRequest(const std::string& input) const override { return input.empty(); } RequestValue handleImplementation(const std::string& input) const override { return { {}, ReturnCodes::EMPTY }; } }; //check for input being a number between -7 and 7, inclusively class SingleNumberStringHandler : public RequestHandler { public: explicit SingleNumberStringHandler(RequestHandler *succesor) : RequestHandler(succesor) { } protected: bool canHandleRequest(const std::string& input) const override { return !input.empty() && !RequestPreprocessor::hasInequalityCharacters(input) && !RequestPreprocessor::hasSlash(input) && RequestPreprocessor::isANumber(input); } RequestValue handleImplementation(const std::string& input) const override { try { //check for incorrect characters if (!RequestPreprocessor::isANumber(input)) { return { {}, ReturnCodes::NOT_A_NUMBER }; } int p = stoi(input); //check range if (RequestPreprocessor::isInRangeInclusive(p)) { return { { p }, ReturnCodes::NUMBERS }; } else { return { { p }, ReturnCodes::OUT_OF_RANGE }; } } //stoi errors catch (std::invalid_argument &invalidArgumentException) { return passFurther(input); } catch (std::out_of_range &outOfRangeException) { return { {}, ReturnCodes::OUT_OF_INT_RANGE }; } } }; //check input with '<' class InequalityStringHandler : public RequestHandler { public: explicit InequalityStringHandler(RequestHandler *succesor) : RequestHandler(succesor) { } protected: bool canHandleRequest(const std::string& input) const override { //the characters after then inequality sign cannot be other inequality return input.size() > 1 && (input[0] == '<' || input[0] == '>') && !RequestPreprocessor::hasSlash(input) && !RequestPreprocessor::hasInequalityCharacters(input.substr(1)); } RequestValue handleImplementation(const std::string& input) const override { try { //check for incorrect characters if (!RequestPreprocessor::isANumber(input.substr(1))) { return { {}, ReturnCodes::NOT_A_NUMBER }; } int p = stoi(input.substr(1)); //check range if (!RequestPreprocessor::isInRangeInclusive(p)) { return { { p }, ReturnCodes::OUT_OF_RANGE }; } if (input[0] == '<') { //grab items <p std::vector<int> result; for (int i = -RequestPreprocessor::maxNumber; i < p; i++) { result.push_back(i); } return { result, ReturnCodes::NUMBERS }; } //provided by canHandleRequest, there're no other cases than '>' else { //grab items >p std::vector<int> result; for (int i = p + 1; i <= RequestPreprocessor::maxNumber; i++) { result.push_back(i); } return { result, ReturnCodes::NUMBERS }; } } //stoi errors catch (std::invalid_argument &invalidArgumentException) { return passFurther(input); } catch (std::out_of_range &outOfRangeException) { return { {}, ReturnCodes::OUT_OF_INT_RANGE }; } } }; //check for space-separated input class ListStringHandler : public RequestHandler { public: explicit ListStringHandler(RequestHandler *succesor) : RequestHandler(succesor) { } protected: bool canHandleRequest(const std::string& input) const override { return input.size() > 1 && !RequestPreprocessor::hasSlash(input) && !RequestPreprocessor::hasInequalityCharacters(input); } RequestValue handleImplementation(const std::string& input) const override { std::vector<int> v; try { //split input by spaces std::vector<std::string> results = tokenize(input); for (auto&& result : results) { //check for incorrect characters if (!RequestPreprocessor::isANumber(result)) { return { { v }, "Not a number @" + std::to_string(v.size() + 1) + "!" }; } int p = stoi(result); //check range if (RequestPreprocessor::isInRangeInclusive(p)) { //no duplicates! if (find(v.begin(), v.end(), p) == v.end()) { v.push_back(p); } } else { return { v, "Out of range @" + std::to_string(v.size() + 1) + "!" }; } } sort(v.begin(), v.end()); return { v, ReturnCodes::NUMBERS }; } //stoi errors catch (std::invalid_argument &invalidArgumentException) { return { {}, "Invalid character @" + std::to_string(v.size() + 1) + "!" }; } catch (std::out_of_range &outOfRangeException) { return { {}, ReturnCodes::OUT_OF_INT_RANGE }; } } private: static std::vector<std::string> tokenize(const std::string& input) { std::istringstream iss(input); return std::vector<std::string>( std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>()); } }; //check for number with '/' operator class DivisorStringHandler : public RequestHandler { public: explicit DivisorStringHandler(RequestHandler *succesor) : RequestHandler(succesor) { } protected: bool canHandleRequest(const std::string& input) const override { return input.size() > 1 && input[0] == '/' && !RequestPreprocessor::hasInequalityCharacters(input); } RequestValue handleImplementation(const std::string& input) const override { try { std::string text = input.substr(1); //check for incorrect characters if (!RequestPreprocessor::isANumber(text)) { return { {}, ReturnCodes::NOT_A_NUMBER }; } int p = stoi(text); //obvious division by zero if (p == 0) { return { { p }, ReturnCodes::DIVISION_BY_ZERO }; } //check range return { { p }, ReturnCodes::DIVISOR }; } //stoi errors catch (std::invalid_argument &invalidArgumentException) { return passFurther(input); } catch (std::out_of_range &outOfRangeException) { return { {}, ReturnCodes::OUT_OF_INT_RANGE }; } } }; #endif
main.cpp Entry point for program, it only runs a series of tests on various input.
#include <iostream> #include <cassert> #include "Parser.h" //go through a set of cases void runTests() { Parser *parser = new Parser(); //empty input RequestValue r = parser->parse(""); assert(r.isValid() && r.result.empty()); //single-number input r = parser->parse("-0"); assert(r.isValid() && r.result[0] == 0); r = parser->parse("7"); assert(r.isValid() && r.result[0] == 7); r = parser->parse("8"); assert(!r.isValid() && r.message == ReturnCodes::OUT_OF_RANGE); r = parser->parse("-7"); assert(r.isValid() && r.result[0] == -7); r = parser->parse("-8"); assert(!r.isValid() && r.message == ReturnCodes::OUT_OF_RANGE); r = parser->parse("9"); assert(!r.isValid() && r.message == ReturnCodes::OUT_OF_RANGE); r = parser->parse("-9"); assert(!r.isValid() && r.message == ReturnCodes::OUT_OF_RANGE); //inequality input r = parser->parse("<2"); assert(r.isValid() && r.result.size() == 9 && r.result[1] == -6); r = parser->parse("<-6"); assert(r.isValid() && r.result.size() == 1 && r.result[0] == -7); r = parser->parse(">6"); assert(r.isValid() && r.result.size() == 1 && r.result[0] == 7); r = parser->parse(">7"); assert(r.isValid() && r.result.empty()); r = parser->parse("<-7"); assert(r.isValid() && r.result.empty()); r = parser->parse("<<2"); assert(!r.isValid()); r = parser->parse(" < -2 2 2 "); assert(!r.isValid()); //list input r = parser->parse("1 2"); assert(r.isValid() && r.result.size() == 2); r = parser->parse("-1, -2 -4"); assert(r.isValid() && r.result.size() == 3 && r.result[2] == -1); r = parser->parse("-2, -2 -2, -3, -4 1 2 2"); assert(r.isValid() && r.result.size() == 5); //division input r = parser->parse("/2"); assert(r.isValid()); r = parser->parse("/"); assert(!r.isValid()); r = parser->parse("/4/"); assert(!r.isValid()); r = parser->parse("/-1231"); assert(r.result.size() == 1 && r.result[0] == -1231); r = parser->parse("//1"); assert(r.isValid() == false); r = parser->parse("/0"); assert(!r.isValid() && r.message == ReturnCodes::DIVISION_BY_ZERO); //mixed / random r = parser->parse("/2 3, -2"); assert(r.isValid() == false); r = parser->parse("sink"); assert(r.isValid() == false); r = parser->parse("111111111111111111111"); assert(!r.isValid() && r.message == ReturnCodes::OUT_OF_INT_RANGE); } int main() { std::cout << "Testing..." << std::endl; runTests(); std::cout << "We are here, so the tests are complete" << std::endl; system("pause"); return 0; }
( (0,1,2,3)>2 ) / 2
as an expression tree:Divide(LargerThan(List(0,1,2,3), List(2)), List(2))
, because it gives you far greater flexibility for representing expressions, and makes it easy to evaluate them.\$\endgroup\$