Adding Memoization - Dynamic Programming - c++

I am currently practicing some dynamic programming and I came across the Circus Tower problem.
I solved the problem with dynamic programming and implemented it using recursion. I've tested it with few inputs and it seems to work fine.
Now, I've been struggling few hours trying to figure out how to add memoization to my solution.
Questions
How can I add a working memoization to my solution. Where is my mistake in this case?
Is there any rule of thumb, or guidlines for how to add memoizations in general.
The Circus Tower Problem:
A circus is designing a tower of people standing atop one another’s shoulders. Each person must be both shorter and lighter than the person below him. Given the heights and weights of each person in the circus, write a method to compute the largest possible number of people in such a tower.
My Solution & Code
Dynamic Programming
OPT[N,P] = highest tower with N given persons and person P is at the top
----------------------------------------------------------
OPT[0,P] = 0
OPT[i,P] where person i can be above P = max(OPT[i-1,i]+1,OPT[i-1,P])
OPT[i,P] else = OPT[i-1,P]
Code:
struct Person{
int ht;
int wt;
};
// Without Memoization
int circusTower(int n, Person* top, std::vector<Person>& persons){
if (n == 0)
return 0;
if (top == NULL || top->ht > persons[n - 1].ht && top->wt > persons[n - 1].wt)
return max(circusTower(n - 1, &persons[n - 1], persons) + 1, circusTower(n - 1, top, persons));
else
return circusTower(n - 1, top, persons);
}
// With Memoization
int circusTower(int n, Person* top, std::vector<Person>& persons, std::vector<int>& memo){
if (n == 0)
return 0;
int result;
if (memo[n-1] == 0) {
if (top == NULL || top->ht > persons[n - 1].ht && top->wt > persons[n - 1].wt)
result = max(circusTower(n - 1, &persons[n - 1], persons, memo) + 1,
circusTower(n - 1, top, persons, memo));
else
result = circusTower(n - 1, top, persons, memo);
memo[n - 1] = result;
return result;
} else {
return memo[n-1];
}
}
Main - test:
int main(){
std::vector<Person> persons = { {65, 100},{100, 150},{56, 90}, {75, 190}, {60, 95},{68, 110} };
std::stable_sort(persons.begin(), persons.end(), sortByWt);
std::stable_sort(persons.begin(), persons.end(), sortByHt);
std::vector<int> memo(6,0);
//Without memoization
cout << circusTower(6, NULL, persons) << endl;
//With memoization
cout << circusTower(6, NULL, persons, memo) << endl;
}
In the example inside the main above, the right result is 5. My regular solution (without memoization) prints 5, but with memoization it prints 6.

Your method depend of 3 arguments
but you memoize only from the first argument n
Indeed in your case, the third one (persons) is constant,
but the second one (top) changes during the recursive call.
so due to your memoization, both the following return wrongly the same value:
circusTower(n - 1, &persons[n - 1], persons, memo)
circusTower(n - 1, top, persons, memo)

Related

Linear Search on Vector

I have tests to pass online using my created methods. I have a feeling there is an issue with one of the tests. The final one i cannot pass.
Here is the test-
TEST_CASE ("Linear Search With Self-Organization 3") {
int searchKey = 191;
vector<int> searchArray(500);
for (int i = 0; i < 500; i++) {
searchArray[i] = i + 1;
}
random_shuffle(searchArray.begin(), searchArray.end());
bool result, result2;
result = linearSearchSO(searchArray, searchKey);
int searchKey2 = 243;
result2 = linearSearchSO(searchArray, searchKey2);
REQUIRE (result == true);
REQUIRE (result2 == true);
REQUIRE (verifySearchArray(searchArray) == true);
REQUIRE (searchArray[0] == searchKey2);
REQUIRE (searchArray[1] == searchKey);
REQUIRE (searchArray.size() == 500);
}
The method in question here is linearSearchSO.
bool linearSearchSO(vector<int> & inputArr, int searchKey) {
printArray(inputArr);
for(int i=0; i < inputArr.size(); i++) {
int temp = inputArr[0];
if (inputArr[i] == searchKey) {
inputArr[0] = inputArr[i];
inputArr[i] = temp;
printArray(inputArr);
return true;
}
}
return false;
}
Worth noting that this method has passed all 3 of the other tests required. As you can see in the test, my tutor has called this method twice passing two different values.The idea is that there is a vector of 500 numbers.. In this instance he randomises the numbers. The best way for me to explain what is happening is that if he didn't randomise and the numbers were simply listed 1-500. The method gets called and I begin with the requested number 191, I move this to front of the vector.
Now it reads 191, 2, 3, 4 etc. 190, 1, 192 etc.
So he then calls the method again, and wants 243 to be moved to the front. His test wants the result to be 243, 191, 2, 3, 4. However what my code does is swap 191 to 243's position.
My result now reads 243, 2, 3, 4 etc. 242, 191, 244, 245 etc.
Every other test is simply taking one number and moving it to the front, the test then checks that each number is in the correct position. My question is, is there a way for me to achieve 243, 191, 2, 3.. without messing up every other test I've passed only using this one linearSearch function? or is there a problem with the test, and hes simply made a mistake.
EDIT- The actual question asked for this test.
Question 4
A self-organising search algorithm is one that rearranges items in a collection such that those items that are searched frequently are likely to be found sooner in the search. Modify the learning algorithm for linear search such that every time an item is found in the array, that item is exchanged with the item at the beginning of the array.
If I have understood correctly you need something like the following
#include <iostream>
#include <vector>
bool linearSearchSO( std::vector<int> & inputArr, int searchKey )
{
bool success = false;
auto it = inputArr.begin();
while ( it != inputArr.end() && *it != searchKey ) ++it;
if ( ( success = it != inputArr.end() ) )
{
int value = *it;
inputArr.erase( it );
inputArr.insert( inputArr.begin(), value );
}
return success;
}
int main()
{
std::vector<int> inputArr = { 1, 2, 3, 4, 5 };
for ( const auto &item : inputArr )
{
std::cout << item << ' ';
}
std::cout << '\n';
linearSearchSO( inputArr, 3 );
for ( const auto &item : inputArr )
{
std::cout << item << ' ';
}
std::cout << '\n';
}
The program output is
1 2 3 4 5
3 1 2 4 5
Pay attention to that instead of writing manually a loop in the function you could use the standard algorithm std::find.

min cost of climbing stairs dynamic programming

I am trying to solve the following problem on leetcode:
On a staircase, the i-th step has some non-negative cost cost[i] assigned (0 indexed).
Once you pay the cost, you can either climb one or two steps.
You need to find minimum cost to reach the top of the floor, and you can either start
from the step with index 0, or the step with index 1.
My solution is shown below:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
return helper(cost, cost.size() - 1);
}
int helper(vector<int>& cost, int currStair) {
static vector<double> minCost(cost.size(), 0);
minCost[0] = cost[0];
minCost[1] = cost[1];
if (minCost[currStair] > 0) {
return minCost[currStair];
}
return minCost[currStair] = min(helper(cost, currStair - 1), helper(cost, currStair - 2)) + cost[currStair];
}
};
When I try to submit, I get the following run-time error. Why?
AddressSanitizer: heap-buffer-overflow on address 0x603000000008 at pc 0x0000004089af bp 0x7ffdd02dcaa0 sp 0x7ffdd02dca98
EDITED SOLUTION:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
return helper(cost, cost.size() - 1);
}
int helper(vector<int>& cost, int currStair) {
if (currStair < 0 || cost.size() <= 1) {
return 0;
}
static vector<double> minCost(cost.size(), 0);
minCost[0] = cost[0];
minCost[1] = cost[1];
if (minCost[currStair] > 0) {
return minCost[currStair];
}
minCost[currStair] = min(helper(cost, currStair - 1), helper(cost, currStair - 2)) + cost[currStair];
return min(minCost[cost.size()-1], minCost[cost.size()-2]);
}
};
As you can see I made changes in the end of the code
The problem is a missing base case for the recursion. currStair keeps decrementing by -1 or -2 in each recursive call, but there's no condition to check if it went below 0 and cut off the recursion, resulting in an illegal memory access like minCost[-1].
Add the precondition
if (currStair < 0) return 0;
and you're back on track, although the algorithm still has correctness issues to resolve.
Here's a hint that might help you get unstuck:
The instructions say: "You need to find minimum cost to reach the top of the floor, and you can either start from the step with index 0, or the step with index 1."

Vector changes value unexpected

I am making a little game (just console) and my vectors don't act like I think they have to. One Value of the vector just changes and I don't know why. The Code shown below is part where this bug come from, I have deleted the rest of the code where these 2 vectors show up and the bug still appears and I DONT KNOW WHY!!
This part of the code is responsible for letting the Enemy spread to a random direction.
/**in this part the first value of the 2 vectors are created
(only once, I've tested it)**/
if(moves == 0){
int randomNum1 = (rand() % HEIGHT)+1;
int randomNum2 = (rand() % WIDTH)+1;
_EnemysY.push_back(randomNum1);
_EnemysX.push_back(randomNum2);
}
/**_Enemy vectors have normal values. For instance: _EnemysX[0]
is 23 and _EnemysY[0] is 12**/
/**In this part, the Enemy spreads in a random direction**/
if (moves > 3){
//save Enemys at the border here (those who can move)
std::vector<int> topX;
std::vector<int> topY;
std::vector<int> botX;
std::vector<int> botY;
std::vector<int> rigX;
std::vector<int> rigY;
std::vector<int> lefX;
std::vector<int> lefY;
/**here, I wanna save all Fields of the Enemy where it can spread to:**/
for (Uint it = 0; it < _EnemysY.size(); it++){
/**_EnemysY is still normal, but _EnemysX is like: 86BF163E0**/
if (_map[_EnemysY[it]-1][_EnemysX[it]] == _Grenade || _map[_EnemysY[it]-1][_EnemysX[it]] == _Field){
topY.push_back(_EnemysY[it]);
topX.push_back(_EnemysX[it]);
}
if (_map[_EnemysY[it]+1][_EnemysX[it]] == _Grenade || _map[_EnemysY[it]+1][_EnemysX[it]] == _Field){
botY.push_back(_EnemysY[it]);
botX.push_back(_EnemysX[it]);
}
if (_map[_EnemysY[it]][_EnemysX[it]-1] == _Grenade || _map[_EnemysY[it]][_EnemysX[it]-1] == _Field){
lefX.push_back(_EnemysX[it]);
lefY.push_back(_EnemysY[it]);
}
if (_map[_EnemysY[it]][_EnemysX[it]+1] == _Grenade || _map[_EnemysY[it]][_EnemysX[it]+1] == _Field){
rigX.push_back(_EnemysX[it]);
rigY.push_back(_EnemysY[it]);
}
}
/**and here is a random direction created and the programm
chooses which Field it will spread to: **/
for (;;){
int ranDir = (rand() % 4)+1;
if (ranDir == 1 && !topY.empty()){
int temp = (rand() % topY.size())+1;
_EnemysY.push_back(topY[temp]);
_EnemysX.push_back(topX[temp]);
return true;
}
if (ranDir == 2 && !botY.empty()){
int temp = (rand() % botY.size())+1;
_EnemysY.push_back(botY[temp]);
_EnemysX.push_back(botX[temp]);
return true;
}
if (ranDir == 3 && !lefY.empty()){
int temp = (rand() % lefY.size())+1;
_EnemysY.push_back(lefY[temp]);
_EnemysX.push_back(lefX[temp]);
return true;
}
if (ranDir == 4 && !rigY.empty()){
int temp = (rand() % rigY.size())+1;
_EnemysY.push_back(rigY[temp]);
_EnemysX.push_back(rigX[temp]);
return true;
}
}
}
First off, why not have a struct describing the "enemy", including its position (via two fields X and Y)? It would improve the code a bit and avoid the need for two vectors that are meaningless if separated.
On topic:
int temp = (rand() % rigY.size())+1;
_EnemysY.push_back(rigY[temp]);
rand() % rigY.size() will give you a number in the interval [0, rigY.size() ). You then add 1 to both sides so temp would be in the interval [1, rigY.size() ].
But rigY[rigY.size()] is not a valid element in the vector...
First of all: thank you Andrei and PaulMcKenzie for your time, your answers didn't solve the problem, but they were still usefull. :)
Now to the answer of the problem:
Alright! I'm a complete douchebag! Because what I didn't print in the code above, was my std::cout s for my variables and I accidentally wrote: "std::cout << ... << std:cout << ... std::endl" and for some reason the second "std::cout" caused a bug, where it printed out an ridiculously high number. So my code in general (without the cout s) works fine, but I still changed some things which you said.

Using recursion and backtracking to generate all possible combinations

I'm trying to implement a class that will generate all possible unordered n-tuples or combinations given a number of elements and the size of the combination.
In other words, when calling this:
NTupleUnordered unordered_tuple_generator(3, 5, print);
unordered_tuple_generator.Start();
print() being a callback function set in the constructor.
The output should be:
{0,1,2}
{0,1,3}
{0,1,4}
{0,2,3}
{0,2,4}
{0,3,4}
{1,2,3}
{1,2,4}
{1,3,4}
{2,3,4}
This is what I have so far:
class NTupleUnordered {
public:
NTupleUnordered( int k, int n, void (*cb)(std::vector<int> const&) );
void Start();
private:
int tuple_size; //how many
int set_size; //out of how many
void (*callback)(std::vector<int> const&); //who to call when next tuple is ready
std::vector<int> tuple; //tuple is constructed here
void add_element(int pos); //recursively calls self
};
and this is the implementation of the recursive function, Start() is just a kick start function to have a cleaner interface, it only calls add_element(0);
void NTupleUnordered::add_element( int pos )
{
// base case
if(pos == tuple_size)
{
callback(tuple); // prints the current combination
tuple.pop_back(); // not really sure about this line
return;
}
for (int i = pos; i < set_size; ++i)
{
// if the item was not found in the current combination
if( std::find(tuple.begin(), tuple.end(), i) == tuple.end())
{
// add element to the current combination
tuple.push_back(i);
add_element(pos+1); // next call will loop from pos+1 to set_size and so on
}
}
}
If I wanted to generate all possible combinations of a constant N size, lets say combinations of size 3 I could do:
for (int i1 = 0; i1 < 5; ++i1)
{
for (int i2 = i1+1; i2 < 5; ++i2)
{
for (int i3 = i2+1; i3 < 5; ++i3)
{
std::cout << "{" << i1 << "," << i2 << "," << i3 << "}\n";
}
}
}
If N is not a constant, you need a recursive function that imitates the above
function by executing each for-loop in it's own frame. When for-loop terminates,
program returns to the previous frame, in other words, backtracking.
I always had problems with recursion, and now I need to combine it with backtracking to generate all possible combinations. Any pointers of what am I doing wrong? What I should be doing or I am overlooking?
P.S: This is a college assignment that also includes basically doing the same thing for ordered n-tuples.
Thanks in advance!
/////////////////////////////////////////////////////////////////////////////////////////
Just wanted to follow up with the correct code just in case someone else out there is wondering the same thing.
void NTupleUnordered::add_element( int pos)
{
if(static_cast<int>(tuple.size()) == tuple_size)
{
callback(tuple);
return;
}
for (int i = pos; i < set_size; ++i)
{
// add element to the current combination
tuple.push_back(i);
add_element(i+1);
tuple.pop_back();
}
}
And for the case of ordered n-tuples:
void NTupleOrdered::add_element( int pos )
{
if(static_cast<int>(tuple.size()) == tuple_size)
{
callback(tuple);
return;
}
for (int i = pos; i < set_size; ++i)
{
// if the item was not found in the current combination
if( std::find(tuple.begin(), tuple.end(), i) == tuple.end())
{
// add element to the current combination
tuple.push_back(i);
add_element(pos);
tuple.pop_back();
}
}
}
Thank you Jason for your thorough response!
A good way to think about forming N combinations is to look at the structure like a tree of combinations. Traversing that tree then becomes a natural way to think about the recursive nature of the algorithm you wish to implement, and how the recursive process would work.
Let's say for instance that we have the sequence, {1, 2, 3, 4}, and we wish to find all the 3-combinations in that set. The "tree" of combinations would then look like the following:
root
________|___
| |
__1_____ 2
| | |
__2__ 3 3
| | | |
3 4 4 4
Traversing from the root using a pre-order traversal, and identifying a combination when we reach a leaf-node, we get the combinations:
{1, 2, 3}
{1, 2, 4}
{1, 3, 4}
{2, 3, 4}
So basically the idea would be to sequence through an array using an index value, that for each stage of our recursion (which in this case would be the "levels" of the tree), increments into the array to obtain the value that would be included in the combination set. Also note that we only need to recurse N times. Therefore you would have some recursive function whose signature that would look something like the following:
void recursive_comb(int step_val, int array_index, std::vector<int> tuple);
where the step_val indicates how far we have to recurse, the array_index value tells us where we're at in the set to start adding values to the tuple, and the tuple, once we're complete, will be an instance of a combination in the set.
You would then need to call recursive_comb from another non-recursive function that basically "starts off" the recursive process by initializing the tuple vector and inputting the maximum recursive steps (i.e., the number of values we want in the tuple):
void init_combinations()
{
std::vector<int> tuple;
tuple.reserve(tuple_size); //avoids needless allocations
recursive_comb(tuple_size, 0, tuple);
}
Finally your recusive_comb function would something like the following:
void recursive_comb(int step_val, int array_index, std::vector<int> tuple)
{
if (step_val == 0)
{
all_combinations.push_back(tuple); //<==We have the final combination
return;
}
for (int i = array_index; i < set.size(); i++)
{
tuple.push_back(set[i]);
recursive_comb(step_val - 1, i + 1, tuple); //<== Recursive step
tuple.pop_back(); //<== The "backtrack" step
}
return;
}
You can see a working example of this code here: http://ideone.com/78jkV
Note that this is not the fastest version of the algorithm, in that we are taking some extra branches we don't need to take which create some needless copying and function calls, etc. ... but hopefully it gets across the general idea of recursion and backtracking, and how the two work together.
Personally I would go with a simple iterative solution.
Represent you set of nodes as a set of bits. If you need 5 nodes then have 5 bits, each bit representing a specific node. If you want 3 of these in your tupple then you just need to set 3 of the bits and track their location.
Basically this is a simple variation on fonding all different subsets of nodes combinations. Where the classic implementation is represent the set of nodes as an integer. Each bit in the integer represents a node. The empty set is then 0. Then you just increment the integer each new value is a new set of nodes (the bit pattern representing the set of nodes). Just in this variation you make sure that there are always 3 nodes on.
Just to help me think I start with the 3 top nodes active { 4, 3, 2 }. Then I count down. But it would be trivial to modify this to count in the other direction.
#include <boost/dynamic_bitset.hpp>
#include <iostream>
class TuppleSet
{
friend std::ostream& operator<<(std::ostream& stream, TuppleSet const& data);
boost::dynamic_bitset<> data; // represents all the different nodes
std::vector<int> bitpos; // tracks the 'n' active nodes in the tupple
public:
TuppleSet(int nodes, int activeNodes)
: data(nodes)
, bitpos(activeNodes)
{
// Set up the active nodes as the top 'activeNodes' node positions.
for(int loop = 0;loop < activeNodes;++loop)
{
bitpos[loop] = nodes-1-loop;
data[bitpos[loop]] = 1;
}
}
bool next()
{
// Move to the next combination
int bottom = shiftBits(bitpos.size()-1, 0);
// If it worked return true (otherwise false)
return bottom >= 0;
}
private:
// index is the bit we are moving. (index into bitpos)
// clearance is the number of bits below it we need to compensate for.
//
// [ 0, 1, 1, 1, 0 ] => { 3, 2, 1 }
// ^
// The bottom bit is move down 1 (index => 2, clearance => 0)
// [ 0, 1, 1, 0, 1] => { 3, 2, 0 }
// ^
// The bottom bit is moved down 1 (index => 2, clearance => 0)
// This falls of the end
// ^
// So we move the next bit down one (index => 1, clearance => 1)
// [ 0, 1, 0, 1, 1]
// ^
// The bottom bit is moved down 1 (index => 2, clearance => 0)
// This falls of the end
// ^
// So we move the next bit down one (index =>1, clearance => 1)
// This does not have enough clearance to move down (as the bottom bit would fall off)
// ^ So we move the next bit down one (index => 0, clearance => 2)
// [ 0, 0, 1, 1, 1]
int shiftBits(int index, int clerance)
{
if (index == -1)
{ return -1;
}
if (bitpos[index] > clerance)
{
--bitpos[index];
}
else
{
int nextBit = shiftBits(index-1, clerance+1);
bitpos[index] = nextBit-1;
}
return bitpos[index];
}
};
std::ostream& operator<<(std::ostream& stream, TuppleSet const& data)
{
stream << "{ ";
std::vector<int>::const_iterator loop = data.bitpos.begin();
if (loop != data.bitpos.end())
{
stream << *loop;
++loop;
for(; loop != data.bitpos.end(); ++loop)
{
stream << ", " << *loop;
}
}
stream << " }";
return stream;
}
Main is trivial:
int main()
{
TuppleSet s(5,3);
do
{
std::cout << s << "\n";
}
while(s.next());
}
Output is:
{ 4, 3, 2 }
{ 4, 3, 1 }
{ 4, 3, 0 }
{ 4, 2, 1 }
{ 4, 2, 0 }
{ 4, 1, 0 }
{ 3, 2, 1 }
{ 3, 2, 0 }
{ 3, 1, 0 }
{ 2, 1, 0 }
A version of shiftBits() using a loop
int shiftBits()
{
int bottom = -1;
for(int loop = 0;loop < bitpos.size();++loop)
{
int index = bitpos.size() - 1 - loop;
if (bitpos[index] > loop)
{
bottom = --bitpos[index];
for(int shuffle = loop-1; shuffle >= 0; --shuffle)
{
int index = bitpos.size() - 1 - shuffle;
bottom = bitpos[index] = bitpos[index-1] - 1;
}
break;
}
}
return bottom;
}
In MATLAB:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% combinations.m
function combinations(n, k, func)
assert(n >= k);
n_set = [1:n];
k_set = zeros(k, 1);
recursive_comb(k, 1, n_set, k_set, func)
return
function recursive_comb(k_set_index, n_set_index, n_set, k_set, func)
if k_set_index == 0,
func(k_set);
return;
end;
for i = n_set_index:length(n_set)-k_set_index+1,
k_set(k_set_index) = n_set(i);
recursive_comb(k_set_index - 1, i + 1, n_set, k_set, func);
end;
return;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Test:
>> combinations(5, 3, #(x) printf('%s\n', sprintf('%d ', x)));
3 2 1
4 2 1
5 2 1
4 3 1
5 3 1
5 4 1
4 3 2
5 3 2
5 4 2
5 4 3

Can this if-else statement be made cleaner

I am trying to improve a C++ assignment to make it more efficient. I am a beginner with the language (and programming in general too), so I am only using what I know so far (if, else). I have a function that converts scores into levels, so anything under 30 = 1, 30-49 = 2, 50-79 = 3 and so on...
Here is how I am doing it:
if (score1 <= 30) level1 = 1;
else if (score1 <= 49) level1 = 2;
else level1 = 3;
if (score2 <= 30) level2 = 1;
else if (score2 <= 49) level2 = 2;
else level2 = 3;
//etc...
Is there a better way to do this, as I am aware this will require a new line for every single score I have.
This rather depends on what you mean by efficiency. You could keep the limits for each level in an array
int level_limits[] = {0, 30, 49, 79, [...]};
int getLevel(int score)
{
int level;
for (level = 0; level < N_LEVELS; ++level)
if (level_limits[level] > score)
return level;
return level; // or whatever should happen when you exceed the score of the top level
}
...
level1 = getLevel(score1);
level2 = getLevel(score2);
... or something like that.
Create a function where you pass in the score and it returns the level. Also, if there are going to be a lot of them you should create an array of scores and levels.
for (x=0;x < num_scores;x++)
{
level[x] = get_level(score[x]);
}
something like that.
First of all factor out the code for computing the level into a separate function, say get_level:
level1 = get_level(score1);
level2 = get_level(score2);
You can implement get_level in different ways.
If the number of levels is small you can use the linear search:
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
const size_t NUM_BOUNDS = sizeof(bounds) / sizeof(*bounds);
for (size_t i = 0; i < NUM_BOUNDS; ++i)
if (score <= bounds[i])
return i + 1;
return NUM_BOUNDS + 1;
}
Or, if you are an STL fan:
#include <algorithm>
#include <functional>
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
return std::find_if(bounds,
bounds + sizeof(bounds) / sizeof(*bounds),
std::bind2nd(std::greater_equal<int>(), score)) - bounds + 1;
}
If you have many levels binary search may be more appropriate:
#include <algorithm>
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
return std::lower_bound(bounds,
bounds + sizeof(bounds) / sizeof(*bounds), score) - bounds + 1;
}
Or if you have relatively small number of levels then use if-else chain similar to your original version:
int get_level(int score)
{
if (score <= 30)
return 1;
else if (score <= 49)
return 2;
else if (score <= 79)
return 3;
return 4;
}
Note that putting returns on separate lines can make your program easier to trace in the debugger.
No, In terms of efficiency it's already optimized.
On another stylistic note. I would recommend putting the conditions and statements on separate lines:
if (score1 <= 30) {
level1 = 1;
} else if (score1 <= 49) {
level1 = 2;
} else if (score1 <= 79) {
level1 = 3;
}
and as the other answer suggest, another stylistic plus would be to extract out the common behavior into a function
The absolute fastest way:
Create an array of 80 values, one for each possible score.
Fill in the array with the level for each possible score.
Example:
int score_array[80] = {1,1,1,1,...};
The following code gets you the level for each score:
level2 = score_array[score2];
This will compile down to one machine instruction. Doesn't get much faster.
If the score has a "manageable" range, how about the following?
// Convert score to level. Valid scores are in the range [0-79]
int score2level( int score ) {
static const int level_table[ 80 ] = {
1, 1, 1, ... // 31 "1"s for range 0-30
2, 2, 2, ... // 19 "2"s for range 31-49
3, 3, 3, ... // 30 "3"s for range 50-79
};
assert( (0 <= score) && (score <= 79) );
return level_table[ score ];
}
The motivation is to avoid conditional code (the if, elses) at the cost of a pre-populated table. May be its too much for 3 levels, but may help when the number of levels increase.
int getLevel(int score)
{
if(score<=30)
return 1;
int level=2,limit=49;
while(score > limit)
{
limit+=30;
level++;
}
return level;
}
int score[]={35,25,67,56,78};
int level[5];
for(int i=0;i<5;i++)
level[i]=getLevel(score[i]);
Cheers :)
You can utilize modulo and a hash table in order to achieve some code style elegance. Pseudo code:
int getLevel(int num)
{
hash_table = (30=>1, 49=>2, 79=>3);
foreach threshold (keys hash_table) {
if (num modulo (threshold + 1) == num) {
return hash_table[threshold];
}
}
}
If you have less levels than fingers on your hands, you should use Paul's suggestion. However, if the number of levels are bigger, you could use binary search if your level thresholds are fairly uniform (and strictly increasing). Then you can get close to logarithmic runtime (I mean, you were asking for efficiency).
This is a good compromise between the lookuptable-suggestion and the linear search, but again, it only pays if you have a lot of levels.
Oh, by the way, if there is a pattern in how the thresholds are chosen, you should use that instead of searching for the right level.