Category Archives: Uncategorized

Design byte queue

Problem statement :

FIFO Queues In Fixed Memory
------------------------------------------------------------------------------

Solution Requirements
---------------------
Your solution should compile and be capable of managing a variable number of
FIFO byte queues, each with variable length, in a small, fixed amount of
memory.

You should provide implementations of the five functions within
QueueManager.cpp.

!! DO NOT MODIFY QueueManager.h !!

You can define additioanl functions within QueueManger.cpp as necessary,
but do not change the QueueManager interface.

Memory Restrictions
-------------------
- No memory dynamically allocated during program execution (new, malloc, etc.).
- All queues must share a single storage space of MAX_DATA_SIZE for their data.
- You may statically allocate as much additional memory as you choose, but
  remember that efficiency is important and that any tradeoffs associated
  with allocating additional storage should be explained.
- You should statically allocate whatever memory you're going to use within
  the QueueManager namespace in QueueManager.cpp.

Common Cases
------------
On average while your system is running, there will be a dozen or so queues
with an average of 100 or so bytes in each queue. Your functions may be asked
to create a larger number of queues with fewer bytes in each. Your functions
may be asked to create a smaller number of queues with more bytes in each.
There may be spikes in the number of queues allocated, or in the size of an
individual queue.  The performance of your code in each of these situations
will be taken into account.  Be sure to explain any tradeoffs you've made to
handle a given case.

Error Handling
--------------
If you are unable to satisfy a request due to lack of memory your code should
call a provided failure function (which will not return):

namespace QueueManager
{
  void onOutOfMemory();
}
 
If the caller makes any illegal request your code should call a provided 
failure function (which will not return):

namespace QueueManager
{
  void onIllegalOperation(const char* fmt = 0, ... );
}

Compilation and Sample Output
-----------------------------
Please compile your implementation of QueueManager.cpp against the provided 
QueueManager.h and main.cpp.

The output of the provided main() should be:

0 1
2 5
3 4 6

Additional Documentation Requirements
-------------------------------------
Please include a description of your implementation.  You should outline areas
in which you feel your solution could be improved given additional time and/or
resources.  

main.cpp

Code:

#include <iostream>
#include <list>
#include <vector>
#include <queue>
#include<set>
#include <algorithm>
#include <map>
#include <cstring>
#include <climits>
#include <stack>
#include <fstream>
#include <assert.h>
#include <memory>

#include "QueueManager.h"

using namespace std;

int main() {
    QueueManager::initializeQueueManager();
    QueueManager::QueueHandle qH =QueueManager::createQueue();
    QueueManager::QueueHandle qH1 =QueueManager::createQueue();
    QueueManager::QueueHandle qH2 =QueueManager::createQueue();
    QueueManager::enQueue(qH,'0');
    QueueManager::enQueue(qH,'1');
    QueueManager::enQueue(qH1,'2');
    QueueManager::enQueue(qH1,'5');
    QueueManager::enQueue(qH2,'3');
    QueueManager::enQueue(qH2,'4');
    QueueManager::enQueue(qH2,'6');

    cout << QueueManager::deQueue(qH) << "\t";
    cout << QueueManager::deQueue(qH) <<endl;

    cout << QueueManager::deQueue(qH1) << "\t";
    cout << QueueManager::deQueue(qH1) <<endl;

    cout << QueueManager::deQueue(qH2) << "\t";
    cout << QueueManager::deQueue(qH2) << "\t";
    cout << QueueManager::deQueue(qH2) <<endl;

    //unsigned  char ch2=QueueManager::deQueue(qH);
    QueueManager::destroyQueue(qH);

//    0 1
//    2 5
//    3 4 6

 return 0;

}

QueueManager.h
//

#define MAX_QUEUES    64
#define MAX_DATA_SIZE 2048

const int QUEUE_HEADER_SIZE = 4;
const int QUEUE_HEADER_STORAGE = 1; // offset at which the Q headers are stored
const int QUEUE_STORAGE_OFFSET = QUEUE_HEADER_STORAGE + QUEUE_HEADER_SIZE*MAX_QUEUES;

const int USABLE_BLOCK_BYTES = 15;

namespace QueueManager
{
    // Queue handles are a single byte.  Do not change the typedef!
    typedef unsigned char QueueHandle;

    // Performs any necessary initialization of the QueueManager.
    void initializeQueueManager();

    // Creates a FIFO byte queue, returns the handle for the created
    // queue.
    QueueHandle createQueue();

    // Destroy the queue specified by queueHandle.
    void destroyQueue (QueueHandle queueHandle);

    // Adds a new byte to the queue specified by queueHandle.
    void enQueue (QueueHandle queueHandle, unsigned char value);

    // Pops the next byte off the queue specified by queueHandle.
    unsigned char deQueue (QueueHandle queueHandle);

    // If you are unable to satisfy a request due to lack of memory your
    // code should call this provided failure function (which will not return):
    void onOutOfMemory();

    // If the caller makes any illegal request your code should call this
    // provided failure function (which will not return):
    void onIllegalOperation(const char* fmt , ... );
    // eg:
    //   int errorCode;
    //   ...
    //   onIllegalOperation("Error in createQueue: %d", errorCode);
}

QueueManager.cpp

//
//
#include <zconf.h>
#include <pthread.h>
//#include <UInts/semaphore.h>
#include <semaphore.h>
#include <iostream>
#include <cstring>
#include "QueueManager.h"
#include "list"
#include <unordered_map>
#include <cstdarg>
namespace QueueManager
{
    // TODO: statically allocate space for data and queue management.
    QueueHandle data[MAX_DATA_SIZE];
    typedef unsigned int UInt;
    typedef unsigned char StorageHandle;
    struct Q
    {
        UInt inUse       : 1;  // 1 if queue is alloc'd, 0 if not
        UInt headBlock   : 7;  // handle to storage block containing deque head
        UInt headOffset  : 4;  // offset of deque head within storage block
        UInt tailBlock   : 7;  // handle to storage block containing deque tail
        UInt tailOffset  : 4;  // offset of deque tail within storage block
        UInt bdBlock    : 7;  // storage list grows by linking after here,
        //   needed for const time
        UInt unused      : 2;
    };
    unsigned char alpha =
            '!';
    std::unordered_map<unsigned char,Q*> um;
    //Q* q=0;

    struct QStorage
    {
        StorageHandle nextBlock;
        unsigned char storage[USABLE_BLOCK_BYTES];
    };

    const int MAX_NUM_BLOCKS =
            (MAX_DATA_SIZE - QUEUE_HEADER_SIZE*MAX_QUEUES - 1) /
            sizeof(QStorage);

    void onOutOfMemory()
    {
        std::cout << "Sorry , We are out of memory";
    }

    char *convNumToBase(unsigned int num, int base)
    {
        static char rep[]= "0123456789ABCDEF";
        static char buf[50];
        char *ptr;

        ptr = &buf[49];
        *ptr = '\0';

        do
        {
            *--ptr = rep[num%base];
            num /= base;
        }while(num != 0);

        return(ptr);
    }

    void onIllegalOperation(const char* fmt = 0, ... ){
        const char *iter;
        unsigned int i;
        char *s;

        //Module 1: Initializing Myprintf's arguments
        va_list arg;
        va_start(arg, fmt);

        for(iter = fmt; *iter != '\0'; iter++)
        {
            while( *iter != '%' )
            {
                putchar(*iter);
                iter++;
            }

            iter++;

            //Module 2: Fetching and executing arguments
            switch(*iter)
            {
                case 'c' : i = va_arg(arg,int); //Fetch char argument
                    putchar(i);
                    break;

                case 'd' : i = va_arg(arg,int);  //Fetch Decimal/Integer argument
                    if(i<0)
                    {
                        i = -i;
                        putchar('-');
                    }
                    puts(convNumToBase(i,10));
                    break;

                case 'o': i = va_arg(arg,unsigned int); //Fetch Octal rep
                    puts(convNumToBase(i,8));
                    break;

                case 's': s = va_arg(arg,char *);  //Fetch string
                    puts(s);
                    break;

                case 'x': i = va_arg(arg,unsigned int); //Fetch Hexadecimal rep
                    puts(convNumToBase(i,16));
                    break;
            }
        }

        //Module 3: Closing argument list to necessary clean-up
        va_end(arg);
    }

    StorageHandle& Freelist()
    {
        return data[0];
    }

    QStorage* GetStorageFromHandle(StorageHandle handle)
    {
        if(handle != 0)
        {
            return reinterpret_cast<QStorage*>(
                    data + QUEUE_STORAGE_OFFSET + sizeof(QStorage)*(handle-1)
            );
        }
        else
        {
            return 0;
        }
    }

    void FreeBlock(StorageHandle block)
    {
        // Link the block back onto the front of the freelist
        GetStorageFromHandle(block)->nextBlock = Freelist();
        Freelist() = block;
    }

    StorageHandle AllocBlock()
    {
        // Pull front block off of freelist
        StorageHandle allocated = Freelist();

        if(allocated != 0)
        {
            Freelist() = GetStorageFromHandle(allocated)->nextBlock;
            GetStorageFromHandle(allocated)->nextBlock = 0;
        }
        else
        {
            QueueManager::onOutOfMemory();
        }

        return allocated;
    }

    // Performs any necessary initialization of the QueueManager.
    void initializeQueueManager()
    {
        // TODO: IMPLEMENT ME

        // Freelist handle initially points at storage block 0
        data[0] = 1;  // 0 is reserved for 'null'

        // Q headers initialized to 0
        memset(data + QUEUE_HEADER_STORAGE, 0, QUEUE_HEADER_SIZE*MAX_QUEUES);

        // Build the freelist, a single-linked list of storage blocks
        for(StorageHandle block = 1; block <= MAX_NUM_BLOCKS; ++block)
        {
            GetStorageFromHandle(block)->nextBlock = block + 1;
        }

        // Last block has 'null' handle
        GetStorageFromHandle(MAX_NUM_BLOCKS)->nextBlock = 0;

    }

    QueueHandle createQueue()
    {//konIllegalOperation
        // TODO: IMPLEMENT ME

        Q* q = 0;

        // Search for a queue header not currently in use
        for(int queueIndex = 0; queueIndex < MAX_QUEUES; ++queueIndex)
        {
            Q* queueHeader = reinterpret_cast<Q*>(
                    data + QUEUE_HEADER_STORAGE + queueIndex*QUEUE_HEADER_SIZE
            );
            if(!queueHeader->inUse)
            {
                q = queueHeader;
                break;
            }
        }

        if(q != 0)
        {
            // Mark this queue as 'in use'
            q->inUse = 1;

            // Allocate a block of storage for the queue to start with
            q->headBlock = AllocBlock();
            q->tailBlock = q->headBlock;
            q->bdBlock = q->headBlock;

            GetStorageFromHandle(q->headBlock)->nextBlock =
                    q->headBlock;

            //  the 'head' and 'tail' pointers of our circular buffer
            // are both set to the first element.
            q->headOffset = q->tailOffset = 0;
        }
        else
        {
            // Tried to create more than 64 queues
            const char* err = "Tried to create more than 64 queues";
            int errCode=1;
            QueueManager::onIllegalOperation("Error in createQueue: %d", errCode);
        }
        um[alpha]=q;
        return alpha++;

    }

    // Destroy the queue specified by queueHandle.
    void destroyQueue (QueueHandle queueHandle)
    {
        Q *q=um[queueHandle];
        //q->headBlock=queueHandle;
        // TODO: IMPLEMENT ME
        StorageHandle blockAfterbdBlock =
                GetStorageFromHandle(q->bdBlock)->nextBlock;
        GetStorageFromHandle(q->bdBlock)->nextBlock = Freelist();
        Freelist() = blockAfterbdBlock;

        // Mark this queue as not 'in use'
        q->inUse = 0;

    }

    // Adds a new byte to the queue specified by queueHandle.
    void enQueue (QueueHandle queueHandle, unsigned char value) {
        // TODO: IMPLEMENT ME
        Q* q =um[queueHandle];
//        q->tailBlock=queueHandle;

        QStorage *tailBlock = GetStorageFromHandle(q->tailBlock);
        tailBlock->storage[q->tailOffset] = value;

        // If the tail wraps to the head block but doesn't collide,
        // move the bud block to where the tail block used to be
        StorageHandle oldTailBlock = q->tailBlock;
        bool updatebdBlock = false;

        unsigned int newOffset = (q->tailOffset + 1) % USABLE_BLOCK_BYTES;
        if (newOffset < q->tailOffset) {
            // Wrap around to head block,
            q->tailBlock = q->headBlock;
            updatebdBlock = true;
        }
            q->tailOffset = newOffset;

            if (q->tailBlock == q->headBlock) {
                if (newOffset == q->headOffset) {
                    // No place to put the next byte, so we need to grow
                    StorageHandle newBlockHandle = AllocBlock();
                    QStorage *newBlock = GetStorageFromHandle(newBlockHandle);

                    // Copy trailing elements, if any, to new block
                    for (unsigned int copyOffset = 0; copyOffset < newOffset; ++copyOffset) {
                        newBlock->storage[copyOffset] =
                                GetStorageFromHandle(q->headBlock)->storage[copyOffset];
                    }

                    // If we just went off the end of a block, that block is the new
                    // bud block
                    if (updatebdBlock) {
                        q->bdBlock = oldTailBlock;
                    }

                    // Link up with new block
                    newBlock->nextBlock = q->headBlock;
                    GetStorageFromHandle(q->bdBlock)->nextBlock = newBlockHandle;
                    q->tailBlock = newBlockHandle;

                    updatebdBlock = false;
                }
            }

            if (updatebdBlock) {
                q->bdBlock = oldTailBlock;
            }
        um[queueHandle]=q;
    }
        // Pops the next byte off the queue specified by queueHandle.
        unsigned char deQueue(QueueHandle queueHandle) {
            // TODO: IMPLEMENT ME
            Q* q =um[queueHandle];
//        q->headBlock=queueHandle;
            if (q->headBlock == q->tailBlock && q->headOffset == q->tailOffset) {
                // Queue is empty!
                const char *err = "Queue is empty!";
                //const char * a = err;
                int errCode=2;
                QueueManager::onIllegalOperation("Error in deQueue: %d", errCode);
                fprintf(stderr, "%s\n", err);
            }
            // We always dequeue at the HEAD
            unsigned char bytePopped =
                    GetStorageFromHandle(q->headBlock)->storage[q->headOffset];

            unsigned int newOffset = (q->headOffset + 1) % USABLE_BLOCK_BYTES;

            if (newOffset < q->headOffset) {
                StorageHandle newHeadBlock =
                        GetStorageFromHandle(q->headBlock)->nextBlock;

                // If the block we just left wasn't the tail block...
                if (q->headBlock != q->tailBlock) {
                    // The block we just left is not being used at all.
                    //  it MUST  be immediately after
                    // the tail block. So we can unlink it, and free it.
                    GetStorageFromHandle(q->tailBlock)->nextBlock = newHeadBlock;
                    FreeBlock(q->headBlock);
                }

                q->headBlock = newHeadBlock;
            }

            q->headOffset = newOffset;

            // For debugging
            //PrintStatus(q);
            um[queueHandle]=q;
            return bytePopped;
        }
};

Further optimizations and execution process:

Implemetation details:
I have created an array based queue.Memory management is handled in the code itself and No memory dynamically allocated during program execution.
These queues are implemented as dequeue with head and tail pointers.Whenever a queue is created , a handle is created and this queue is mappedto that handle.

Both adding and removing a byte only causes a pointer shuffle and thus avoids performance impact.

======================== Compilation Steps:
g++ -c *.cpp -std=c++11
g++ *.o -o Queue.out

./Queue.out

Possible improvements:
I feel my solution could be improved given additional time and/or
resources by extending this system to become highly concurrent. This could be done by thread and event driven
programming where multiple queuing request can be processed in parallel.
Threaded solution can be further improved with a policy based design. This policy based design could be used to
create a generic thread pool based on various thresholds and policies.
Also separate thread pools can be maintained for different queues and load balancing can be introduced based on
pending data in queues and available threads in thread pools.
Further , This solution was asked for only Queues that stores byte , these queues can be extended to queues storing multiple data-types using
templates.
Finally , We could also introduce a Producer -Consumer model to Queue If a particular variant is used in designing sockets where producer is sending data and consumer is waiting for data to arrive and can consume it immediately.