CMSC216 Project 1: C Programming
- Due: 11:59pm Mon 29-Sep-2025
- Approximately 4.0% of total grade
- Submit to Gradescope Submission will open soon
- Projects are individual work: no sharing of code between students is allowed. Seek help from course staff if you get stuck for too long.
CODE DISTRIBUTION: p1-code.zip
VIDEO OVERVIEW: Not Yet Available
CHANGELOG: Empty
1 Introduction
Basic application programming in C is an essential step downward towards the lower levels of computing. This project explores fundamental aspects of getting work done in C:
- Dynamic memory management with
malloc()/free()
- Reading data from files in text format
- Displaying information to the screen
- Reading commands from users in interactive programs
- Building data structures with C
structs
The assignment is divided into several problems utilizing many of the above techniques.
- Problem 1 implements a few simple function surrounding a
struct
representing stock prices. - Problem 2 builds on the previous routines with more complex functionality related to the stock data.
- Problem 3 completes the application by coding a
main()
function to employ the stock functions to plot from the command line.
Problems build on each other and should be done in order to complete the project.
Each problem contributes to an application that will read stock price data in a simple text format and display that information in a text terminal as a primitive graph. The application is divided between a set of service functions and a main function which will call those functions to achieve the overall aim of the application.
2 Download Code and Setup
Download the code pack linked at the top of the page. Unzip this which will create a project folder. Create new files in this folder. Ultimately you will re-zip this folder to submit it.
File | State | Notes |
---|---|---|
Makefile |
Provided | Build file to compile all programs |
gradescope-submit |
Provided | Allows submission to Gradescope from the command line |
stock_funcs.c |
CREATE | Problem 1/2 functions to write |
stock_main.c |
CREATE | Problem 3 main function to complete |
stock_demo.c |
Provided | Problem 1/2 demo code to show some function invocations |
stock.h |
Provided | Problem 1/2 header file |
test_stock_funcs.c |
Testing | Testing file for Problems 1 & 2 |
TESTING | ||
testy |
Testing | Test running script |
test-results/ |
Testing | Directory in which temporary testing files are written |
test_stock1.org |
Testing | Problem 1 tests |
test_stock2.org |
Testing | Problem 2 tests |
test_stock3.org |
Testing | Problem 3 tests |
data/stock-ascending.txt |
Data | Data files testing |
data/stock-valley.txt |
Data | |
data/stock-jagged.txt |
Data | |
… | Data |
2.1 Makefile
A Makefile
is provided as part of this project. Building programs in
C is a bit tedious and most folks use build systems of which make
is the oldest. The instructions and dependencies to create programs
are written in a Makefile
which is then interpreted by the make
program which will run gcc
and other commands to create programs.
Use this Makefile
by issuing commands like make prob1
>> make help Typical usage is: > make # build all programs > make clean # remove all compiled items > make zip # create a zip file for submission > make prob1 # built targets associated with problem 1 > make test # run all tests > make test-prob2 # run test for problem 2 > make test-prob2 testnum=5 # run problem 2 test #5 only >> make prob2 # build problem 2 demo program gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_demo.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_demo stock_demo.o stock_funcs.o >> make clean # remove all programs/binary object files rm -f stock_main stock_demo test_stock_funcs *.o >> make prob3 # build problem 3 main program gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_main.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_main stock_main.o stock_funcs.o >> make clean # remove all programs/binary object files rm -f stock_main stock_demo test_stock_funcs *.o >> make # build all programs/objects for the assignment gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_main.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_main stock_main.o stock_funcs.o gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_demo.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_demo stock_demo.o stock_funcs.o gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o test_stock_funcs test_stock_funcs.c stock_funcs.o
You are not required to understand all that is in the Makefile
(yet)
but it is a very useful tool well worth your time to learn.
2.2 Automated Tests
Automated tests are included with the code distribution. These tests are known to work on grace.umd.edu only but in most cases they should run identically in Linux environments. They may work on the Windows Subsystem for Linux but no guarantees are made. They very unlikely to run on MacOS natively as Linux-specific tools are used.
The provided Makefile
allows automated tests to be run via calls
like make test-prob1
to test Problem 1 and make test-prob2
to test
Problem 2. See the transcript below.
>> make test-prob1 # run tests for problem 1, compiles required code first gcc -Wall -Wno-comment -Werror -g -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -o test_stock_funcs test_stock_funcs.c stock_funcs.o ./testy test_stock1.org ============================================================ == test_stock1.org : Problem 1 First 3 Functions in stock_funcs.c == Running 15 / 15 tests 1) stock_new : ok 2) stock_free1 : ok 3) stock_free2 : ok 4) stock_free3 : ok 5) stock_free4 : ok 6) stock_print1 : ok 7) stock_print2 : ok 8) stock_print3 : ok 9) stock_print4 : ok 10) stock_print5 : ok 11) stock_print_prices_0 : ok 12) stock_print_prices_1 : ok 13) stock_print_prices_2 : ok 14) stock_print_prices_3 : ok 15) stock_print_final : ok ============================================================ RESULTS: 15 / 15 tests passed >> make test-prob2 # run tests for problem 2 gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o test_stock_funcs test_stock_funcs.c stock_funcs.o gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_demo.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_demo stock_demo.o stock_funcs.o ./testy test_stock2.org ============================================================ == test_stock2.org : Problem 2 Remaining Functions in stock_funcs.c == Running 20 / 20 tests 1) stock_set_hilo1 : ok 2) stock_set_hilo2 : ok 3) stock_set_hilo3 : ok 4) stock_set_best1 : ok 5) stock_set_best2 : ok 6) stock_set_best3 : ok 7) stock_set_best4 : ok 8) count_lines : ok 9) stock_load1 : ok 10) stock_load2 : ok 11) stock_load3 : ok 12) stock_load pathological : ok 13) stock_plot1 : ok 14) stock_plot2 : ok 15) stock_plot3 : ok 16) stock_plot4 : ok 17) stock_plot5 : ok 18) stock_plot6 : ok 19) stock_plot7 : ok 20) stock_demo : ok ============================================================ RESULTS: 20 / 20 tests passed >> make test # run tests for all problems ...
Each problem describes specifically how tests can be run and how credit will be assigned.
Note that one can run a single test with the following make
invocation which sets testnum
.
>> make test-prob2 testnum=5
This is useful when debugging to limit the output and time it takes to check program results.
3 Outline of Code
The file stock_funcs.c
will contain most of the support functions
for the plotting program. An outline of these functions are presented
below. Note that each function has the Problem # to which it
belongs. The final main()
function should be written in the
stock_main.c
file.
You may add additional functions to those indicated ("helper" or "utility" functions) but these will not graded and the design of the project is oriented towards not needing additional functions.
// stock_funcs.c: support functions for the stock_main program. #include "stock.h" stock_t *stock_new(); // PROBLEM 1: Allocate a new stock struct and initialize its fields. // Integer fields like 'count' and 'lo_index' should be initialied to // -1. Pointer fields like 'prices' should be initialized to // NULL. The stock should be heap-allocated using malloc() and // returned. Since this is an allocation function, no use of 'free()' // should appear. void stock_free(stock_t *stock); // PROBLEM 1: Free a stock. Check the 'data_file' and 'prices' fields: // if they are non-NULL, then free them. Then free the pointer to // 'stock' itself. void stock_print(stock_t *stock); // PROBLEM 1: Prints data about a stock that is passed in via a // pointer. Uses the syntax ptr->field to access fields of the struct // pointed by 'stock'. Output follows the general convention: // // ==STOCK DATA== // data_file: data/stock-jagged.txt // count: 15 // prices: [103.00, 250.00, 133.00, ...] // lo_index: 8 // hi_index: 11 // best_buy: 8 // best_sell: 11 // profit: 232.00 // // Each line prints a field of the stock_t struct. In all cases, // floating point numbers are printed with 2 decimal digits of // accuracy. // // NULLS FOR FIELDS // The fields 'data_file' and 'prices' may be NULL in which case they // will be printed specially as in // // data_file: NULL // prices: NULL // // This requires a manual if/else check for NULL values for these // pointers. // // PRICES FIELD // When printing the 'prices' field, if the 'count' field is 0 to 3, // print the entire array as in // // prices: [] # count == 0 // prices: [70.00] # count == 1 // prices: [50.00, 90.00] # count == 2 // prices: [59.00, 45.00, 103.00] # count == 3 // // Otherwise, print the first 3 elements with a ... at the end as in // // prices: [10.00, 20.00, 30.00, ...] # count > 3 // // PROFIT // There is no 'profit' field in the struct. Instead, calculate the // profit as the difference between the price at the 'best_sell' index // and 'best_buy' index. If these indices are -1 indicating the best // buy/sell time is not known or not viable, print a proit of 0.0 void stock_set_hilo(stock_t *stock); // PROBLEM 2: Sets the index of 'lo_index' and 'hi_index' fields of // the stock to be the positions in 'prices' of the lowest and highest // values present in it. Uses a simple loop over the array 'prices' // which is 'count' elements long to examine each for high/low. If // 'count' is zero, makes no changes to 'lo_index' and 'hi_index'. int stock_set_best(stock_t *stock); // PROBLEM 2: Sets the 'best_buy' and 'best_sell' fields of 'stock'. // This corresponds to the pair which produces the best profit. On // determining the best buy/sell indices which produce a positive // profit, sets these fields in 'stock' and returns 0. If there is no // buy/sell point which would result in a positive profit, sets the // 'best_buy' and 'best_sell' indices to -1 and returns -1. Always // calculates the earliest buy/sell pair of indices that would get the // best profit: if 5,8 and 5,9 and 7,10 all give the same, maximal // profit, the best buy/sell indices are set to 5,7. // // ALGORITHM NOTES // One intuitive algorithm to compute this is to try every possible // buy index (outer loop) and every possible sell index after it // (inner loop) looking for the maximum profit produced in it. This is // a O(N^2) algorithm with N=count. Using this algorithm is a good // start. Some MAKEUP CREDIT will be awarded for implementing a more // efficient, O(N) algorithm here. See the specification for more details. int count_lines(char *filename); // PROBLEM 2: Opens file named 'filename' and counts how many times // the '\n' newline character appears in it which corresponds to how // many lines of text are in it. Makes use of either fscanf() with // the %c format to read single characters or alternative I/O // functions like fgetc(). Closes the file before returning a count of // how many lines are it it. If for any reason the file cannot be // opened, prints a message like // // Could not open file '<FILENAME>', cannot count lines // // with <FILENAME> substituted for the name of the file. Returns -1 to // indicate failure in this case. int stock_load(stock_t *stock, char *filename); // PROBLEM 2: Loads a stock from file 'filename' into 'stock' filling // its 'prices' and 'count' fields in. Makes use of the count_lines() // function to determine how many lines are in the file which will // dictate 'count' and the length of 'prices'. Allocates space in the // heap for the stock's 'prices' array, opens the 'filename' and // iterates through reading prices into the array. The data format for // prices files is // // time_03 133.00 // time_04 143.00 // time_05 168.00 // time_06 91.00 // // where each line has a time as as single string and a price which is // floating point number. The times can be ignored which can be // accomplished with a fscanf() call with format "%*s" to read a // string but ignore/discard it. // // Assigns the 'datafile' field to be a duplicated string of // 'filename' for which 'strdup()' is extremely useful. This string // must be free()'d later likely in 'stock_free()' // // On successfully loading the stock, returns 0. // // If 'filename' cannot be opened, prints the message // // Unable to open stock file '<FILENAME>', cannot load stock data // // with '<FILENAME>' substituted in for the name of the stock and // returns -1. void stock_plot(stock_t *stock, int max_height, int start, int stop); // PROBLEM 2: Plots a graphical representation of stock // information. First calculates and prints plot which is in the // following format: // // ==PLOT DATA== // start/stop: 0 15 // max_height: 14 // price range: 309.00 // plot step: 22.07 // +--B=S----------+ // 300.93 | H * | // 278.86 | * H * | // 256.79 | * H * | // 234.71 | * H * | // 212.64 |** H * | // 190.57 |** H * * | // 168.50 |** H** * * *| // 146.43 |** H** * ****| // 124.36 |** H**** ****| // 102.29 |** H**** ****| // 80.21 |** *H***** ****| // 58.14 |** *H***** ****| // 36.07 |** *H***** ****| // 14.00 |****H*****L****| // +^----^----^----+ // 0 5 10 // // Here brief notes on the format with more detail in the project // specification. // - Parameters start, stop, and max_height are printed first along with // calculated information like the price_range (uses hi_index and // lo_index) // - The main body of the plot is exactly max_height characters high. The // lowest line is the price at lo_index; the highest is hi_index minus // plot_step // - The vertical axis prices can be printed with the format specifier // %10.2f to give the correct leading whitespace with 2 digits of // accuracy // - If the hi and lo prices appear in the range, their bar is printed // with an H or an L while all other stocks are printed with a * // - The top axis may include a B and an S at the positions of the // best_buy and best_sell index with a = between these to show the // period of ownership. // - The bottom axis shows tic marks as ^ and below them index labels at // values divisible by 5. For the numeric index labels, it is easiest // to print spaces to a value divisible by 5 then use the format // specifier %-5d to print integers: this aligns left and prints // whitespace padding on the right to width 5. In a loop, one can // advance by +5 each time a label is printed. int main(int argc, char *argv[]); // PROBLEM 4: main() in stock_main.c // // The program must support three command line forms. // 1. ./stock_main <stockfile> <max_height> // 2. ./stock_main <stockfile> <max_height> <start> // 3. ./stock_main <stockfile> <max_height> <start> <stop> // // All forms have a the stock file as the first command line argument // and the maximum plotting height as the 2nd argument. Forms 2 and 3 // optionally specify a starting time index (inclusive) and stopping // time index (exclusive) to plot a time range for the stock prices. // // NOTES // - It is a good idea to check that the number of command line // arguments is either 3 (Form 1) or 4 (Form 2) or 5 (form 3) if // not, bail out from the program. There may or may not be an // automated test that checks that no action is taken when an // incorrect number of command line arguments appear but there will // definitely be tests for each of the forms indicated so a logical // and clean structure that attends to them is a good idea. // - All command line arguments come into C programs as strings // (char*). That means the numbers on the command line like // `max_height` will also be a string of characters and need to be // converted to an int to be used in the program. The atoi() // function is useful for this: search for documentation on it and // use it for the conversion. // - Make sure to check that loading stock data from a file // succeeds. If not, print the following error message: // // Failed to load stock file, exiting // // This message is in addition to the error message that is printed // by the stock_load() function. Return 1 from main() to indicate // that the program was not successful. // - If stock data is successfully loaded, perform the requested // plotting using appropriate functions. Don't forget to free // heap-allocated memory before ending the program. int stock_set_best_linear(stock_t *stock); // OPTIONAL MAKEUP CREDIT: Identical to stock_set_best() except uses a // linear time algorithm to determine the best buy/sell times
3.1 Getting Started
Create the file mazesolve_funcs.c
. Copy the code outline above into
the C file to serve as a starting point for the project. Filling in
dummy bodies (e.g. return 0
or return NULL
etc.) will allow the
Automated Tests to run after which one can begin filling in
definitions for the functions according to the provided specification.
For the most part, try to solve functions in the order that they appear in the code outline as later functions will use earlier functions. If you do get stuck on a function, don't be afraid to move on momentarily to keep your momentum up.
3.2 The Dev Cycle
To complete the project
- Read documentation on a required function
- Write part of the implementation
- Run test cases associated with function
- Analyze the results
- Consult docs on the function for clarification
- Add
printf()
statements to show debug information - Repeat the above until all tests for the function pass
- Consult the MANUAL INSPECTION criteria to ensure your style and functionality meet their requirements
- Repeat for the next function
When stuck on a bug for an Extended period (1+ hour)
- Search for related examples from Labs / HWs / Lectures
- Ask for help on Piazza or visit office hours
- Take a break and come back after recovering some mental energy
- Stay Determined!
4 Problem 1: Stock Plotting
4.1 Overview and Demo
Problems 1 and 2 create a small plotting application that is focused on stock prices. In stock trading, the idea is to buy a stock when it is priced low and sell it at a later time point when the price is high which will net the profit of the difference between each. Of course, this must be done by predicting when prices are at their highest and lowest points and some insight can be garnered from examining historical data for stock prices. The application you will build allows for easy analysis of a simple data file containing times/prices for stocks and display of their information in simple text plots. At the end of problems 1 and 2, you will have an application which produces the following kind of output.
>> make stock_main # compile the stock_main program (after completing problems 1&2) gcc -Wall -Wno-comment -Werror -g -c stock_main.c gcc -Wall -Wno-comment -Werror -g -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -o stock_main stock_main.o stock_funcs.o >> ./stock_main data/stock-ascending.txt 10 # plot the data in a stock-ascending data file ==STOCK DATA== data_file: data/stock-ascending.txt count: 10 prices: [10.00, 20.00, 30.00, ...] # shows the first few stock prices lo_index: 0 # calculates index of low/high price hi_index: 9 best_buy: 0 # calculates optimal buy/sell time best_sell: 9 profit: 90.00 ==PLOT DATA== start/stop: 0 10 max_height: 10 # 10 specifed on command line for bar height price range: 90.00 # highest_price - lowest_price plot step: 9.00 # range / max_height +B========S+ # top axis shows Buy/Sell range 91.00 | H| # main plot body shows bars for stock price 82.00 | *H| # at each time step 73.00 | **H| 64.00 | ***H| # normal prices plotted with a * 55.00 | ****H| # Lowest and Highest prices plotted with 46.00 | *****H| # an L and H respectively 37.00 | ******H| 28.00 | *******H| # bars are between 1 and max_height chars 19.00 | ********H| # tall 10.00 |L********H| +^----^----+ # bottom axis has tick marks and labels 0 5 # at time indexes divisible by 5 >> ./stock_main data/stock-ascending.txt 5 3 10 ==STOCK DATA== # same file but make bars at most 5 high data_file: data/stock-ascending.txt # and limit output to time index 3 to 9 count: 10 prices: [10.00, 20.00, 30.00, ...] # same info on stock file lo_index: 0 hi_index: 9 best_buy: 0 best_sell: 9 profit: 90.00 ==PLOT DATA== start/stop: 3 10 # plot data has adjusted start/stop index max_height: 5 # different max_height price range: 90.00 plot step: 18.00 # plot step as seen on left axis labels is +======S+ # different due to the different max_height 82.00 | *H| 64.00 | ***H| 46.00 | *****H| 28.00 |******H| 10.00 |******H| +--^----+ # bottom axis indicates starting time of 3 5 # rather than 0, tics and labels adjusted >> ./stock_main data/stock-valley.txt 12 # plot a "valley" shaped stock pricing ==STOCK DATA== data_file: data/stock-valley.txt count: 12 prices: [100.00, 90.00, 80.00, ...] lo_index: 5 hi_index: 11 best_buy: 5 # best to buy in the middle at lowest point best_sell: 11 # and sell at the end at highest price profit: 55.00 ==PLOT DATA== start/stop: 0 12 max_height: 12 price range: 55.00 plot step: 4.58 +-----B=====S+ # top axis shows - when stock is not 100.42 | H| # purchased, B at the best_buy index 95.83 |* H| # and S at the best_sell index with 91.25 |* *H| # = symbols between B and S 86.67 |** *H| 82.08 |** **H| 77.50 |*** **H| 72.92 |*** ***H| 68.33 |**** ***H| 63.75 |**** ****H| 59.17 |***** ****H| 54.58 |***** *****H| 50.00 |*****L*****H| +^----^----^-+ 0 5 10 >> ./stock_main data/stock-jagged.txt 15 # plot a jagged stock price file ==STOCK DATA== # where prices bound around a lot data_file: data/stock-jagged.txt count: 15 prices: [103.00, 250.00, 133.00, ...] lo_index: 8 hi_index: 11 best_buy: 8 best_sell: 11 profit: 232.00 ==PLOT DATA== start/stop: 0 15 max_height: 15 price range: 232.00 plot step: 15.47 +--------B==S---+ # Buy to Sell range is shorter in 254.53 | H | # this case and does not align with 239.07 | * *H | # either the first or last time index 223.60 | * * *H | 208.13 | * * *H | 192.67 | * * *H | 177.20 | * * *H | 161.73 | * * * *H | 146.27 | * * * *H | 130.80 | **** * *H | 115.33 | **** * *H | 99.87 |***** * *H *| 84.40 |******* *H *| 68.93 |******* *H **| 53.47 |******** *H***| 38.00 |********L**H***| +^----^----^----+ 0 5 10 >> ./stock_main data/stock-min-after-max.txt 10 ==STOCK DATA== # file with buy/sell different than data_file: data/stock-min-after-max.txt # hi/lo index count: 15 prices: [223.00, 292.00, 27.00, ...] lo_index: 10 # minimum price appear after hi_index: 4 # the maximum price best_buy: 2 # finding the optimal buy/sell best_sell: 4 # point is harder in this case profit: 296.00 ==PLOT DATA== start/stop: 0 15 max_height: 10 price range: 309.00 plot step: 30.90 +--B=S----------+ # best_buy index is not at the 292.10 | H * | # low price because the high 261.20 | * H * | # price occurs before the low 230.30 | * H * | # price 199.40 |** H * * | 168.50 |** H** * * *| 137.60 |** H**** ****| 106.70 |** H**** ****| 75.80 |** *H***** ****| 44.90 |** *H***** ****| 14.00 |****H*****L****| +^----^----^----+ 0 5 10 >> ./stock_main data/stock-descending.txt 9 # prices descend No viable buy/sell point # printed when trying to find a ==STOCK DATA== # buy/sell point data_file: data/stock-descending.txt count: 10 prices: [100.00, 90.00, 80.00, ...] lo_index: 9 hi_index: 0 best_buy: -1 # left as -1 to indicate no best_sell: -1 # good time to buy/sell was found profit: 0.00 ==PLOT DATA== start/stop: 0 10 max_height: 9 price range: 90.00 plot step: 10.00 +----------+ 90.00 |H* | # prices descend over the 80.00 |H** | # entire period meaning NOT 70.00 |H*** | # investing is the best option 60.00 |H**** | 50.00 |H***** | 40.00 |H****** | 30.00 |H******* | 20.00 |H******** | 10.00 |H********L| +^----^----+ 0 5 >> ./stock_main data/stock-FB-08-02-216.txt 15 87 142 ==STOCK DATA== # plot values for Facebook's stock data_file: data/stock-FB-08-02-216.txt # on a day when prices were gradually count: 543 # falling. Shows a slice of time from prices: [358.94, 358.50, 358.50, ...] # this large file which includes the lo_index: 470 # best Buy/Sell time indices hi_index: 15 best_buy: 109 best_sell: 129 profit: 2.38 ==PLOT DATA== start/stop: 87 142 max_height: 15 price range: 8.00 plot step: 0.53 +----------------------B===================S------------+ 358.46 | | 357.92 |** | 357.39 |**** | 356.86 |****** | 356.32 |******* *** * | 355.79 |************* | 355.25 |***************** | 354.72 |****************** ****** ** | 354.19 |****************** * * ***** *************** | 353.65 |******************** ************ ******************| 353.12 |********************** ********************************| 352.59 |*******************************************************| 352.05 |*******************************************************| 351.52 |*******************************************************| 350.99 |*******************************************************| +---^----^----^----^----^----^----^----^----^----^----^-+ 90 95 100 105 110 115 120 125 130 135 140
4.2 Editing and Testing
The project code contains a skeleton version of stock_funcs.c
which
you should fill in with definitions. With this skeleton version, you
can immediately start testing your code by typing make test-prob1
.
Without changes, you will get failures for all tests as in
>> make test-prob1 gcc -Wall -Wno-comment -Werror -g -o test_stock_funcs test_stock_funcs.c stock_funcs.o ./testy test_stock1.org ============================================================ == test_stock1.org : Problem 1 First 3 Functions in stock_funcs.c == Running 15 / 15 tests 1) stock_new : FAIL -> results in file 'test-results/prob1-01-result.tmp' 2) stock_free1 : FAIL -> results in file 'test-results/prob1-02-result.tmp' 3) stock_free2 : FAIL -> results in file 'test-results/prob1-03-result.tmp' ... ============================================================ RESULTS: 0 / 15 tests passed
However, the ability to run tests on your code an see progress is extremely important. Your first goal when starting a new project should be to see some results or running the program which is much easier if some benevolent dictator has provided a bunch of tests.
You can also use the invocation
> make test-prob1 testnum=5
to run a single test and see its results in the terminal.
Failed tests generate results files which can be viewed in any text
editor or in the terminal. The cat
command can show such results in
the terminal via cat test-results/some-file.txt
.
Below the results for the first test are shown and from the comparison
and Valgrind report, it appears that there is some sort of Memory
problem with the stock_new()
function.
>> cat test-results/prob1-01-result.tmp * (TEST 1) stock_new COMMENTS: ** program: ./test_stock_funcs stock_new ** --- Failure messages --- - FAILURE(139) due to SIGSEGV (segmentation fault) from OS - FAILURE: Output Mismatch at lines marked ** --- Side by Side Differences --- - Expect output in: test-results/raw/prob1-01-expect.tmp - Actual output in: test-results/raw/prob1-01-actual.tmp - Differing lines have a character like '|' '>' or '<' in the middle #+BEGIN_SRC sbs-diff ==== EXPECT ==== ==== ACTUAL ==== { { // Tests stock_new() function and whether it initializes fields // Tests stock_new() function and whether it initializes fields // correctly before returning a stock. // correctly before returning a stock. stock_t *stock = stock_new(); // call function to allocate/init stock_t *stock = stock_new(); // call function to allocate/init printf("stock->data_file: %p\n" , stock->data_file); printf("stock->data_file: %p\n" , stock->data_file); printf("stock->count: %d\n" , stock->count); printf("stock->count: %d\n" , stock->count); printf("stock->prices: %p\n" , stock->prices); printf("stock->prices: %p\n" , stock->prices); printf("stock->lo_index: %d\n" , stock->lo_index); printf("stock->lo_index: %d\n" , stock->lo_index); printf("stock->hi_index: %d\n" , stock->hi_index); printf("stock->hi_index: %d\n" , stock->hi_index); printf("stock->best_buy: %d\n" , stock->best_buy); printf("stock->best_buy: %d\n" , stock->best_buy); printf("stock->best_sell: %d\n" , stock->best_sell); printf("stock->best_sell: %d\n" , stock->best_sell); free(stock); // de-allocate manually free(stock); // de-allocate manually } } stock->data_file: (nil) | Segmentation Fault stock->count: -1 < stock->prices: (nil) < stock->lo_index: -1 < stock->hi_index: -1 < stock->best_buy: -1 < stock->best_sell: -1 < #+END_SRC ** --- Line Differences --- EXPECT: 17) stock->data_file: (nil) EXPECT: 18) stock->count: -1 EXPECT: 19) stock->prices: (nil) EXPECT: 20) stock->lo_index: -1 EXPECT: 21) stock->hi_index: -1 EXPECT: 22) stock->best_buy: -1 EXPECT: 23) stock->best_sell: -1 ACTUAL: 17) Segmentation Fault --- Valgrind Log from: test-results/raw/prob1-01-valgrd.tmp --- ==150464== Memcheck, a memory error detector ==150464== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==150464== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info ==150464== Command: ./test_stock_funcs stock_new ==150464== Parent PID: 150463 ==150464== ==150464== Invalid read of size 8 ==150464== at 0x109354: main (test_stock_funcs.c:33) ==150464== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==150464== ==150464== ==150464== Process terminating with default action of signal 11 (SIGSEGV): dumping core ==150464== Access not within mapped region at address 0x0 ==150464== at 0x109354: main (test_stock_funcs.c:33) ==150464== If you believe this happened as a result of a stack ==150464== overflow in your program's main thread (unlikely but ==150464== possible), you can try to increase the size of the ==150464== main thread stack using the --main-stacksize= flag. ==150464== The main thread stack size used in this run was 10022912. ==150464== ==150464== HEAP SUMMARY: ==150464== in use at exit: 0 bytes in 0 blocks ==150464== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==150464== ==150464== All heap blocks were freed -- no leaks are possible ==150464== ==150464== For lists of detected and suppressed errors, rerun with: -s ==150464== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
4.3 Implementation Notes
Use the Arrow
The first 3 functions to implement in stock_funcs.c
are utility
functions for stocks to allocate, de-allocate and print them. As will
be covered in lecture, C often deals with pointers to a struct
and
that is the case in these functions. For this, the sp->fld
syntax is
used to first dereference the struct
pointer and access a field of
the struct
. For example, in stock_print()
one could use the
following code to print the count
field of the parameter struct
:
void print_stock(stock_t *stock){ // ... printf("count: %d\n", stock->count); // ^^^^^^^^^^^^
Throughout most of this problem and the next, the Arrow syntax will be used in most functions.
Allocating Stocks in Heap Memory
In order to get dynamically allocated memory in the Heap, the
malloc()
function should be used. This function takes a number of
bytes and returns a void *
pointer for that number of bytes if
available. As will be discussed in lecture, C provides the sizeof()
operator to calculate the size of various intrinsic and user-defined
types. To allocate enough space for a stock_t
, use
malloc(sizeof(stock_t))
.
Though malloc()
returns a void *
, it is typical to simply assign
it to a pointer of some other kind. For example, the following is a
typical way to allocate space for a single stock_t
struct
and save
the reference for later use.
stock_t *stock = malloc(sizeof(stock_t));
Once allocated, the Arrow syntax mentioned above, stock->lo_index
can be used to adjust fields.
To understand which fields are present in the stock_t
struct, open
up the header file stock.h
which declares the struct. There will be
a list of fields like prices
and hi_index
: these will all need to
receive initial values like -1
or NULL
. Don't alter anything in
the header as this may break tests that you'll run later.
Avoid Freeing NULL
While the free()
function is used to de-allocate memory that has
been obtained via malloc()
, it should never be called with a NULL
pointer. In the stock_free()
function, one should check the
data_file
and prices
fields and free them only if they are not
NULL
. The following code demonstrates how this can be done for one
of those fields.
void stock_free(stock_t *stock){ // ... if(stock->data_file != NULL){ free(stock->data_file); } // ...
Printing Prices Array
During stock_print()
, some special code will be needed to print the
prices[]
array if it has 3 or fewer elements. As per the function
documentation, the line associated with prices will like one of the
following depending on the value for count
:
prices: [] # count == 0 prices: [70.00] # count == 1 prices: [50.00, 90.00] # count == 2 prices: [59.00, 45.00, 103.00] # count == 3 prices: [10.00, 20.00, 30.00, ...] # count > 3
There are a couple tricks for producing this type of output and it may take some care to get the output formatted correctly for all situations. You might try either of the following:
- Brute force: establish 5 cases for each of the formats above with
separate
printf()
call for each. Since the number of cases is relatively small, this should not be unmanageable. - Use the
count==0
as a special case and then write a carefully crafted loop with conditionals to handle the remaining cases. This can get tricky but is more flexible if for later one wants to see the first 5 numbers instead.
Either approach is acceptable in this case.
Keep in mind that though prices
is declared as a double *prices
(pointer to double
data), it is fine to use array syntax like square
brace indexing with it. Syntax like stock->prices[2]
will follow a
pointer to a stock_t struct
then find the prices
field and index
into it as an array.
4.4 Grading Criteria for Problem 1 grading 30
The following criteria will be checked.
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob1 |
|
15 | Runs tests in test_stock1.org on the first 3 functions in stock_funcs.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
5 | stock_new() and stock_free() |
Appropriate use of malloc() and sizeof() to get heap memory |
|
Correct initialization of all fields to -1 or NULL |
|
Appropriate use of the Arrow syntax for field access | |
Checks for data_file and prices fields to avoid calling free() on a NULL pointer |
|
Appropriate use of the Arrow syntax for field access | |
5 | stock_print() |
Appropriate use of the Arrow syntax for field access | |
Clear logic which deals with printing prices[] array with 3 or fewer elements specially |
|
Clear section to calculate Profit from best_buy/best_sell or print 0.0 if these are -1 |
5 Problem 2: Complete Stock Plot Functions
5.1 Overview
This problem implements further functions required by the Stock
Plotting application. These functions are somewhat more complex and
utilize functions and techniques that are part of Problem 1. When this
problem is complete, all functions in stock_funcs.c
will be working
which will allow the provided stock_demo.c
program to be compiled
and run on the command line. These functions will then be used in the
next problem to complete stock_main.c
.
The remainder of this section gives some notes and implementation hints on how to handle the various functions.
5.2 Setting Low/High Index
void stock_set_hilo(stock_t *stock)
This function should be implemented as a simple "scan" across the
prices[]
array. Each step will check an element of prices[]
and
adjust a running min/max. Make use of similar syntax here as you used
in stock_print()
in order to access the elements of prices
and
subsequently set the lo_index
and hi_index
fields.
5.3 Best Buy/Sell Index
int stock_set_best(stock_t *stock);
In many cases it is best to buy at a minimum price and sell at a
maximum price. This approach fails if the maximum price occurs
before the minimum price. The job of stock_set_best()
is to run an
algorithm to locate the best possible buy/sell pair and set the
best_buy / best_sell
fields of a stock. As the documentation for the
function indicates, one can use a simple "brute force" approach to
this by simply trying every buy index paired with every sell index
(which must occur later in the array than the buy index). This
approach leads to the following pattern of iteration through the
prices
array which uses a doubly nested pair of loops.
set best_buy and best_sell to -1 set best_profit to 0.0 for every buy index I in prices for every sell index J later than I in prices if prices[J] - prices[I] > best_profit set best_buy,best_sell, to I,J set best_profit to prices[J] - prices[I] done done
Start by adapting this approach in the function and make sure it works. Then move on to solve other problems here.
For those that want a bit more fun, revisit this function later to implement a more efficient version of it as described in the Optional MAKEUP CREDIT Section.
5.4 Stock Files
Data on stock prices over time are stored in several example files in
the data/
directory such as data/stock-ascending.txt
and
data/stock-jagged.txt
.
>> ls data/stock-* # list all the sample stock data data/stock-1only.txt data/stock-empty.txt data/stock-TSLA-08-02-216.txt data/stock-2only.txt data/stock-FB-08-02-216.txt data/stock-TSLA-08-12-216.txt data/stock-3only.txt data/stock-GOOG-08-02-216.txt data/stock-valley.txt data/stock-ascending.txt data/stock-jagged.txt data/stock-descending.txt data/stock-min-after-max.txt
Each of has a simple data format which looks like the following.
>> cat data/stock-jagged.txt time_01 103.00 time_02 250.00 time_03 133.00 time_04 143.00 time_05 168.00 time_06 91.00 time_07 234.00 time_08 59.00 time_09 38.00 time_10 45.00 time_11 254.00 time_12 270.00 time_13 59.00 time_14 72.00 time_15 107.00
- Each time/price pair appears on its own line so that the number of lines in the file is the number of time/price pairs
- The first item on each line is a time at which a price appears: this will be ignored.
- The second item that appears on each line is a floating point number which is the stock price at that time.
5.5 Counting Lines
Since the number of lines in stock files corresponded to the number of prices, it is useful to have a utility function to count lines in a file which is required.
int count_lines(char *filename)
As the documentation for the function indicates, this function will
open the named file and count the lines in it. This can be done most
easily via treating the file as a long array of characters and looking
for the special \n
newline character which designates a line break.
Character comparisons are done in C like other equality comparisons
via syntax like
if(somevar == '\n'){ ... } if(somevar == 'X'){ ... }
To read a single character from a file, a common approach is to use
the fscanf()
function with the %c
format specifier as in
char c; int ret = fscanf(fin, "%c", &c);
Keep in mind that the fscanf()
function will fail to read a
character when it reaches the end of a file. In these cases it will
return the special return value EOF
which should be checked. A
common structure is
int ret = fscanf(fin, ..., ...); if(ret == EOF){ // take action for end of file, like breaking out of a loop // or returning } else{ // fscanf() completed normally so variables should contain // new values }
Experiment with these elements to complete the count_lines()
function.
Keep in mind that count_lines()
should fail gracefully if a file
cannot be opened. In such cases, the fopen()
function returns
NULL
. Make sure to check for this, print an error message as
indicated in the function documentation, and return -1 from the
function to indicate a failure has occurred.
5.6 Loading Stock Files
int stock_load(stock_t *stock, char *filename)
This function takes an existing stock
and fills in its price
and
count
fields based on the contents of the given file. An example:
stock_t *stock = new_stock(); // all fields of stock have default values like -1 and NULL int ret = stock_load(stock, "data/stock-ascending.txt"); // Specified data file as 10 lines so // stock->count is now 10 // stock->prices is an array of 10 values read from the file // If the load failed, -1 would be returned
Make use of the count_lines()
function to determine how many lines
are in the stock file. This will allow you to immediately allocate
enough space for a double
array for all of the prices in the file.
You will need to skip the first string on each line in prices files:
time_03 133.00 time_04 143.00 | | | +---> Read these into prices +---> Skip these times
An easy way to accomplish this is to use the C format modifier *
which reads a field but does not place it in memory anywhere. For
example:
fscanf(fin, "%*s");
would read a string but ignore it thus one does not have to provide a memory address to store the string.
One requirement of stock_load()
is to set the data_file
field to
be a copy of the filename
parameter. While this can be done
manually with malloc()
and a loop, the strdup()
function makes
this easy and is encouraged here. Look up documentation for it either
in the Unix man pages (type man strdup
in a terminal) or via an
internet search.
Ensure that at the end of stock_load()
the data_file
, prices
,
and count
fields have been set according to the data from the
file. Do not modify any other fields in the stock.
5.7 Plotting Stocks
By far the trickiest function is the stock plot function which
presents a dense set of information in a compact region. Its output
will follow the below pattern which shows output for
stock-min-after-max.txt
.
==PLOT DATA== start/stop: 0 15 max_height: 10 price range: 309.00 plot step: 30.90 +--B=S----------+ 292.10 | H * | 261.20 | * H * | 230.30 | * H * | 199.40 |** H * * | 168.50 |** H** * * *| 137.60 |** H**** ****| 106.70 |** H**** ****| 75.80 |** *H***** ****| 44.90 |** *H***** ****| 14.00 |****H*****L****| +^----^----^----+ 0 5 10
This section outlines some techniques to make implementing this function more tractable.
Plot Data
Initially, some plot data should be printed to verify that correct values are being used for the remainder of the plotting routine.
==PLOT DATA== start/stop: 0 15 max_height: 10 price range: 309.00 plot step: 30.90
The first 3 numbers are parameters to stock_plot()
. The price range
can be computed from looking at the stock's price
array at
hi_index
and lo_index
and the plot step
by dividing the price
range by max_height
.
Vertical Printing
Character terminals tend to print things well horizontally so printing the vertical bars that appear in the main body of the plot will take some planning and effort. The general approach is encoded in the following pseudocode.
for h=max_height-1 down to 0 do thresh = h*plot_step + min_price # make sure to use a double for thresh for i=start index to stop index # note start/stop are parameters to stock_plot() if stock price i >= thresh print "*" else print " " done print "\n" done
This approach gives the general idea, that one iterates top down and prints a symbol for stocks with a price larger than a threshold and a blank space for those less than. After printing a "row" of such symbols, advance to the next line down and repeat the process with a lower threshold.
Follow this pseudocode approach closely but note that it misses several details.
- The syntax needs to be converted to C code.
- It does not print any of the left axis number labels or left axis bar.
- It does not print the Highest priced stock or Lowest priced stock
(at stock index
hi_index
andlo_index
with the special symbolsH
andL
that should be used.
After getting the basic body of the algorithm working you can add these elements in.
NOTE: Floating point computations are a bit fragile so make sure
that you follow the above code well and use a double
for
thresholds. Past students who have worked on projects like this one
have sometimes failed tests if they use a float
instead due to
imprecision and rounding errors.
Left-side Axis Values and Left/Right Bars
The left side axis labels look something like
292.10 | 261.20 | 230.30 | 199.40 | .......... 10-char wide numeric field
Note that the whitespace leading each of the numeric label values on
the left axis. It is painful to try to manually print such numbers
with appropriate spacing and unnecessary as the printf()
function
already has such facilities built in. Use an invocation like the
following
printf("%10.2f |", ...)
which will print a field that is 10 characters wide and place a
floating point value aligned to right in it. The number will be
printed with 2 decimal places and be trailed by the string constant
" |"
to set up the left axis bar.
The value to be printed is the thresh
value described in the
previous section, the price at which a symbol is printed on the row
for prices at or above that level. After printing the left axis bar,
print out the main plot line at that threshold.
At the end of each line in the main plot body, print a "|\n"
to get
the right axis bar and carry down to the next line.
Top Axis Buy/Sell Markings
The top axis will look like one of the following:
...........11 spaces +--B====S------+ Best Buy/Sell is being plotted +----------B===+ Best Buy part of plot range but Sell is not in range +----------B===+ Best Buy part of plot range but Sell is not in range +====S---------+ Best Sell is being plotted but Buy is out of range +--------------+ Best Buy/Sell is not in range or not set
This can be done with a loop that traverses from the start
to stop
index and checks if the index is at the best_buy
or best_sell
index, in between these two, or outside the range. In each case,
prints an appropriate character, one of B S = -
.
Note also the leading whitespace which is 10 spaces plus 1 space and a
+
symbol. This can be easily printed with a printf()
invocation
like
printf("%10s +","");
which prints a string in a space 10 characters wide but substitutes an empty string in that space. The 10 spaces matches the width of the left side numeric labels.
Bottom Axis Tic Marks and Numeric Indices
The bottom axis and axis labels will look something like one of the following.
...........11 spaces +^----^----^----^--+ Starting at 0, axis marks at indices divisible by 5 0 5 10 15 Axis labels aligned with each tic +---^----^----^----+ Starting at 2, axis marks at indices divisible by 5 5 10 15 Axis labels aligned with each tic
The axis tic marks can be printed easily by starting the line with
spaces and a +
, then iterating from the start
to stop
parameters
and printing a -
except when the index is divisible by 5 in which
case a ^
is printed.
The numeric labels are a bit trickier. The following approach is suggested.
- Print 11 spaces to line up with the first bottom axis mark.
- Create a loop variable outside of any loop as this loop variable will be used in 2 consecutive loops.
- Start a loop with your loop variable initialized to
start
and increment the variable each iteration. Terminate this loop when the variable is evenly divisible by five (e.g.var % 5 == 0
). Each iteration print a space. This loop will "line up" further printing at a positions divisible by 5. Start a second loop based on the current value of your loop variable. Each iteration increase the variable by 5 and terminate it after surpassing
stop
. Suggested syntax isfor(; var < stop; var += 5){ ... }
with the first
;
indicating there is no loop initialization so the previous value ofvar
will be used as the staring point.Each iteration print out a numeric index using a 5-char width but left aligned. This can be done with
printf("%-5d",...)
with the negative sign yielding left-alignment.
It may take a bit of work to get the formatting correct but this will be good practice to acquaint you with formatted output.
Don't forget you can run stockplot_main
to observe its output
while working to get the right format: many forget this while
attempting to only use the test cases.
5.8 Grading Criteria for Problem 2 grading 35
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob2 |
|
20 | Runs the tests provided in test_prob2.org for last functions in stock_funcs.c, stock_demo.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
2 | stock_set_hilo() and stock_set_best() |
Clear loop to scan through the prices array to find the low/high and best buy/sell |
|
Proper changes to the fields lo_index / hi_index / best_buy / best_sell |
|
3 | stock_load() and count_lines() |
Use of fopen() and fscanf() to read in data from the file |
|
Code to gracefully handle failure to open a file and print appropriate messages. | |
Proper use of count_lines() to determine the number of lines in the data file |
|
Use of malloc() to allocate memory for the prices array |
|
Use of the "%*s" format specifier to skip leading "time" string in files |
|
Duplication of the filename to the data_file field likely using the strdup() function |
|
5 | stock_plot() |
Clear section that calculates the "plot step" with appropriate variables like range | |
Code to print out the initial plot information and loop to print the upper axis "bar" | |
Clear loop that prints out bars with height proportional to the price of the stock | |
Section of conditional code which prints if stock is low/high and best buy/sell | |
Clear section dealing with lower axis ticks and labels. |
6 Problem 3: Main Stock Application
6.1 main() Command Line Args
The final steps to complete the application are to create a main()
function that combines the previously completed functions to load
stock data and plot it. There are examples of how this main
application should run that appear earlier in the Overview Section
which are worth inspecting. Briefly, the syntax to run the main
function is as follows which features 2, 3, or 4 command line
arguments.
# 2 required command line arguments, otherwise print a usage message >> ./stock_main usage: ./stock_main <stockfile> <max_height> [start] [stop] # arg1: stock file, arg2: maxiumum height >> ./stock_main data/stock-jagged.txt 20 ... # arg3 preset: start index to print (inclusive) # arg4 missing: no stop index given # print from prices[6] to the end of the array >> ./stock_main data/stock-jagged.txt 20 6 ... # arg3 preset: start index to print (inclusive) # arg4 present: stop index (exclusive) # print from prices[3] to prices[7] >> ./stock_main data/stock-jagged.txt 20 3 8 ...
Most C program main()
functions have the following prototype
(return value and argument types):
int main(int argc, char *argv[])
argc
is the number of items that appear on a command lineargv[]
is an array of strings which are those command line arguments
Using a condition and/or loop like the following, you can print out all command line arguments
int main(int argc, char *argv[]){ if(argc > 2){ printf("passed at least 2 arguments\n"); } for(int i=0; i<argc; i++){ printf("argv[%d]: %s\n",argv[i]); } ...
Running a program with that loop at the top would print something like the following:
>> ./a.out argv[0]: a.out >> ./a.out stuff.txt argv[0]: a.out argv[1]: stuff.txt >> ./a.out one 2 three passed at least 2 arguments argv[0]: a.out argv[1]: one argv[2]: 2 argv[3]: three
Note that all of the arguments are strings so if you want binary number versions of them, you'll need to use a conversion function.
6.2 Implementation of main()
Create the file stock_main.c
and write your main()
function in
it. Do not write a main()
in stock_funcs.c
as this will cause
testing files to fail to compile. C programs can have only one
main()
function and the testing setup already has a main()
that is
used to interrogate the service functions. It is customary to have a
main()
in a separate file so that multiple programs can use the same
set of service functions.
Here are few notes on how to complete main()
function in
stock_main.c
.
- Use the functions you have completed previously for most of the
functionality that
main()
should perform. - Examine how to convert a string to a binary integer using the
atoi()
function. There is an example of this given in a PROVIDED section ofmain()
. - It may be helpful to review a
main()
function that is similar to what you wish to accomplish. Good options are themain()
provided instock_demo.c
OR themain()
in a recent lab like the "treasure maps" exercise. - Avoid repeating common code in conditionals. Load the stock file in only one line of code, not in multiple conditional cases. While it can be easy to handle the cases of 3, 4, 5, etc. command line arguments in an if/else if and copy paste code between them, this runs against the "DRY" principle ("Don't Repeat Yourself") that should guide most programming. Make your code compact with the smallest amount of copy/pasted lines you can muster.
Below is a quick demo of how the behavior of main()
should work on
the command line.
# omit command line arguements to show usage >> ./stock_main usage: ./stock_main <stockfile> <max_height> [start] [stop] # plot all prices at height 10 >> ./stock_main data/stock-jagged.txt 10 ==STOCK DATA== data_file: data/stock-jagged.txt count: 15 prices: [103.00, 250.00, 133.00, ...] lo_index: 8 hi_index: 11 best_buy: 8 best_sell: 11 profit: 232.00 ==PLOT DATA== start/stop: 0 15 max_height: 10 price range: 232.00 plot step: 23.20 +--------B==S---+ 246.80 | * *H | 223.60 | * * *H | 200.40 | * * *H | 177.20 | * * *H | 154.00 | * * * *H | 130.80 | **** * *H | 107.60 | **** * *H | 84.40 |******* *H *| 61.20 |******* *H **| 38.00 |********L**H***| +^----^----^----+ 0 5 10 # plot all prices at height 5 >> ./stock_main data/stock-jagged.txt 5 ==STOCK DATA== data_file: data/stock-jagged.txt count: 15 prices: [103.00, 250.00, 133.00, ...] lo_index: 8 hi_index: 11 best_buy: 8 best_sell: 11 profit: 232.00 ==PLOT DATA== start/stop: 0 15 max_height: 5 price range: 232.00 plot step: 46.40 +--------B==S---+ 223.60 | * * *H | 177.20 | * * *H | 130.80 | **** * *H | 84.40 |******* *H *| 38.00 |********L**H***| +^----^----^----+ 0 5 10 # plot a specific range of prices, times 20 to 50 >> ./stock_main data/stock-GOOG-08-02-2021.txt 10 20 50 ==STOCK DATA== data_file: data/stock-GOOG-08-02-2021.txt count: 345 prices: [2715.00, 2715.00, 2711.00, ...] lo_index: 24 hi_index: 337 best_buy: 24 best_sell: 337 profit: 25.75 ==PLOT DATA== start/stop: 20 50 max_height: 10 price range: 25.75 plot step: 2.58 +----B=========================+ 2717.22 | | 2714.64 | | 2712.07 | | 2709.49 | | 2706.91 | **| 2704.34 | ***| 2701.76 | * *******| 2699.19 | * ****** ********| 2696.61 |**** ************ **********| 2694.04 |****L*************************| +^----^----^----^----^----^----+ 20 25 30 35 40 45 # plot from a large index to the end of the stock prices >> ./stock_main data/stock-GOOG-08-02-2021.txt 10 290 ==STOCK DATA== data_file: data/stock-GOOG-08-02-2021.txt count: 345 prices: [2715.00, 2715.00, 2711.00, ...] lo_index: 24 hi_index: 337 best_buy: 24 best_sell: 337 profit: 25.75 ==PLOT DATA== start/stop: 290 345 max_height: 10 price range: 25.75 plot step: 2.58 +===============================================S-------+ 2717.22 | **H*******| 2714.64 | ****H*******| 2712.07 |** ******** * * * *****H*******| 2709.49 |***************************************** *****H*******| 2706.91 |***********************************************H*******| 2704.34 |***********************************************H*******| 2701.76 |***********************************************H*******| 2699.19 |***********************************************H*******| 2696.61 |***********************************************H*******| 2694.04 |***********************************************H*******| +^----^----^----^----^----^----^----^----^----^----^----+ 290 295 300 305 310 315 320 325 330 335 340 # show tricky case where the low/high stock points are not the best # buy/sell times >> ./stock_main data/stock-min-after-max.txt 10 ==STOCK DATA== data_file: data/stock-min-after-max.txt count: 15 prices: [223.00, 292.00, 27.00, ...] lo_index: 10 hi_index: 4 best_buy: 2 best_sell: 4 profit: 296.00 ==PLOT DATA== start/stop: 0 15 max_height: 10 price range: 309.00 plot step: 30.90 +--B=S----------+ 292.10 | H * | 261.20 | * H * | 230.30 | * H * | 199.40 |** H * * | 168.50 |** H** * * *| 137.60 |** H**** ****| 106.70 |** H**** ****| 75.80 |** *H***** ****| 44.90 |** *H***** ****| 14.00 |****H*****L****| +^----^----^----+ 0 5 10 # attempt to load a missing file >> ./stock_main data/stock-no-such-file.txt 15 Could not open file 'data/stock-no-such-file.txt', cannot count lines Unable to open stock file 'data/stock-no-such-file.txt', cannot load stock data Failed to load stock file, exiting # too few command line args >> ./stock_main data/stock-jagged.txt usage: ./stock_main <stockfile> <max_height> [start] [stop] # too many command line args >> ./stock_main data/stock-jagged.txt 15 100 200 500 900 usage: ./stock_main <stockfile> <max_height> [start] [stop]
Once you have completed the main function, you can test it via make test-prob3
.
>> make gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_main.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_funcs.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_main stock_main.o stock_funcs.o gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -c stock_demo.c gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o stock_demo stock_demo.o stock_funcs.o gcc -Wall -Wno-comment -Werror -g -Wno-unused-variable -o test_stock_funcs test_stock_funcs.c stock_funcs.o >> make test-prob3 ./testy test_stock3.org ============================================================ == test_stock3.org : Problem 3 stock_main == Running 10 / 10 tests 1) stock_main data/stock-ascending.txt all : ok 2) stock_main fail to open : ok 3) stock_main data/stock-ascending.txt 5 to 10 : ok 4) stock_main data/stock-ascending.txt 5 to End : ok 5) stock_main data/stock-min-after-max.txt : ok 6) stock_main Facebook Stock 5 to 43 : ok 7) stock_main Facebook Stock 100 to 140 : ok 8) stock_main Facebook Stock 152 to 203 : ok 9) stock_main Facebook Stock 500 to End : ok 10) stock_main Google Stock : ok ============================================================ RESULTS: 10 / 10 tests passed
6.3 Grading Criteria for Problem 3 grading 35
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob3 |
|
10 | Runs the tests provided in test_prob3.org for stock_main.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
10 | Correct use of a previously written functions for memory allocation, loading, setting stock properties, and plotting |
10 | Logically handles command line arguments including appropriate behavior for different invocations |
Avoids too much duplicate code while handling command line arguments | |
Avoids repeated code for common actions like loading the stock file; loads only appear in one spot, not several conditions | |
Uses of an appropriate library function to convert from strings to integers for numeric arguments |
7 OPTIONAL Makeup Credit
The "brute force" approach initially suggested to find best_buy /
best_sell
indices compares all possible buy/sell pairs to determine
the best profit possible. This is a Quadratic algorithm with
complexity O(N^2)
with N
being the length of the prices[]
array.
One can improve upon this algorithm to get an O(N)
linear
algorithm. This takes some cleverness or some research. For example,
the problem is directly related to the Maximum Subarray Problem and
its various solutions among which the Linear time algorithm is
sometimes referred to as Kadane's approach. Some possible references
are here:
- https://en.wikipedia.org/wiki/Maximum_subarray_problem
- https://cs.gmu.edu/~kauffman/cs310/01-intro.pdf
- https://www.techiedelight.com/maximum-subarray-problem-kadanes-algorithm/
Note that adapting these approaches to the current setting will
require some care: the Maximum Subarray problem assumes elements in
the array are added together whereas in our Stock setting, it is the
difference between elements that matter. However, by absorbing the
tricks used the Linear time Maximum Subarray algorithm, one can get
the same effect for the Stocks, an O(N)
algorithm to set best_buy /
best_sell
.
For up to 10 points of PROJECT MAKEUP CREDIT implement the following alternate function.
int stock_set_best_linear(stock_t *stock); // OPTIONAL MAKEUP CREDIT: Identical to stock_set_best() except uses a // linear time algorithm to determine the best buy/sell times
- The normal
stock_set_best()
function MUST be present with Quadratic complexity (or worse) - The linear time version must also be present
- The linear time version MUST be used in
main()
and pass test cases associated with running the whole program.
MAKEUP CREDIT will go into the general pool of points earned for projects to make up for points missed on projects elsewhere. If a student scores 110/100 on Project 1 and 90 / 100 on Project 2, the 10 points from Project 1 will make up for the credit lost on Project 2. Your total score on All Projects cannot exceed 100% so any points beyond the overall total will simply be dropped.
Grading Criteria for MAKEUP CREDIT grading 10
Weight | Criteria |
---|---|
10 | TOTAL OPTIONAL MAKEUP CREDIT |
Implements the normal stock_set_best() which uses a quadratic or slower algorithm |
|
ALSO Implements a stock_set_best_linear() which uses a linear time algorithm. |
|
5 | Manual verification by graders that the linear algorithm looks correct runs in linear time on the size of the stock prices. |
5 | Uses stock_set_best_linear() in stock_main() and passes all associated the main function test cases. |
8 Project Submission
8.1 Submit to Gradescope
Some of the pictures below are dated and contain out of sync info like:
- 'Assignment' rather than'Project'
- Class code 'CSCI 2021' rather than 'CMSC216'
- Mention some files that are not part of the current project
- May indicate some other zip name when
p1-complete.zip
is that default name of the zip for completed projects.
However, most students should have no trouble adapting these
instructions which amount to: make zip
then upload the zip to
Gradescope.
In a terminal, change to your project code directory and type make zip which will create a zip file of your code. A session should look like this:
>> cd 216-sync/p1-code # location of assignment code >> ls Makefile stock_main.c stock_funcs.c data/ ... >> make zip # create a zip file using Makefile target rm -f p1-complete.zip cd .. && zip "p1-code/p1-complete.zip" -r "p1-code" adding: p1-code/ (stored 0%) adding: p1-code/Makefile (deflated 68%) adding: p1-code/test_prob1.org (deflated 69%) ... Zip created in p1-complete.zip >> ls p1-complete.zip p1-complete.zip
Log into Gradescope and locate and click 'Project 1' which will open up submission
Click on the 'Drag and Drop' text which will open a file selection dialog; locate and choose your
p1-complete.zip
fileThis will show the contents of the Zip file and should include your C source files along with testing files and directories.
Click 'Upload' which will show progress uploading files. It may take a few seconds before this dialog closes to indicate that the upload is successful. Note: there is a limit of 256 files per upload; normal submissions are not likely to have problems with this but you may want to make sure that nothing has gone wrong such as infinite loops creating many files or incredibly large files.
WARNING: There is a limit of 256 files per zip. Doing
make zip
will warn if this limit is exceeded but uploading to Gradescope will fail without any helpful messages if you upload more the 256 files in a zip.Once files have successfully uploaded, the Autograder will begin running the command line tests and recording results. These are the same tests that are run via
make test
.When the tests have completed, results will be displayed summarizing scores along with output for each batch of tests.
8.2 Late Policies
You may wish to review the policy on late project submission which will cost 1 Engagement Point per day late. No projects will be accepted more than 48 hours after the deadline.