Starting from:

$30

EECS281- Project 2 Mine All Mine (Priority Queues) Solved

This project is broken into two parts (A and B). Part A is your main()​            ,​ using the STL std::priority_queue<>; part B is you implementing several different versions of a priority queue.​    

Project Goals 
●       Understand and implement several kinds of priority queues.

●       Be able to independently read, learn about, and implement an unfamiliar data structure.

●       Be able to develop efficient data structures and algorithms.

●       Implement an interface that uses templated “generic” code.

●       Implement an interface that uses inheritance and basic dynamic polymorphism.

●       Become more proficient at testing and debugging your code.

 

Part A: Gold Mining
For Part A, you should always use std::priority_queue<>​  , not your templates from Part B.​            In Part A you probably do not need or want to use pointers; in Part B you will have to (for the Pairing Heap). 

Overview
You are an adventurous gold miner.  However, in your avarice you’ve ignored several safe-tunneling practices, and a recent earthquake has left you trapped!  Luckily, out of paranoia, you always carry ridiculous quantities of dynamite sticks with you.  You need to blast your way out and make the most of each dynamite stick by blasting the piles of rubble which take the fewest sticks to destroy.  

Breaking Out of the Mine
The mine you are trapped in can be represented with a 2-dimensional grid.  There are 2 types of tiles:

●       Tiles containing rubble.

○    Think of cleared tiles as containing 0 rubble.  A tile in the mine could contain 0 before you clear it, that means that it never contained rubble.

●       Tiles containing TNT.

 

You (the miner) start on a specified tile.  At every iteration, you will attempt to blast away the “easiest” tile you can “access”, until you escape!

 

Definition of Discovered 
The mine is very dark and you cannot see very far. When standing on a clear tile there are only four tiles that you can discover from your current tile: up, down, left and right (except for edge-of-map conditions). This is true in every case except for the start of the simulation, when you can only discover the starting tile. Adding tiles to the primary priority queue counts as "discovering" them.  They can never be discovered (added to the primary PQ) twice.

 

Definition of Investigating
The priority queue will always tell you what your “next” tile will be.  Investigating is taking the “next” tile from the priority queue and making it your “current” location. When you investigate a tile, you must clear it if it contains rubble or TNT.  After clearing the tile, you can then discover any of the four tiles visible from your current location, add them to the priority queue, etc.  You use the priority queue to remember tiles that you have discovered, but have not yet investigated.

 

Definition of Easiest Tile
The easiest tile you can reach is defined as follows, in the stated order:

1.     Any TNT tile you can reach.

2.     The lowest rubble-value tile you can reach.

 

Tie-breaking
In the event of ties (two TNT tiles or two rubble tiles with the same rubble-value):

1.     Investigate the tile with the lower column coordinate first.

2.     If the tiles have the same column coordinate, investigate the tile with the lower row coordinate.

 

Clearing Tiles 
When clearing away rubble tiles, the tile turns into a cleared tile.

When clearing away TNT tiles, the following happens:

●       The TNT tile becomes a cleared tile.

●       All tiles touching the TNT tile are also “cleared”

                       ○            If a TNT tile is touching another TNT tile, this will cause a chain explosion.

                       ○    Diagonals are not considered for TNT explosions.

 

Definition of Escape 

The miner escapes when their current tile has been cleared and is at the edge of the grid.

Example
In the following example, you start at position​ [1,2]​ (row 1, column 2).  The tiles that the miner has investigated are ​red​ and the tiles that the miner has discovered are ​blue​. The tile that the miner just cleared is ​red and underlined​. Note: all cleared tiles will be 0, because you must clear a tile as part of investigating it. Positive integers signify rubble tiles (0 is a clear tile) and the value -1 signifies a TNT tile.  

 

Please note: ​This example mine is for illustrative purposes and is not the same as the input file described in the ​Full I/O Example​ section​.​ To make things clearer, there are ​bold ​indices on the edges that refer to row and column number - they are not a part of the actual input file.

 

    0   1   2   3   4 0​  20  20  20  20  20 1​  20 100  ​10​  20  15 2 ​ 20  15   5   0  20 

3​  20  20   0  -1 100 

4​ 100  -1  -1  20  20  

 

At the first iteration, the only tile that can be discovered is the starting location, ​[1,2]​. The miner clears this tile and then can discover other tiles.

 

    0   1   2   3   4 0​  20  20  ​20​  ​20  20 1​  20 ​100   ​0​  20​  15 2​  20  15  ​ ​5​   0  20 3​  20  20   0  -1 100 

4​ 100  -1  -1  20  20 

 

Next, the miner will investigate and clear tile ​[2,2]​ because there are no TNT tiles in view and it has the lowest rubble-value.  Clearing that tile allows us to discover more tiles!  

 

    0   1   2   3   4  
0​  20  20  ​20​  20  20 1​  20 ​ ​100  ​0​  20​  15 2​  20  ​15​  ​ ​0​   ​0​  ​20 3​  20  20​   0  ​-1 100 4​ 100  -1  -1  20  20 

 

The tiles [2,1], [2, 3], and [3,2] are now discoverable (but still uninvestigated). The miner then investigates tile ​[3,2]​. Both this tile and tile ​[2,3]​ have an equally low level of difficulty. The tile ​[3,2] is chosen because its lower column value of ​2​ breaks the tie. 

      

    0   1   2   3   4  
0​  20  20  ​20​  20  20 1​  20 ​ ​100  ​0​  20​  15 2​  20  ​15​  ​ ​0​   ​0​  ​20 3​  20  ​20   ​0​  -1​ 100 4​ 100  -1  ​-1​  20  20 

 

At this point, there are two TNT tiles which could be investigated next!  However, due to the tie-breaking rules, the miner will choose to blow up the TNT at ​[4,2]​ instead of [3​ ,3]​ (this choice is made because it is at the top of the priority queue).  Blowing up the TNT tile at ​[4,2]​ clears all the tiles touching it, creating a chain reaction with the TNT tile at ​[4,1]​.  After all the TNT explosions have been resolved, the grid looks like the following: 

    0   1   2   3   4  
0​  20  20  ​20​  20  20 1​  20 ​ ​100  ​0​  20​  15 2​  20  ​15​  ​ ​0​   ​0​  ​20 3​  20​  ​ ​0   ​0​  -1​ 100 

4​   ​0​   ​0​   ​0​  ​ 0​  ​20 

 

An explosion makes tiles ​discovered​ but does not ​investigate​ the tile (or discover around it). So, for example, [3, 1] was blown up by TNT and thus discovered.  Therefore it is a candidate to be

investigated next, but [3, 0] (adjacent to it) is not. Since the current location is ​[4,2]​, and this tile is now cleared and on the edge of the map, we have escaped!

TNT Explosions  
As you will see in the ​Output​ section, you need to keep track of the order in which tiles are cleared.

When a TNT tile detonates, all tiles ​  that are cleared as a result of the TNT detonation (including chain​       reactions) are cleared in order from “easiest” to “hardest” (as defined in Breaking Out of the Mine​   ,​ including tie-breaker rules).  These tiles should also be discovered.  As stated in Breaking Out of the​   

Mine, do ​ NOT​  consider diagonals; even TNT only destroys rubble piles up, down, left and right of it.​                                                                                        

 

When a TNT detonation occurs, you should use a priority queue to determine detonation order.  Push all the detonated tiles into a separate TNT priority queue, and then pop them out in priority order.  You need to use some type of priority queue, because TNT blows up other TNT first, followed by smaller piles of rubble, etc.

 

Notice that, as you progress through your TNT priority queue, you may blow up a tile that is already waiting in your primary priority queue.  If this happens, you have to make sure that the new rubble value of 0 comes out of the primary PQ sooner than the old, non-0 value.  Think about how it will be possible for the primary PQ to actually know that a tile has changed! What if there were two entries for the same tile, and you ignore the second one?  See the Full I/O Example​                for more details.​ 

Command Line
Your program MineEscape​       should take the following case-sensitive command-line options:​        ●          -​ h, --help 

                       ○    Print a description of your executable and exit(0)​ .​

●       -s, --stats N 

○       An optional flag that tells the program it should print extra summarization statistics upon termination.  This optional flag has a required argument N.  Details are covered in the Output section.​    

●       -m, --median 

○       An optional flag that tells the program it should print the median difficulty of clearing a rubble tile that the miner has seen. Details are covered in the Output​  section.​             

●       -v, --verbose 

○       An optional flag that tells the program it should print out every rubble value (or TNT) as a tile is being cleared.  Details are covered in the Output​  section.​             

  

 Examples of legal command lines:

●       ./MineEscape -v < infile.txt 

●       ./MineEscape --stats 15 < infile.txt > outfile.txt 

  

We will not be error checking your command-line handling, but we expect your program to accept any valid combination of input parameters. 

 

Input and Output Redirection
In order to read an input file, you will use input redirection, just like you did in project 1. If you want to send your output to a file, you can also use output redirection, as seen in one of the command line examples above. The < redirects​ ​the file specified by the next command line argument to be the standard input (​stdin/cin​) for the program. The > redirects the standard output (​stdout/cout​) of the program to be printed to the file specified by the next command line argument.  

Input
Settings will be given from an input file, ​‘MINEFILE’​ (this input file will not necessarily be named ‘MINEFILE’​). There will be two different types of input: map (M) and pseudo-random (R).  Map input is for your personal debugging, but pseudorandom allows easier testing of large grids.

 

Map input mode (M) 
Input will be formatted as follows:

●       ‘​M​’ - A single character indicating that this file is in map format.

●       ‘​Size​’ - Positive integer number that specifies the size of the square grid  (20 means a grid with 20 rows and 20 columns).

●       ‘​Start​’ - Coordinate indicating the starting location, row followed by column (two integers).

 

Map input consists of a map of all the tiles in the mine.  Each tile will be separated from other tiles on the same line with whitespace (any number of spaces or tabs). There are 2 types of tiles:

●       Rubble tiles which are signified by an integer between 0 and 999 (0 means the tile is already clear of rubble).

●       TNT tiles, which are represented by the integer -1.  

Tiles are indexed as follows:

●       The tile in the top left corner is at location [0,0].

●       The tile in the bottom right corner is at location [Size-1,Size-1].

 

Example of M input (starting location is at [1,2], or row 1 column 2, underlined):

 



Size: 5 

Start: 1 2 

    9    0    9    3    3     6    9    6​    8    3​                9    4    1    9    0 

    2    0   -1   -1    9 

    8    3    9    7    5 

 

Pseudorandom Mode (R)  
 

Input will be formatted as follows:

●       'R'​ - character indicating that this file is in pseudorandom format.

●       'Size'​ - Number that specifies the size of the square grid (unsigned integer)

●       'Start' - ​Coordinate indicating the starting location (two unsigned integers, row first).

●       'Seed'​ - Number used to seed the random number generator (unsigned integer)

●       'Max_Rubble' ​-  The max rubble value a tile can have (unsigned integer)

●       'TNT' ​-  Chance that a generated tile will be a TNT tile (20 = 1 in 20 chance of a given tile being a TNT tile, 0 = no chance of TNT; also an unsigned integer)

 

Example of R input:

 



Size: 5 

Start: 1 2 

Seed: 0 

Max_Rubble: 10 

TNT: 5 

 

Generating your grid in R mode: 

Included in Canvas with the project spec are the files ​P2random.h​ and ​P2random.cpp​ that​ ​contain definitions for the following function:

void P2random::PR_init(std::stringstream& ss, unsigned int floor_size,                         unsigned int seed, unsigned int max_rubble,                        unsigned int tnt_chance);  

The function ​P2random::PR_init(...)​ will set the contents of the stringstream argument (​ss​) so that you can use it just like you would ​cin​ for M input mode.  You may find the following (incomplete) C++ code helpful in reducing code duplication.  Remember, ​DO NOT​ copy/paste from a PDF file, retype the code by hand!

 

    ​stringstream ss;     if (mode == "R") { 

        // Read some variables from cin 

        P2random::PR_init(ss, floor_size, seed, max_rubble, tnt_chance); 

    } // if 

 

    // If map mode is on, read from cin;     // otherwise read from the stringstream     istream &inputStream = (mode == "M") ? cin : ss; 

 

From this point on, read from ​inputStream​; it doesn’t matter whether the mode is M or R!

 

The example R and M input files given above are equivalent. ​ That is, ​both​ should generate the exact same map!

 

Errors you must check for
You must make sure that the input file describes a valid mine by checking for each of the following:

●       The character on the first line of the file is either a ‘​M​’ or an ‘​R​’

●       The coordinate specifying the ‘​Start’​ is a valid location given the ‘​Size​’ of the grid

 

If you detect invalid input at any time during the program, print a helpful message to ​cerr​ and exit(1)​.  ​You do not need to check for input errors not explicitly mentioned here. 

 

Look in the Part A samples file, ​Error_messages.txt​: if your program performs an ​exit(1)​ and produces one of those error messages for a valid test case, we'll show you your error output (to help you debug).

Output
Default Output 
After completing the escape, your program should ​always​ print a summary line: 

 

Cleared ​<NUM>​ tiles containing ​<AMOUNT>​ rubble and escaped.

<NUM>     the number of tiles cleared.  This number ​does ​include tiles cleared by TNT, but does not include the TNT tile itself.

<AMOUNT> the total rubble cleared.  This number ​does ​include rubble cleared by TNT, but clearing (detonating) the TNT tile itself counts as 0 rubble cleared.

 

Verbose Output 
During the program execution, if the ​-v or​     ​ --verbose​ switch is present, each time you clear a tile whose rubble amount is greater than 0, you should print:

 

Cleared: ​<RUBBLE>​ at [​<ROW>​,​<COL>​] 

 

               <RUBBLE>          the amount of rubble being cleared

               <ROW> <COL>       the coordinates of the tile being cleared

 

Any TNT tiles blown up should display:

 

TNT explosion at ​[<ROW>,<COL>]​! 

 

Any rubble tiles cleared by TNT should add the words “by TNT” to the cleared line; the general format is:

 

Cleared by TNT: ​<RUBBLE>​ at [​<ROW>​,​<COL>​] 

 

The verbose mode output is produced as tiles are cleared, before the summary line.  Consider getting verbose mode working early: it involves very little code, and will help you debug if you have any incorrect output.

 

Median Output 
During the program execution, if the ​-m or​ ​ --median​ switch is present, each time you clear a tile, you should print:

 

Median difficulty of clearing rubble is: ​<MEDIAN>

<MEDIAN> The median value of rubble cleared so far. This includes rubble tiles cleared

by TNT, but does not include TNT tiles (-1 values should not be included in this calculation).  Tiles that start out clear (rubble value 0) are not included here.

 

Median output must display 2 decimal digits. You can use:

cout << std::fixed << std::setprecision(2); at the beginning of your program to guarantee this.

 

Stats Output 
After printing the summary line, if the ​-s​ or ​--stats ​option is specified print the following output (where ​N​ is the argument given to the -s flag on the command line):

  

A.    The line, ​“First tiles cleared:”​ (without quotes) followed by the first ​N​ tiles cleared in the following format:

 

<TILE_TYPE>​ at [​<ROW>​,​<COL>​] 

 

<TILE_TYPE>  the type of the tile being cleared.  If it is a rubble  tile, this is the

rubble amount.  If this is a TNT tile, print ​“TNT”​ without the quotes 

<ROW>,<COL> the coordinates of the tile being cleared.

 

Remember​: when a TNT tile detonates or when there is a chain reaction, all the tiles are cleared from easiest to hardest.  Refer back to ​TNT Explosions​ for more details. 

 

B.    The line, ​“Last tiles cleared:”​ (without quotes) followed by the last ​N tiles cleared in the​         same format as part ​A​.  The ​last​ tile cleared should be printed first, followed by the second last, etc.  

 

C.    The line, ​“Easiest tiles cleared:”​ (without quotes) followed by the ​N​ easiest tiles you blew up in the same format as part ​A ​in ​descending order ​(easiest tile followed by next easiest tile)

 

D.    The line, ​“Hardest tiles cleared:”​ (without quotes)’ followed by the​ N​ hardest tiles you blew up in the same format as part ​A. in ​ ​ascending order​ (hardest tile followed by next hardest tile)

 

If you have cleared less than N tiles, then simply print as many as you can.  Tiles that start out clear (rubble value 0) are not included here.

Full I/O Example
Input (spec-M.txt​  and ​ spec-R.txt​ ):​ 

Equivalent input files (the R input file will generate a grid that looks just like the M input file): 

M                                      R 

Size: 5                                Size: 5 

Start: 1 2                             Start: 1 2 

    9    0    9    3    3              Seed: 0 

    6    9    6    8    3              Max_Rubble: 10 

    9    4    1    9    0              TNT: 5

    2    0   -1   -1    9 

    8    3    9    7    5 

Output: 

We ran the program with this command line:  ./MineEscape -v -m -s 10 < spec-M.txt​      The output is shown below, with the verbose mode​                highlighted in blue.  The median mode output is​          easy to identify, and statistics come after the “Cleared 6 tiles” summary line.  

Cleared: 6 at [1,2] 

Median difficulty of clearing rubble is: 6.00 Cleared: 1 at [2,2] 

Median difficulty of clearing rubble is: 3.50 

TNT explosion at [3,2]! 

TNT explosion at [3,3]! 

Cleared by TNT: 7 at [4,3] 

Median difficulty of clearing rubble is: 6.00 Cleared by TNT: 9 at [4,2] 

Median difficulty of clearing rubble is: 6.50 

Cleared by TNT: 9 at [2,3] 

Median difficulty of clearing rubble is: 7.00 Cleared by TNT: 9 at [3,4] 

Median difficulty of clearing rubble is: 8.00 Cleared 6 tiles containing 41 rubble and escaped. 

First tiles cleared: 

6 at [1,2] 

1 at [2,2] 

TNT at [3,2] 

TNT at [3,3] 

7 at [4,3] 

9 at [4,2] 

9 at [2,3] 

9 at [3,4] Last tiles cleared: 

9 at [3,4] 

9 at [2,3] 

9 at [4,2] 

7 at [4,3] 

TNT at [3,3] 

TNT at [3,2] 

1 at [2,2] 

6 at [1,2] 

Easiest tiles cleared: 

TNT at [3,2] 

TNT at [3,3] 

1 at [2,2] 

6  at [1,2] 

7  at [4,3] 

9 at [4,2] 

9 at [2,3] 

9 at [3,4] 

Hardest tiles cleared: 

9 at [3,4] 

9 at [2,3] 

9 at [4,2] 

7 at [4,3] 

6 at [1,2] 

1 at [2,2] 

TNT at [3,3] 

TNT at [3,2] 

       

PART B: Priority Queue Implementation
For this project, you are required to implement and use your own priority queue containers.  You will implement a​ “sorted array priority queue”​, a ​“binary heap priority queue”​, and a ​“pairing heap priority queue”​ which implement the interface defined in ​Eecs281PQ.h​.  

 

To implement these priority queues, you will need to fill in separate header files, ​SortedPQ.h​,

BinaryPQ.h​, and ​PairingPQ.h​, containing all the definitions for the functions declared in

Eecs281PQ.h​.  We have provided these files with empty function definitions for you to fill in. You may also add any additional functions needed to maintain the priority queue.

 

We provide a very bad priority queue implementation called the “​Unordered priority queue​” in

UnorderedPQ.h​, which does a linear search for the most extreme element each time it is requested. You can look at the code in this priority queue to see the use of ​this->compare()​ (more on that below), how to write your constructors, etc.  There’s also an ​UnorderedFastPQ.h​ that is faster; look at how it uses ​mutable​ to accomplish that.. You can also use this priority queue to ensure that your other priority queues are returning elements in the correct order.  All priority queues should return elements in the same (priority) order no matter which implementation is being used.  

 

These files specify more information about each priority queue type, including runtime requirements for each member function and a general description of the container.  

 

You are not allowed to modify ​Eecs281PQ.h​ in any way.  Nor are you allowed to change the interface (names, parameters, return types) that we give you in any of the provided headers.  You are allowed to add your own private helper functions and variables to the other header files as needed, so long as you still follow the requirements outlined in both the spec and the comments in the provided files.

 

These priority queues can take in an optional comparison functor type, ​COMP_FUNCTOR​. Inside the classes, you can access an instance of ​COMP_FUNCTOR​ with ​this->compare()​. All of your priority queues must default to be MAX priority queues.  This means that if you use the default comparison functor with an integer PQ, ​std::less<int>​, the PQ will return the ​largest​ integer when you call top()​. Here, the definition of max (aka most extreme) is entirely dependent on the comparison functor.

For example, if you use ​std::greater<int>​, it will become a min-PQ. The definition is as follows:

 

If A is an arbitrary element in the priority queue, and ​top()​ returns the “most extreme” element. this->compare(top(), A)​ should always return false (A is “less extreme” than ​top()​). 

 

It might seem counterintuitive that ​std::less<>​ yields a max-PQ, but this is consistent with the way that the STL ​std::priority_queue<>​ works (and other STL functions that take custom comparators, like ​sort()​).

 

We will compile your priority queue implementations with our own code to ensure that you have correctly and fully implemented them. To ensure that this is possible (and that you do not lose credit for these tests), do not define a main function in one of the PQ headers, or any header file for that matter.

Eecs281PQ<> interface​  

Functions: 

push(const TYPE& val) //inserts a new element into the priority //queue 

top()             //returns the highest priority element in the 

//priority_queue 

pop()               //removes the highest priority element from  

//the priority queue 

size()                 //returns the size of the priority queue 

empty()                 //returns true if the priority queue is  

//empty, false otherwise 

      

Unordered Priority Queue
The ​unordered priority queue​ implements the priority queue interface by maintaining a vector.  This has already been implemented for you, and you can use the code to help you understand how to use the comparison functor, etc.  Complexities and details are in ​UnorderedPQ.h​ and UnorderedFastPQ.h​      ​.

Sorted Priority Queue
The ​sorted priority queue​ implements the priority queue interface by maintaining a ​sorted ​vector. Complexities and details are in ​SortedPQ.h​.  This should be written almost entirely using the STL.  The number of lines of code needed to get this working is fairly low, generally <= 10.

Binary Heap Priority Queue
Binary heaps will be covered in lecture.  We also highly recommend reviewing Chapter 6 of the CLRS book. Complexities and details are in ​BinaryPQ.h​.  One issue that you may encounter is that the examples and code in the slides use 1-based indexing, but your code must store the values in a vector (where indexing starts at 0).  There are three possible solutions to this problem:

 

1)     Add a “dummy” element at index 0, make sure you never let them access it, and make sure that size()​ and ​empty()​ work properly.

2)     Translate the code from 1-based to 0-based.  This is the best solution but the hardest.

3)     Use a function to translate indices for you.  Instead of accessing ​data[i]​, use getElement(i)​.  The code for ​getElement()​ is given below (both versions are needed).

 

    // Translate 1-based indexing into a 0-based vector​            TYPE &getElement(std::size_t i) {         return data[i - 1]; 

    } // getElement() 

     const TYPE &getElement(std::size_t i) const {         return data[i - 1]; 

    } // getElement()

 

Pairing Priority Queue
Pairing heaps are an advanced heap data structure that can be quite fast. In order to implement the pairing priority queue, read the two papers we provide you describing the data structure. Complexity details can be found in PairingPQ.h​               . We have also included a couple of diagrams that may help you​             understand the tree structure of the pairing heap.

 

Below is the pairing heap modeled as a tree, in which each node is greater than each of its children:

  

To implement this structure, the pairing heap will use child and sibling pointers to have a structure like this:

  

Implementing the Priority Queues 
Look through the included header files: you need to add code in ​SortedPQ.h​, ​BinaryPQ.h​, and

PairingPQ.h​, and this is the order that we would suggest implementing the different priority queues. Each of these files has TODO comments where you need to make changes.  We wanted to provide you with files that would compile when you receive them, so some of the changes involve deleting and/or changing lines that were only placed there to make sure that they compile.  For example, if a function was supposed to return an integer, NOT having a return statement that returns an integer would produce a compiler error.  Also, functions which accept parameters have had the name of the parameter commented out (otherwise you would get an unused parameter error).  Look at UnorderedPQ.h​ as an example, it’s already done.

When you implement each priority queue, you cannot compare ​data​ yourself using the ​<​ operator.  You can still use ​<​ for comparisons such as a vector index to the size of the vector, but you must use the provided comparator for comparing the data stored inside your priority queue.  Notice that

Eecs281PQ.h​ contains a member variable named ​compare​ of type ​COMP​ (one of the templated class types).  Although the other classes inherit from ​Eecs281PQ.h​, you cannot access the ​compare​ member directly, you must always say ​this->compare()​ (this is due to a template inheriting from a template).

Notice that in ​UnorderedPQ<>​ it uses ​this->compare()​ by passing it to the ​max_element() algorithm to use for comparisons.

When you write the ​SortedPQ<>​ you cannot use ​binary_search()​ from the STL, but you wouldn’t want to: it only returns a ​bool​ to tell you if something is already in the container or not!  Instead use the lower_bound()​ algorithm (which returns an iterator), and you can also use the ​sort()​ algorithm -- you don’t have to write your own sorting function.  You do however have to pass the ​this->compare functor to both ​lower_bound()​ and ​sort()​.

The ​BinaryPQ<>​ is harder to write, and requires a more detailed and careful use of the comparison functor, and you have to know how one works to write one in the first place, even for ​UnorderedPQ<> to use. See the ​About Comparators​ section below.  

Compiling and Testing Priority Queues 
You are provided with a testing file, ​testPQ.cpp​. ​testPQ.cpp​ contains examples of unit tests you can run on your priority queues to ensure that they are correct; however, it is ​not​ a complete test of your priority queues; for example it does not test ​updatePriorities()​. It is especially lacking in testing the ​PairingPQ<>​ class, since it does not have any calls to ​addNode()​ or ​updateElt()​. You should add more tests to this source code file.

Using the 281 ​Makefile​, you can compile ​testPQ.cpp​ by typing in the terminal: ​make testPQ​. You may use your own ​Makefile​, but you will have to make sure it does not try to compile your driver program as well as the test program (i.e., use at your own risk).

 

Logistics
The std::priority_queue<>​      

The STL ​std::priority_queue<>​ data structure is basically an efficient implementation of the binary heap which you are also coding in ​BinaryPQ.h​. To declare a ​std::priority_queue<>​ you need to state either one or three types:

1)     The data type to be stored in the container.  If this type has a natural sort order that meets your needs, this is the only type required.

2)     The underlying container to use, usually just a ​std::vector<>​ of the first type. 3) The comparator to use to define what is considered the highest priority element.

If the type that you store in the container has a natural sort order (i.e. it supports ​operator<()​), the std::priority_queue<>​ will be a max-heap of the declared type. For example, if you just want to store integers, and have the largest integer be the highest priority:

std::priority_queue<int> pqMax; 

When you declare this, by default the underlying storage type is ​vector<int>​ and the default comparator is ​less<int>​.  If you want the smallest integer to be the highest priority:

std::priority_queue<int, vector<int>, greater<int>> pqMin; 

If you want to store something other than integers, define a custom comparator as described below.

About Comparators 
The functor must accept two of whatever is stored in your priority queue: if your PQ stores integers, the functor would accept two integers.  If your PQ stores pointers to units, your functor would accept two pointers to orders (actually two ​const​ pointers, since you don’t have to modify units to compare them).

Your functor receives two parameters, let’s call them ​a​ and ​b​.  It must always answer the following question: ​is the priority of ​a​ less than the priority of b​ ​?  What does lower priority mean?  It depends on your application.  For example, refer back to the ​Breaking Out of the Mine​ section: if you have multiple tiles in your priority queue, you determine priority based on smallest rubble value.  If rubble value is the same, break ties based on column or row number.  

When you would have wanted to write a comparison, such as:

    if (data[i] < data[j]) 

You would instead write:

    if (this->compare(data[i], data[j]) 

Your priority queues must work ​in general​. In general, a priority queue has no idea what kind of data is inside of it.  That’s why it uses ​this->compare()​ instead of ​<​.  What if you wanted to perform the comparison ​if (data[i] > data[j])​?  Use the following:

    ​if (this->compare(data[j], data[i]) 

Libraries and Restrictions
For part A, we encourage the use of the STL, with the exception of these prohibited features:

●       The thread/atomics libraries (e.g., boost, pthreads, etc) which spoil runtime measurements.

●       Smart pointers (both unique and shared).

 

You ​are​ allowed to use ​std::vector<>​, ​std::priority_queue<>​ and std::deque<>​                                                                                                                                           ​.

 

You are ​not​ allowed to use other STL containers. Specifically, this means that use of ​std::stack<>​, std::queue<>​, ​std::list<>​, ​std::set<>​, ​std::map<>​, ​std::unordered_set<>​, std::unordered_map<>​, and the ​‘multi’​ variants of the aforementioned containers are forbidden.

 

For part B,

 

You ​are​ allowed to use ​std::sort()​.

You ​are​ allowed to use ​std::lower_bound()​.

 

You are ​not​ allowed to use ​std::partition()​, ​std::partition_copy()​, std::partial_sort()​, ​std::stable_partition()​, ​std::make_heap()​, std::push_heap()​, ​std::pop_heap()​, ​std::sort_heap()​, ​std::qsort()​, or anything that trivializes the implementation of the binary heap.

 

You are ​not​ allowed to use the C++14 regular expressions library (it is not fully implemented on gcc

6.2) or the thread/atomics libraries (it spoils runtime measurements).

 

You are ​not​ allowed to use other libraries (eg: boost, pthread, etc).

  

Furthermore, you may ​not​ use any STL component that trivializes the implementation of your priority queues (if you are not sure about a specific function, ask us).

 

Your main program (part A) ​must​ use ​std::priority_queue<>​, but your PQ implementations (part B) ​must not​.


More products