Reputation: 3044
I quickly wrote the below class for this question.
I'm looking for a way to merge addFruit()
with removeFruit()
to reduce the code.
They both use identical conditions but just different function call at the end.
My Code :
#include <iostream>
#include <string>
#include <vector>
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
if (str == "apples")
addToVec(apples, count);
else if (str == "oranges")
addToVec(oranges, count);
else if (str == "lemons")
addToVec(lemons, count);
else if (str == "melons")
addToVec(melons, count);
else if (str == "bananas")
addToVec(bananas, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void removeFruit(const std::string &str)
{
if (str == "apples")
removeFromVec(apples);
else if (str == "oranges")
removeFromVec(oranges);
else if (str == "lemons")
removeFromVec(lemons);
else if (str == "melons")
removeFromVec(melons);
else if (str == "bananas")
removeFromVec(bananas);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
private:
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
Any clever way to nicely merge the two functions so I can reduce the code?
Upvotes: 6
Views: 597
Reputation: 11
I would do this:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
doToFruit(str, [&](std::vector<int> &vec){ addToVec(vec, count); });
}
void removeFruit(const std::string &str)
{
doToFruit(str, [&](std::vector<int> &vec){ removeFromVec(vec); });
}
private:
template<typename Callable>
void doToFruit(const std::string &str, const Callable &func)
{
std::pair<const char*, std::vector<int>&> fruits[] = {
{"apple", apples}, {"oranges", oranges}, {"lemons", lemons},
{"melons", melons}, {"bananas", bananas}
};
for (auto i : fruits)
if (str == i.first)
return func(i.second);
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
You can use pointer to members if you want better performance:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
doToFruit(str, [&](std::vector<int> &vec){ addToVec(vec, count); });
}
void removeFruit(const std::string &str)
{
doToFruit(str, [&](std::vector<int> &vec){ removeFromVec(vec); });
}
private:
template<typename Callable>
void doToFruit(const std::string &str, const Callable &func)
{
auto iFound = fruits.find(str);
if (iFound != fruits.end())
return func(this->*(iFound->second));
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
static std::unordered_map<std::string, std::vector<int> MyClass::*> fruits;
};
std::unordered_map<std::string, std::vector<int> MyClass::*> MyClass::fruits = {
{"apple", &MyClass::apples}, {"oranges", &MyClass::oranges},
{"lemons", &MyClass::lemons}, {"melons", &MyClass::melons},
{"bananas", &MyClass::bananas}
};
Upvotes: 1
Reputation: 3277
What about below solution. That will also allow you add/remove known fruits easily by adding/removing line in constructor.
#include <iostream>
#include <string>
#include <vector>
#include <map>
class MyClass
{
public:
MyClass()
{
allowedFruits["apples"] = {};
allowedFruits["oranges"] = {};
allowedFruits["lemons"] = {};
allowedFruits["melons"] = {};
allowedFruits["bananas"] = {};
}
void addFruit(const std::string &str, int count)
{
auto it = allowedFruits.find(str);
if(it != MyClass::allowedFruits.end()){
it->second.push_back(count);
}
else {
std::cout << "Unknown Fruit : " << str << '\n';
}
}
void removeFruit(const std::string &str)
{
auto it = allowedFruits.find(str);
if(it != allowedFruits.end()){
// my be some check here
it->second.pop_back();
}
else {
std::cout << "Unknown Fruit : " << str << '\n';
}
}
private:
std::map<std::string,std::vector<int>> allowedFruits;
};
Upvotes: 2
Reputation: 3666
A code that selects the vector to be used and then perform the action can be used :
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
auto vec = selectVector(str);
if(vec != nullptr)
addToVec(*vec, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void removeFruit(const std::string &str)
{
auto vec = selectVector(str);
if(vec != nullptr)
removeFromVec(*vec);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
private:
std::vector<int> *selectVector(const std::string &str)
{
if (str == "apples")
return &apples;
else if (str == "oranges")
return &oranges;
else if (str == "lemons")
return &lemons;
else if (str == "melons")
return &melons;
else if (str == "bananas")
return &bananas;
else
return nullptr;
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
Upvotes: 2
Reputation: 1775
I would go a little functionnal by passing the function to apply.
#include <iostream>
#include <string>
#include <vector>
#include <functional>
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
searchAndApplyHelper(str, std::bind(&MyClass::addToVec, *this, std::placeholders::_1, count));
}
void removeFruit(const std::string &str)
{
searchAndApplyHelper(str, std::bind(&MyClass::removeFromVec, *this, std::placeholders::_1));
}
private:
template <class Func>
void searchAndApplyHelper(const std::string str, Func f)
{
if (str == "apples")
f(apples);
else if (str == "oranges")
f(oranges);
else if (str == "lemons")
f(lemons);
else if (str == "melons")
f(melons);
else if (str == "bananas")
f(bananas);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
I did it with a template but you could use std::function
as well.
Upvotes: 1
Reputation: 7055
Using C++17 you could use optional arguments:
void addRemoveFruit(const std::string &str, std::optional<int> count = std::nullopt)
{
if (str == "apples")
addRemoveVec(apples, count);
else if (str == "oranges")
addRemoveVec(oranges, count);
else if (str == "lemons")
addRemoveVec(lemons, count);
else if (str == "melons")
addRemoveVec(melons, count);
else if (str == "bananas")
addRemoveVec(bananas, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
and:
void addRemoveVec(std::vector<int> &vec, std::optional<int> count = std::nullopt)
{
if(count.has_value()) {
vec.push_back(count.value());
} else {
vec.pop_back();
}
}
This way existing calls to addFruit
/removeFruit
only need to be changed to addRemoveFruit
without having to change the parameters passed.
Upvotes: 0
Reputation: 8598
To stick with your pattern of multiple distinct vectors, I'd recommend using an internal enum that decides the method, while the choice of the vector can be done in one place:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
addOrRemoveFruit(str, count, Method::ADD);
}
void removeFruit(const std::string &str)
{
addOrRemoveFruit(str, 0, Method::REMOVE);
}
private:
enum class Method
{
ADD,
REMOVE
};
void addOrRemoveFruit(const std::string &str, int count, Method method)
{
if (str == "apples")
addOrRemoveFruitImpl(apples, count, method);
else if (str == "oranges")
addOrRemoveFruitImpl(oranges, count, method);
else if (str == "lemons")
addOrRemoveFruitImpl(lemons, count, method);
else if (str == "melons")
addOrRemoveFruitImpl(melons, count, method);
else if (str == "bananas")
addOrRemoveFruitImpl(bananas, count, method);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void addOrRemoveFruitImpl(std::vector<int> &vec, int count, Method method)
{
if (method == Method::ADD)
vec.push_back(count);
else
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
However even better would be to use a map:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
fruits[str].push_back(count);
}
void removeFruit(const std::string &str)
{
if (fruits.count(str) > 0)
fruits[str].pop_back();
}
private:
std::unordered_map<std::string, std::vector<int>> fruits;
};
Upvotes: -1
Reputation: 1
The easiest solution might be to use a std::map
for those vectors:
std::map<std::string,std::vector<int>> fruitVecs;
The key values of the map would be "apples"
, "oranges"
, "bananas"
etc.
Thus you can easily access the corresponding vector for any operation through the map.
Upvotes: 3
Reputation: 122585
Without changing the interface you could do it like this:
std::vector<int>& pickVector(std::string str) {
// put all the switch here and return a reference to the correct vector
}
void addFruit(const std::string &str, int count)
{
addToVec(pickVector(str),count);
}
Upvotes: 1
Reputation: 464
Make an additional function e.g. determineTargetVector(const std::string &str)
which returns the corresponding vector, where you want to insert/remove an element, so you have no redundant conditions. Also its nice to have only a single reponsibility for each function.
For example:
std::vector<int> *determineTargetVector(const std::string &str)
{
if (str == "apples")
return &apples;
else if (str == "oranges")
return &oranges;
else if (str == "lemons")
.
.
.
else
//something invalid, to check for in superior function
return nullptr;
}
Upvotes: 6