Last Updated: 2025-09-03 Wed 16:47

CMSC216 Project 1: C Programming

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:

  1. 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.
  2. 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.

  1. The syntax needs to be converted to C code.
  2. It does not print any of the left axis number labels or left axis bar.
  3. It does not print the Highest priced stock or Lowest priced stock (at stock index hi_index and lo_index with the special symbols H and L 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.

  1. Print 11 spaces to line up with the first bottom axis mark.
  2. Create a loop variable outside of any loop as this loop variable will be used in 2 consecutive loops.
  3. 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.
  4. 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 is

       for(; var < stop; var += 5){ ... }
    

    with the first ; indicating there is no loop initialization so the previous value of var 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 line
  • argv[] 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 of main().
  • It may be helpful to review a main() function that is similar to what you wish to accomplish. Good options are the main() provided in stock_demo.c OR the main() 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:

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.

  1. 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
    
  2. Log into Gradescope and locate and click 'Project 1' which will open up submission

    gradescope01.png

  3. Click on the 'Drag and Drop' text which will open a file selection dialog; locate and choose your p1-complete.zip file

    gradescope02.png

  4. This will show the contents of the Zip file and should include your C source files along with testing files and directories.

    gradescope03.png

  5. 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.

    gradescope04.png

  6. 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.

    gradescope05.png

  7. When the tests have completed, results will be displayed summarizing scores along with output for each batch of tests.

    gradescope06.png

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.

https://kauffman77.github.io/216/syllabus.html


Web Accessibility
Author: Chris Kauffman (profk@umd.edu)
Date: 2025-09-03 Wed 16:47