Algorithm correctness for Bank Kattis problem - c++

This is the problem I am referring to. In quick summary:
Input: An integer time T; the time in minutes in which a bank closes and a set of pairs c and t that denotes the amount of cash (integer) this person carries and the time in minutes from now after which person leaves if not served. It takes one minute to serve a person and you must begin serving a person at time t at the latest.
Output: Maximum amount of money that can be collected within closing time.
My approach was this: place all the people in a map that maps money to time. I sort this map by money. I then make a queue-like structure where I take the person with the most money and place him/her as far back as possible. If the spot is occupied, then I move forward until I find a spot. If I can't then I just don't add the person.
Below is my helper function to determine whether or not I can insert a person.
// returns index where we can insert, otherwise -1
int canInsert(bool* queue, int timeToInsert) {
if (!queue[timeToInsert]) {
return timeToInsert;
} else {
int index = timeToInsert-1;
while (index >= 0) {
if (!queue[index]) {
return index;
} else {
index--;
}
}
return -1;
}
}
Here is the main driver function:
// moneyToTime is a map that maps a person's money to the time value
int Bank(int T, map<int, int> moneyToTime) {
int answer = 0;
bool queue[47] = {0};
for (map<int,int>::reverse_iterator i = moneyToTime.rbegin(); i != moneyToTime.rend(); i++) {
if (T > 0) {
// try to insert. If we can, then add to sum. Otherwise, don't.
int potentialIndex = canInsert(queue, i->second);
if (potentialIndex != -1) {
queue[potentialIndex] = 1;
answer += i->first;
T--;
}
} else {
break;
}
}
return answer;
}
Logically, this makes sense to me and it is passing almost all the test cases. There are a couple that are failing; I can not see what they are. The test case errors are in fact indicating wrong answer, as opposed to bad runtime errors. Can someone help me see the fallacy in my approach?

You don't show how you build the moneyToTime but anyway it looks like map<int, int> is a wrong type for that. Imagine you have many people with the same amount of money and different timings. How would you represent then in your moneyToTime?
If my theory is correct, an example like this should break your solution:
3 3
2000 2
2000 1
500 2
Obviously the best sum is 4000 = 2000 + 2000. I suspect you get only 2500.

I think the best sum for the TC is 4500,
3 3
2000 2
2000 1
500 2
{money, time}
{2000,0} | {2000,1} | {500,2}

Related

Is there a way to make a function do something different the second time a number appears?

Im trying to make a small program about the method in which the amount of money awarded to players at the end of a game is decided. So far I have used a RNG to simulate what happens in a round of the game but have gotten stuck. I want to find out how to design my code in order for it to do something different the second time the same number is generated from the RNG.
while (active==1)
{
random=rand()%11+1;
if (random==11)
{
bomb=1;
}
}
Thanks for any responses :)
Keep a map of (number, count) pairs:
std::unordered_map<int, std::size_t> number_frequencies;
while (active) {
int number = random_number();
++number_frequencies[number];
if (number_frequencies[number] == 2) {
// do something
} else {
// do something else
}
}
First of all, for clarity, apply the following:
Define MAX to whatever maximum value you want to have (e.g., 11)
Use random between 0 and MAX-1 (instead of between 1 and MAX)
Then, you can try to adjust the following piece of code to your requirements:
#define MAX 11
...
int count[MAX] = {0};
while (active == 1)
{
random = rand()%MAX;
count[random]++;
...
}
The count array indicates the number of times that each random value was generated.
So at each iteration, you can use count[random] in order to choose what action to take.

C++ simple array element comparison

Okay I don't know if this is even a valid question but I'm posting here because I don't know where else to turn with this. I just started studying programming this half year at a university and we just had the final exam, which I failed. Basically, there were 4 questions, and while the second one looked easy it was actually tricky and I just can't figure out how it should have been done.
Basically the problem is: There is a bank, and when people log in to do business, you need to write a program that records the time they logged in (0-24h), the minutes (0-59), the type of transaction they choose (1 for logging in with a bank card, -1 for logging out with the same bank card, 2 for money input into the account, -2 for withdrawal) and finally either their bank acc number (if they pressed 1 or -1 previously), or the amount they are withdrawing or putting in (if they chose 2 or -2).
Basically here is how we had to do it:
int n; //size of the array or number of ppl who transacted that day
cin >> n;
int bank[n][4];
for (int i=0; i<n; ++i)
{
cin >> bank[n][0];
cin >> bank[n][1];
cin >> bank[n][2];
cin >> bank[n][3];
}
This fills up all the info and then,
basically a sample input looked like this for 4 customers during the day:
11 40 1 458965
12 20 2 6000
15 40 -1 458965
16 25 -2 18000
Here is the part I could not solve:
Our test asked us: How many people were logged in from 12 o clock to 13:00 oclock?
At first I did
int count=0;
for (int i=0; i<n; ++i)
{
if (bank[i][0]==12)
{
count=count+1;
}
}
cout << count;
The problem with this, is that it does not account for people who log in before 12 with a 1 in the third column, but log out at later than 1 oclock with a -1. Which means they were still logged in from 12 to 1pm.
so then I did
int count=0;
for (int i=0; i<n; ++i)
{
if (bank[i][0]==12)
{
count=count+1;
}
if (bank[i][2]==-1)
{
count=count+1;
}
}
cout << count;
but then I realized that this would count some logins twice, because if they logged in at 12 with a 1 for example, then logged out at 3 o clock with a -1 it would count that one person twice.
It also asked us what is the longest period that any person was logged in, assuming the bank kicks everyone off at 24:00. I honestly am not even sure how to even begin that one.
EDIT: SORRY i edited a bunch of stuff to make it clearer and correct code. I'm not too good at this yet forgive my mistakes
I didn’t know how the bank system works. So I made a minimal example for you.
I also didn't know if you used classes before, so I wrote it without.
I cleaned your code a bit:
//Use these enums
enum action { action_login = 1, action_logout = -1, action_input = 2, action_output = -2 };
enum information {information_time_h, information_time_m, information_action, information_bankNumber};
//Place this in the function you have
int peapelToInput = 0; //size of the array or number of ppl who transacted that day
cin >> peapelToInput;
for (int i=0; i<peapelToInput; ++i)
{
//Maby add error handeling? When some one inputs a 'a', it won't do what you want.
cin bank[i][information_time_h];
cin bank[i][information_time_m];
cin bank[i][information_action];
cin bank[i][information_bankNumber];
}
As you can see, I made the code cleaner by adding enums. This makes developing a lot easier.
The login code:
int count=0;
int bankSize = bank.size(); //I guess it's a vector?
for (int i=0; i < bankSize; ++i)
{
if (bank[i][information_time_h] == 12 && bank[i][information_action] == action_login)
count++;
}
cout << "logins at 12:00 - 12:59:" << count << endl;
You can do 2 checks in 1 if, I increment count when they were logedin from 12:00 - 12:59. Do you need exclude people that were loggedout?
The longest time code:
//A function to search when he is logedout
int findLogoutIndex(int start, int accountNumber, XXX bank)
{
int bankSize = bank.size();
for (int i=start; i < bankSize; ++i)
if( bank[i][information_action] == action_logout && bank[i][information_bankNumber] == accountNumber)
return i;
return -1; //Handle this error
}
//And how it workes
int logenst = 0;
int indexLongest = 0;
int bankSize = bank.size(); //I guess it's a vector?
for (int i=0; i < bankSize; ++i)
{
if( bank[i][information_action] != action_login )
continue;
int logoutIndex = findLogoutIndex(i,bank[i][information_bankNumber],bank);
//check if logoutIndex is not -1, or handle the error on an other way.
int loginTimeHour = bank[logoutIndex][information_time_h] - bank[i][information_time_h];
int loginTimeMinute = bank[logoutIndex][information_time_m] - bank[i][information_time_m];
int loginTime = (loginTimeHour * 100) + loginTimeMinute;
if( logenst < loginTime)
{
logenst = loginTime;
indexLongest = i;
}
}
cout << "longest is: H:" << bank[indexLongest][information_time_h] << " M: " << bank[indexLongest][information_time_m] << endl;
You don't need to keep the time format, this way makes comparing a lot easier. Just save the longest login time and the index number of it. That way you can easily access all the data you want.
I didn't take the time to write "good code". But you asked how it can be done, I guess this is good enough to understand it?
I didn't test the code and wrote it in Notepad. So I don't know if it will compile.
The first thing that you need to know is what the questions are actually asking. In the first case, How many people were logged in from 12 o clock to 1 oclock? can mean multiple things. It could mean how many people were logged in during the whole period or how many people were logged in at any given time between those two hours. The difference is whether someone that logs in at 12:15 and logs out at 12:30 is counted or not. The second question is calculating the longest period someone was logged in, and that can be done at the same time.
One possible approach would be managing a lookup table from user id to login times. You read the input linearly, whenever someone logs in you add an entry (acct, time) into the table. When they log out you lookup the account number and calculate the difference of times. If the difference is greater than the maximum you store the new maximum.
For the first question, at 12 you can create a set of the people that was logged in from that lookup table. Whenever someone logs out between that time and 1 you find whether the person was in the set and you remove it if so. When you find the first operation after 1, the set contains the account numbers of all the people that was logged in for the whole period from 12 to 1.
If the question was getting all people that was logged at any time in the period, instead of removing from the set those users that log out before 1, you need to include new users that log in inside the period. At the end of the period the set contains all users that were logged in at any time within the period.
You only need to perform a single pass over the input data which means that you don't even need to store all of the transactions in memory, only the map/set required above. The overall cost of the operation is O(n log n) on the number of operations. (Disclaimer: I haven't done the math, this is a hunch :))
Haven't tested this. Nevertheless, the process followed should be correct.
I'm sure this can still be optimized in terms of execution speed.
Also, I assume by time 12 you mean 12:00 and by time 1 you mean 13:00.
int main()
{
int answer = 0;
// For each transaction
for ( int i = 0; i < count; i++ ) {
// If logged in before 12:00
// bank[i][2] > 0 tells you user logged in.
if ( bank[i][0] < 12 && bank[i][2] > 0 ) {
// Loop through each following transaction.
for ( int j = i + 1; j < count; j++ ) {
// If logged out after 13:00
if ( bank[j][0] > 13 && bank[j][2] < 0 ) {
// Now to check if it was the same user who logged in earlier - how?:
// Only way to differentiate is by comparing the transaction amounts and types.
if ( (bank[i][3] == bank[j][3]) && (bank[i][2] == -1*bank[j][2]) ) { // log-in code = -1 * log-out code.
answer++; // Number of transactions that spanned from before 12:00 till after 13:00.
// Remember, a single person can't have multiple log-ins at the same time. ( assumption )
}
}
}
}
}
}

Program crashes, Tree too large

I'm trying to answer this problem as an exercise:
here are set of coins of {50,25,10,5,1} cents in a box.Write a program to find the number of ways a 1 dollar can be created by grouping the coins.
My solution involves making a tree with each edge having one of the values above. Each node would then hold a sum of the coins. I could then populate this tree and look for leaves that add up to 100. So here is my code
class TrieNode
{
public:
TrieNode(TrieNode* Parent=NULL,int sum=0,TrieNode* FirstChild=NULL,int children=0, bool key =false )
:pParent(Parent),pChild(FirstChild),isKey(key),Sum(sum),NoChildren(children)
{
if(Sum==100)
isKey=true;
}
void SetChildren(int children)
{
pChild = new TrieNode[children]();
NoChildren=children;
}
~TrieNode(void);
//pointers
TrieNode* pParent;
TrieNode* pChild;
int NoChildren;
bool isKey;
int Sum;
};
void Populate(TrieNode* Root, int coins[],int size)
{
//Set children
Root->SetChildren(size);
//add children
for(int i=0;i<size;i++)
{
TrieNode* child = &Root->pChild[0];
int c = Root->Sum+coins[i];
if(c<=100)
{
child = new TrieNode(Root,c);
if(!child->isKey) //recursively populate if not a key
Populate(child,coins,size);
}
else
child = NULL;
}
}
int getNumKeys(TrieNode* Root)
{
int keys=0;
if(Root == NULL)
return 0;
//increment keys if this is a key
if(Root->isKey)
keys++;
for(int i=0; i<Root->NoChildren;i++)
{
keys+= getNumKeys(&Root->pChild[i]);
}
return keys;
}
int _tmain(int argc, _TCHAR* argv[])
{
TrieNode* RootNode = new TrieNode(NULL,0);
int coins[] = {50,25,10,5,1};
int size = 5;
Populate(RootNode,coins,size);
int combos = getNumKeys(RootNode);
printf("%i",combos);
return 0;
}
The problem is that the tree is so huge that after a few seconds the program crashes. I'm running this on a windows 7, quad core, with 8gb ram. A rough calculation tells me I should have enough memory.
Are my calculations incorrect?
Does the OS limit how much memory I have access to?
Can I fix it while still using this solution?
All feedback is appreciated. Thanks.
Edit1:
I have verified that the above approach is wrong. By trying to build a tree with a set of only 1 coin.
coins[] = {1};
I found that the algorithm still failed.
After reading the post from Lenik and from João Menighin
I came up with this solution that ties both Ideas together to make a recursive solution
which takes any sized array
//N is the total the coins have to amount to
int getComobs(int coins[], int size,int N)
{
//write base cases
//if array empty | coin value is zero or N is zero
if(size==0 || coins[0]==0 ||N==0)
return 0;
int thisCoin = coins[0];
int atMost = N / thisCoin ;
//if only 1 coin denomination
if(size==1)
{
//if all coins fit in N
if(N%thisCoin==0)
return 1;
else
return 0;
}
int combos =0;
//write recursion
for(int denomination =0; denomination<atMost;denomination++)
{
coins++;//reduce array ptr
combos+= getComobs(coins, size-1,N-denomination*thisCoin);
coins--;//increment array ptr
}
return combos;
}
Thanks for all the feedback
Tree solution is totally wrong for this problem. It's like catching 10e6 tigers and then let go all of them but one, just because you need a single tiger. Very time and memory consuming -- 99.999% of your nodes are useless and should be ignored in the first place.
Here's another approach:
notice your cannot make a dollar to contain more than two 50 cents
notice again your cannot make a dollar to contain more than four 25 cent coins
notice... (you get the idea?)
Then your solution is simple:
for( int fifty=0; fifty<3; fifty++) {
for( int quarters=0; quarters<5; quarters++) {
for( int dimes=0; dimes<11; dimes++) {
for( int nickels=0; nickels<21; nickels++) {
int sum = fifty * 50 + quarters * 25 + dimes * 10 + nickels * 5;
if( sum <= 100 ) counter++; // here's a combination!!
}
}
}
}
You may ask, why did not I do anything about single cent coins? The answer is simple, as soon as the sum is less than 100, the rest is filled with 1 cents.
ps. hope this solution is not too simple =)
Ok, this is not a full answer but might help you.
You can try perform (what i call) a sanity check.
Put a static counter in TrieNode for every node created, and see how large it grows. If you did some calculations you should be able to tell if it goes to some insane values.
The system can limit the memory available, however it would be really bizarre. Usually the user/admin can set such limits for some purposes. This happens often in dedicated multi-user systems. Other thing could be having a 32bit app in 64bit windows environment. Then mem limit would be 4GB, however this would also be really strange. Any I don't think being limited by the OS is an issue here.
On a side note. I hope you do realize that you kinda defeated all object oriented programming concept with this code :).
I need more time to analyze your code, but for now I can tell that this is a classic Dynamic Programming problem. You may find some interesting texts here:
http://www.algorithmist.com/index.php/Coin_Change
and here
http://www.ccs.neu.edu/home/jaa/CSG713.04F/Information/Handouts/dyn_prog.pdf
There is a much easier way to find a solution:
#include <iostream>
#include <cstring>
using namespace std;
int main() {
int w[101];
memset(w, 0, sizeof(w));
w[0] = 1;
int d[] = {1, 5, 10, 25, 50};
for (int i = 0 ; i != 5 ; i++) {
for (int k = d[i] ; k <= 100 ; k++) {
w[k] += w[k-d[i]];
}
}
cout << w[100] << endl;
return 0;
}
(link to ideone)
The idea is to incrementally build the number of ways to make change by adding coins in progressively larger denomination. Each iteration of the outer loop goes through the results that we already have, and for each amount that can be constructed using the newly added coin adds the number of ways the combination that is smaller by the value of the current coin can be constructed. For example, if the current coin is 5 and the current amount is 7, the algorithm looks up the number of ways that 2 can be constructed, and adds it to the number of ways that 7 can be constructed. If the current coin is 25 and the current amount is 73, the algorithm looks up the number of ways to construct 48 (73-25) to the previously found number of ways to construct 73. In the end, the number in w[100] represents the number of ways to make one dollar (292 ways).
I really do believe someone has to put the most efficient and simple possible implementation, it is an improvement on lenik's answer:
Memory: Constant
Running time: Considering 100 as n, then running time is about O(n (lg(n))) <-I am unsure
for(int fifty=0; fifty <= 100; fifty+=50)
for(int quarters=0; quarters <= (100 - fifty); quarters+=25)
for(int dimes=0; dimes <= (100 - fifty - quarters); dimes+=10)
counter += 1 + (100 - fifty - quarters - dimes)/5;
I think this can be solved in constant time, because any sequence sum can be represented with a linear formula.
Problem might be infinite recursion. You are not incrementing c any where and loop runs with c<=100
Edit 1: I am not sure if
int c = Root->Sum+coins[i];
is actually taking it beyond 100. Please verify that
Edit 2: I missed the Sum being initialized correctly and it was corrected in the comments below.
Edit 3: Method to debug -
One more thing that you can do to help is, Write a print function for this tree or rather print on each level as it progresses deeper in the existing code. Add a counter which terminates loop after say total 10 iterations. The prints would tell you if you are getting garbage values or your c is gradually increasing in a right direction.

C+ program involving functions...Please help me

I am having a hard time with two functions. Here are the project instructions:
Assignment:
Write a program which keeps track of the number of roaches in two adjacent houses for a number of weeks. The count of the roaches in the houses will be determined by the following:
The initial count of roaches for each house is a random number between 10 and 100.
Each week, the number of roaches increases by 30%.
The two houses share a wall, through which the roaches may migrate from one to the other. In a given week, if one house has more roaches than the other, roaches from the house with the higher population migrate to the house with the lower population. Specifically, 30% of the difference (rounded down) in population migrates.
Every four weeks, one of the houses is visited by an exterminator, resulting in a 90% reduction (rounded down) in the number of roaches in that house.
Here's my code:
#include <iostream>
#include <cmath>
using namespace std;
int house, increase, roaches, moreRoaches, fewerRoaches, filthyBeasts, change; // My variables for my four functions
int initialCount(int house);
int weeklyIncrease(int increase);
double roachesMigration(int moreRoaches, int fewerRoaches, int change);
int exterminationTime (int filthyBeasts);
// My four function prototypes
int main()
{
int houseA, houseB;
houseA = initialCount(houseA); //Initializing the initial count of House A.
houseB = initialCount(houseB); //Initializing the initial count of House B.
int week = 0;
for (week = 0; week < 11; week++) // My for loop iterating up to 11 weeks.
{
houseA = weeklyIncrease(houseA);
houseB = weeklyIncrease(houseB);
cout << "For week " << week << ", the total number of roaches in House A is " << houseA << endl;
cout << "For week " << week << ", the total number of roaches in House B is " << houseB << endl;
if((houseA > houseB)) // Migration option 1
{
roachesMigration(moreRoaches, fewerRoaches, change);
}
else if((houseB > houseA)) // Migration option 2
{
roachesMigration(moreRoaches, fewerRoaches, change);
}
if ((week + 1) % 4 == 0) // It's extermination time!
{
if ((rand() % 2) == 0) // Get a random number between 0 and 1.
{
houseB = exterminationTime(houseB);
}
else
{
houseA = exterminationTime(houseA);
}
}
}
return 0;
}
int initialCount(int house) // Initializing both houses to random numbers between 10 and 100.
{
int num;
num = (rand() % 91) + 10;
return num;
}
int weeklyIncrease(int increaseHouses) // Increasing the roaches in both houses by 30% weekly.
{
int increase = 0;
increase = (increaseHouses * .3) + increaseHouses;
return increase;
}
double roachesMigration(int moreRoaches, int fewerRoaches, int change)
{
more -= change;
fewer += change;
change = ((more - fewer) * .3);
return change;
}
int exterminationTime(int filthyBeasts) // Getting rid of the filthy little beasts!
{
filthyBeasts = (filthyBeasts * .1);
return filthyBeasts;
}
The issues are with the migration and extermination functions. My code runs fine, but at weeks 4 and 8, the randomly selected house should get exterminated, and the number of roaches in that house should be 90% less than the previous week. What do you guys think I should do to correct these issues? I really need all the help I can get!
Regarding this line:
roachesMigration(change);
change is not declared in your main function, hence the error. Also, roachesMigration function expects 3 parameters and not 1.
The variable change is not a global variable, but appears inside main (so it has no meaning inside main).
Your roachesMigration fonction is declared with three formal arguments (without default values), but you use it with one actual argument.
Ask your compiler to give you all the warnings and to produce debugging information (g++ -Wall -g on Linux). Improve the code till you get no warnings.
Learn to use a debugger (e.g. gdb on Linux).
Have fun.
Depending on the instructor, you will get zero marks for this code, even if you can get it to work perfectly! This is because you have not used any object orientated design in building your code. In C++, that means classes.
What sort of object do you need for this problem. A house!
What sort of attribute should your house have? Roaches!
So something like this:
class cHouse
{
int MyRoachCount;
...
};
If you start fresh, like this, you will find things start to fall neatly into place.
One possible way to handle the migration is like this pseudocode:
// compute size of migration
count = migration(houseA, houseB)
if (houseA < houseB)
add count to houseA
subtract count from houseB
else
add count to houseB
subtract count from houseA

Fastest container or algorithm for unique reusable ids in C++

I have a need for unique reusable ids. The user can choose his own ids or he can ask for a free one. The API is basically
class IdManager {
public:
int AllocateId(); // Allocates an id
void FreeId(int id); // Frees an id so it can be used again
bool MarkAsUsed(int id); // Let's the user register an id.
// returns false if the id was already used.
bool IsUsed(int id); // Returns true if id is used.
};
Assume ids happen to start at 1 and progress, 2, 3, etc. This is not a requirement, just to help illustrate.
IdManager mgr;
mgr.MarkAsUsed(3);
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());
Would print
1
2
4
Because id 3 has already been declared used.
What's the best container / algorithm to both remember which ids are used AND find a free id?
If you want to know the a specific use case, OpenGL's glGenTextures, glBindTexture and glDeleteTextures are equivalent to AllocateId, MarkAsUsed and FreeId
My idea is to use std::set and Boost.interval so IdManager will hold a set of non-overlapping intervals of free IDs.
AllocateId() is very simple and very quick and just returns the left boundary of the first free interval. Other two methods are slightly more difficult because it might be necessary to split an existing interval or to merge two adjacent intervals. However they are also quite fast.
So this is an illustration of the idea of using intervals:
IdManager mgr; // Now there is one interval of free IDs: [1..MAX_INT]
mgr.MarkAsUsed(3);// Now there are two interval of free IDs: [1..2], [4..MAX_INT]
mgr.AllocateId(); // two intervals: [2..2], [4..MAX_INT]
mgr.AllocateId(); // Now there is one interval: [4..MAX_INT]
mgr.AllocateId(); // Now there is one interval: [5..MAX_INT]
This is code itself:
#include <boost/numeric/interval.hpp>
#include <limits>
#include <set>
#include <iostream>
class id_interval
{
public:
id_interval(int ll, int uu) : value_(ll,uu) {}
bool operator < (const id_interval& ) const;
int left() const { return value_.lower(); }
int right() const { return value_.upper(); }
private:
boost::numeric::interval<int> value_;
};
class IdManager {
public:
IdManager();
int AllocateId(); // Allocates an id
void FreeId(int id); // Frees an id so it can be used again
bool MarkAsUsed(int id); // Let's the user register an id.
private:
typedef std::set<id_interval> id_intervals_t;
id_intervals_t free_;
};
IdManager::IdManager()
{
free_.insert(id_interval(1, std::numeric_limits<int>::max()));
}
int IdManager::AllocateId()
{
id_interval first = *(free_.begin());
int free_id = first.left();
free_.erase(free_.begin());
if (first.left() + 1 <= first.right()) {
free_.insert(id_interval(first.left() + 1 , first.right()));
}
return free_id;
}
bool IdManager::MarkAsUsed(int id)
{
id_intervals_t::iterator it = free_.find(id_interval(id,id));
if (it == free_.end()) {
return false;
} else {
id_interval free_interval = *(it);
free_.erase (it);
if (free_interval.left() < id) {
free_.insert(id_interval(free_interval.left(), id-1));
}
if (id +1 <= free_interval.right() ) {
free_.insert(id_interval(id+1, free_interval.right()));
}
return true;
}
}
void IdManager::FreeId(int id)
{
id_intervals_t::iterator it = free_.find(id_interval(id,id));
if (it != free_.end() && it->left() <= id && it->right() > id) {
return ;
}
it = free_.upper_bound(id_interval(id,id));
if (it == free_.end()) {
return ;
} else {
id_interval free_interval = *(it);
if (id + 1 != free_interval.left()) {
free_.insert(id_interval(id, id));
} else {
if (it != free_.begin()) {
id_intervals_t::iterator it_2 = it;
--it_2;
if (it_2->right() + 1 == id ) {
id_interval free_interval_2 = *(it_2);
free_.erase(it);
free_.erase(it_2);
free_.insert(
id_interval(free_interval_2.left(),
free_interval.right()));
} else {
free_.erase(it);
free_.insert(id_interval(id, free_interval.right()));
}
} else {
free_.erase(it);
free_.insert(id_interval(id, free_interval.right()));
}
}
}
}
bool id_interval::operator < (const id_interval& s) const
{
return
(value_.lower() < s.value_.lower()) &&
(value_.upper() < s.value_.lower());
}
int main()
{
IdManager mgr;
mgr.MarkAsUsed(3);
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());
return 0;
}
It would be good to know how many ids you're supposed to keep track of. If there's only a hundred or so, a simple set would do, with linear traversal to get a new id. If it's more like a few thousands, then of course the linear traversal will become a performance killer, especially considering the cache unfriendliness of the set.
Personally, I would go for the following:
set, which helps keeping track of the ids easily O(log N)
proposing the new id as the current maximum + 1... O(1)
If you don't allocate (in the lifetime of the application) more than max<int>() ids, it should be fine, otherwise... use a larger type (make it unsigned, use a long or long long) that's the easiest to begin with.
And if it does not suffice, leave me a comment and I'll edit and search for more complicated solutions. But the more complicated the book-keeping, the longer it'll take to execute in practice and the higher the chances of making a mistake.
But I don't think you have to guarantee the id must starts from 1. You can just make sure the available id must be larger than all allocated ids.
Like if the 3 is registered first, then the next available id can just be 4. I don't think it is necessary to use 1.
I'm assuming that you want to be able to use all available values for the Id type and that you want to reuse freed Ids? I'm also assuming that you'll lock the collection if you're using it from more than one thread...
I'd create a class with a set to store the allocated ids, a list to store the free ids and a max allocated value to prevent me having to preload the free id list with every available id.
So you start off with an empty set of allocated ids and empty list of free ids and the max allocated as 0. You allocate, take the head of the free list if there is one, else take max, check it's not in your set of allocated ids as it might be if someone reserved it, if it is, increment max and try again, if not add it to the set and return it.
When you free an id you simply check it's in your set and if so push it on your free list.
To reserve an id you simply check the set and if not present add it.
This recycles ids quickly, which may or may not be good for you, that is, allocate(), free(), allocate() will give you the same id back if no other threads are accessing the collection.
Compressed vector. But I don't think any container would make noticeable difference.
Normally, i'd say stick to an simple implementation until you have an idea of which methods are used most. Premature tuning might prove wrong. Use the simple implementation, and log its use, then you can optimize from the functions that are used the most. No use in optimizing for quick removal or quick allocation if you only need a couple of hundred ids and a simple vector would be enough.
Similar to skwllsp, I'd keep track of the ranges that have not been allocated, but my methods are slightly different. The base container would be a map, with the key being the upper bound of the range and the value being the lower bound.
IdManager::IdManager()
{
m_map.insert(std::make_pair(std::numeric_limits<int>::max(), 1);
}
int IdManager::AllocateId()
{
assert(!m_map.empty());
MyMap::iterator p = m_map.begin();
int id = p->second;
++p->second;
if (p->second > p->first)
m_map.erase(p);
return id;
}
void IdManager::FreeId(int id)
{
// I'll fill this in later
}
bool IdManager::MarkAsUsed(int id)
{
MyMap::iterator p = m_map.lower_bound(id);
// return false if the ID is already allocated
if (p == m_map.end() || id < p->second || id > p->first)))
return false;
// first thunderstorm of the season, I'll leave this for now before the power glitches
}
bool IdManager::IsUsed(int id)
{
MyMap::iterator p = m_map.lower_bound(id);
return (p != m_map.end() && id >= p->second && id <= p->first);
}
So a friend pointed out that in this case a hash might be better. Most OpenGL programs don't use more than a few thousand ids so a hash with say 4096 slots is almost guaranteed to have only 1 or 2 entries per slot. There is some degenerate case where lots of ids might go in 1 slot but that's seriously unlikely. Using a hash would make AllocateID much slower but a set could be used for that. Allocating being slower is less important than InUse being fast for my use case.