Advertisement
TeslaCoilGirl

Programming Assignment 1

Sep 10th, 2021
5,542
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 16.29 KB | None | 0 0
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4.  
  5. FILE *fpin;
  6. FILE *fpout;
  7.  
  8. /*
  9.     Defining file pointers at a global scope allows for fewer pointers
  10.     being passed between functions. This allows us to call fpin and
  11.     fpout from any function, whilst keeping the pointer at the same
  12.     location in the file. It is important to remember not to reopen
  13.     the file as to not reset the file pointer.
  14. */
  15.  
  16. typedef struct item {
  17.     int itemID;
  18.     int numParts;
  19. } item;
  20.  
  21. typedef struct recipe {
  22.     int numItems;
  23.     item* itemList;
  24.     int totalParts;
  25. } recipe;
  26.  
  27. typedef struct Quantity {
  28.     int recipeID;
  29.     int weight;
  30. } Quantity;
  31.  
  32. typedef struct Store {
  33.     int numRecipes;
  34.     Quantity* quantities;
  35.     double* totalQuantities;
  36. } Store;
  37.  
  38. char** readIngredients(int *numIngredients){
  39.    
  40.     // Defining the list of ingredients up to 10^5 ingredients
  41.    
  42.     char** ingredientList = (char**)malloc((*numIngredients)*(sizeof(char*)));
  43.    
  44.     for(int i = 0; i < *numIngredients; i++) {
  45.        
  46.         /*
  47.             We define a packing string that is more than long enough
  48.             to hold a single ingredient. We then malloc a pointer
  49.             exactly long enough to hold the string, plus one character
  50.             for the '\n', to hold the string which will get passed into
  51.             the character array.
  52.            
  53.             The documentation mentioned that each ingredient would be
  54.             up to 20 characters long, but this does not account for
  55.             ingredients such as "the blood of my sworn slain enemies"
  56.             or "dead babies slain by atheists to appease Satan" and
  57.             the like.
  58.            
  59.             As of today I have learned that fgets is deprecated and
  60.             that I should be using getline instead. That way the
  61.             block of memory can easily be realloced automatically by
  62.             getline to prevent buffer overflow, which is a common
  63.             error for fgets and fscanf. The only downside is that
  64.             you can't directly typecast and need to manually
  65.             typecast data types. That way we can specify the
  66.             data to be 20 characters long, but should it encounter
  67.             any data that is longer than 20 characters, it would
  68.             dynamically allocate that memory to account for
  69.             more characters.
  70.            
  71.             Technically it should be fine to use fgets since we
  72.             know we won't return NULL. But to practice best practice
  73.             I use getline here as it will make my code better.
  74.         */
  75.         /*
  76.         size_t bufferSize = 20; // 200 characters to scan.
  77.         char* temp = (char*)malloc(bufferSize*sizeof(char));
  78.         getline(&temp, &bufferSize, fpin);  
  79.         */ //Only available on Linux systems...
  80.  
  81.         /*
  82.             Since the TAs may not be aware of the fact that fgets is
  83.             a deprecated function and may not be familiar with getline,
  84.             I will explain.
  85.            
  86.             A variable buffer size is defined of type size_t, which is
  87.             a data type used to store sizes of data types. Here we are
  88.             specifically defining the buffer size to be 200, which the
  89.             function getline can dynamically change via realloc in order
  90.             to prevent buffer overflow. The format for getline is as follows:
  91.            
  92.             getline(&charArray, &bufferSize, modeOfInput)
  93.            
  94.             and its return type is size_t. I believe this is how to
  95.             implement the code in best practice, although typecasting
  96.             is now required should you wish to extract an int or something
  97.             from the line. The function reads a line up to and including
  98.             the newline terminator
  99.            
  100.             The strcspn function (I don't know what it stands for) returns
  101.             the index of string1 where string2 is first found. That way we
  102.             can use it to find the first instance of the newline character
  103.             and remove it, so that we are left with just plain text. Man,
  104.             I've been wondering how to do that for years and TIL how to
  105.             do that. Thanks StackOverflow! That is a very useful function
  106.             to learn and I feel like it needs to get the same attention
  107.             as strcpy and strcmp.
  108.         */
  109.  
  110.         char temp[20];
  111.         fgets(temp, 20, fpin);
  112.         temp[strcspn(temp, "\n")] = 0;
  113.         // Quick fix, this is unstable which is why I wanted
  114.         // to use getline...
  115.        
  116.         char* ingredient = (char*)malloc((strlen(temp)+1)*sizeof(char));
  117.         strcpy(ingredient, temp);
  118.         ingredient[strcspn(ingredient, "\n")] = 0;
  119.         ingredientList[i] = ingredient;    
  120.         //free(temp); //Only necessary for getline...
  121.     }
  122.    
  123.     return ingredientList;
  124.    
  125. }
  126.  
  127. recipe* readRecipe(double** weights){
  128.    
  129.     /*
  130.         Why is this a separate function? It's not too much code to
  131.         put in the main recipe function. We are playing a dangerous
  132.         game with the file pointer here. I feel like it is bad practice
  133.         to define the file pointer globally instead of manually shuffling
  134.         it around because calling this function after you call fclose would
  135.         lead to a segfault I think... I do not think this project was well
  136.         thought out, as it is encouraging some bad practice habits. Does it
  137.         work? Yes. Will it always work if we write the code right? Yes. But
  138.         taking shortcuts only helps in the short term. Effectively writing your
  139.         code so that you can dynamically use snippets in other settings without
  140.         dependencies is far better practice and a good habit to have. That's why
  141.         I wish I could've wrote this code on my own without having to use these
  142.         predefined declarations. I don't like taking shortcuts in my code when I
  143.         know something is bad practice.
  144.        
  145.         But anyway, we are going to use the same trick that we did before to
  146.         account for any size of the input array. Then we will tokenize it
  147.         using strtok, and use atoi to cast it to an int. While atoi is
  148.         bad practice and it seems strtol should be used instead, strtol
  149.         requires knowledge of the end pointer, which in this use case is
  150.         more trouble than it is worth, especially since we can guarantee
  151.         what we find is an integer. It would technically be better to use
  152.         atof, since it is reasonable for us to find fractional parts in
  153.         recipes (sometimes it's easier to use fractional parts than to
  154.         find the least common integer multiple of all the parts), but the
  155.         defined structs require us to use ints... I suppose that makes sense
  156.         since you rarely see ratios involve nonints.
  157.     */
  158.    
  159.     recipe* currentRecipe = (recipe*)malloc(sizeof(recipe));
  160.    
  161.     /*
  162.     size_t bufferSize = 50; // We are assuming 50 characters, but this can dynamically change to however many.
  163.     char* temp = (char*)malloc(bufferSize*sizeof(char));
  164.     getline(&temp, &bufferSize, fpin);
  165.     */ // Apparently getline is only available on GNU systems.
  166.  
  167.     char temp[200];
  168.     fgets(temp, 200, fpin);
  169.     temp[strcspn(temp, "\n")] = 0;
  170.     //Replacement code...
  171.  
  172.     int numItems_ = atoi(strtok(temp, " "));
  173.     currentRecipe->numItems = numItems_;
  174.    
  175.     item* itemList_ = (item*)malloc(numItems_*sizeof(item));
  176.     int totalParts_ = 0;
  177.     /*
  178.         Note that in best practice code, numInputs isn't even necessary as we can just loop until
  179.         we hit a null terminator. But since we are casting to ints and not keeping strings, it's
  180.         probably best to do this the lazy way.
  181.                
  182.         Back in Intro to C I read from a CSV into a struct, which is where I learned to tokenize.
  183.         This allows for easy breakdown of strings with differing delimiters, but also easy breakdown
  184.         of dynamically lengthed strings of varying data types.
  185.     */
  186.    
  187.     for(int i = 0; i < numItems_; i++){
  188.         itemList_[i].itemID = atoi(strtok(NULL," ")); // Casting the tokenized string to an int.
  189.         itemList_[i].numParts = atoi(strtok(NULL," ")); // Pointer is NULL as we are using the previously set one.
  190.         totalParts_ += itemList_[i].numParts; // Sum of parts.
  191.         weights[itemList_[i].itemID]+=itemList_[i].numParts;
  192.     }
  193.    
  194.     currentRecipe->itemList = itemList_;    // Passing the pointer of itemList_ to the struct.
  195.     currentRecipe->totalParts = totalParts_; // Passing the sum of itemList_ to the struct.
  196.    
  197.     return currentRecipe;
  198. }
  199.  
  200. recipe** readAllRecipes(int* numRecipes, double** amtOfEachItem, int* numIngredients) {
  201.    
  202.     /*
  203.         I suppose I would've just done all this in one function,
  204.         instead of having nested functions because it's a simple
  205.         matter of using getdelim instead of getline... although
  206.         the edge case where the last character is a newline, not
  207.         a space, might need to get handled... I might end up
  208.         implementing the old method I used when I built a csv
  209.         parser and just use strtok, as that does not need to
  210.         account for fringe cases... then again, since strtok
  211.         doesn't have a fringe case issue, it would just encounter
  212.         the \0 and terminate I suppose. But getdelim WOULD encounter
  213.         the newline and have issue with that. If the TA is more
  214.         familiar with getdelim and knows how to avoid the
  215.         fringe case situation without hardcoding the fringe
  216.         handling, let me know, since it would be more efficient
  217.         to directly tokenize the string while reading it, than
  218.         read the whole string, then tokenize it.
  219.        
  220.         Otherwise this specific portion of the code works exactly
  221.         the same as readIngredients(), as it simply allocates
  222.         an array of recipe structs and then reads a single
  223.         struct into the array over the specified range.
  224.     */
  225.  
  226.     recipe** recipeList = (recipe**)malloc(*numRecipes*sizeof(recipe*));
  227.  
  228.     for(int i = 0; i < *numRecipes; i++){
  229.         amtOfEachItem[i]=(double*)malloc(*numIngredients*sizeof(double));
  230.         recipe* singleRecipe = readRecipe(amtOfEachItem);
  231.         int j = 0;
  232.         item* getItem = singleRecipe->itemList;
  233.         while(getItem != NULL){
  234.             amtOfEachItem[i][getItem->itemID]+=getItem->numParts/singleRecipe->totalParts;
  235.             getItem++;
  236.         }
  237.         recipeList[i] = singleRecipe;
  238.     }
  239.    
  240.     return recipeList;
  241.    
  242.    
  243. }
  244.  
  245. double* addWeights(double** amtOfEachItem, Store* currentStore, int* numIngredients){
  246.     int i = 0;
  247.     Quantity* quantities_ = currentStore->quantities;
  248.     double* weightedQuants = (double*)calloc(*numIngredients,sizeof(double));
  249.     while(quantities_ != NULL){
  250.         double* temp = amtOfEachItem[quantities_[i].recipeID];
  251.         int j = 0;
  252.         while(temp != NULL){
  253.             weightedQuants[j] += temp[j]*quantities_[i].weight;
  254.             j++;
  255.             temp++;
  256.         }
  257.     }
  258.  
  259.     return weightedQuants;
  260.  
  261. }
  262.  
  263. Store* readStore(double** amtOfEachItem, int* numIngredients){
  264.    
  265.     /*
  266.         The exact same logic as readRecipe()
  267.     */
  268.    
  269.     Store* currentStore = (Store*)malloc(sizeof(Store));
  270.    
  271.     /*size_t bufferSize = 50;
  272.     char* temp = (char*)malloc(bufferSize*sizeof(char));
  273.     getline(&temp, &bufferSize, fpin);
  274.     */ // Same issue as before...
  275.  
  276.     char temp[200];
  277.     fgets(temp, 200, fpin);
  278.     temp[strcspn(temp, "\n")] = 0;
  279.     //Replacement code...
  280.  
  281.     int numRecipes_ = atoi(strtok(temp, " " ));
  282.     currentStore->numRecipes = numRecipes_;
  283.    
  284.     Quantity* quantityList_ = (Quantity*)malloc(numRecipes_*sizeof(Quantity));
  285.    
  286.     for(int i = 0; i < numRecipes_; i++){
  287.         quantityList_[i].recipeID = atoi(strtok(NULL," "));
  288.         quantityList_[i].weight = atoi(strtok(NULL," "));
  289.     }
  290.    
  291.     currentStore->quantities = quantityList_;
  292.     currentStore->totalQuantities = addWeights(amtOfEachItem, currentStore, numIngredients);
  293.    
  294.     return currentStore;
  295.    
  296. }
  297.  
  298. Store** readAllStores(int* numStores, double** amtOfEachItem, int* numIngredients){
  299.     /*
  300.         The exact same logic as readAllRecipes
  301.     */
  302.  
  303.     Store** storeList = (Store**)malloc(*numStores*sizeof(Store*));
  304.    
  305.     for(int i = 0; i < *numStores; i++){
  306.         Store* singleStore = readStore(amtOfEachItem, numIngredients);
  307.         storeList[i] = singleStore;
  308.     }
  309.    
  310.     return storeList;
  311. }
  312.  
  313. void printOutput(Store** storeList, char** ingredientList){
  314.     int i = 0;
  315.     while(storeList != NULL){
  316.         int j = 0;
  317.         printf("Store #%d:\n", i+1);
  318.         while(storeList[i]->totalQuantities != NULL){
  319.             printf("%s %0.6lf\n",ingredientList[j],storeList[i]->totalQuantities[j]);
  320.             fprintf(fpout, "%s %0.6lf\n",ingredientList[j],storeList[i]->totalQuantities[j]);
  321.             storeList[i]->totalQuantities++;
  322.             j++;
  323.         }
  324.         storeList++;
  325.         i++;
  326.     }
  327. }
  328.  
  329.  
  330.  
  331.  
  332.  
  333. int main(){
  334.    
  335.     fpin = fopen("sample_in1.txt","r");
  336.     fpout = fopen("out.txt", "w");
  337.    
  338.     /*
  339.         This line is necessary for error handling.
  340.         Without this, the code might segfault.
  341.     */
  342.    
  343.     if(fpin == NULL) {
  344.             fprintf(stderr, "Could not open in file. Exiting now.");
  345.     }
  346.    
  347. /* ============================================================================= */
  348. /*               Part 1: Reading The Possible Ingredients List                   */
  349. /* ============================================================================= */
  350.    
  351.     int numIngredients; // This value ranges from 1 to 10^5.
  352.            
  353.     fscanf(fpin, "%d", &numIngredients);
  354.     numIngredients++; // Add buffer
  355.    
  356.     if(numIngredients > 10000){
  357.             fprintf(stderr,"Ingredients list too large. Aborting.");
  358.     }
  359.    
  360.     char** ingredientList = readIngredients(&numIngredients);
  361.    
  362.     /*
  363.         numIngredients is defined to hold the number of ingredients that need
  364.         to be read in the following numIngredients lines. For whatever reason
  365.         the given required function declaration takes a pointer, even though there
  366.         is no real reason for it to be a pointer, except possibly memory, as it
  367.         temporarily would save 4 bytes of memory, I suppose. Is it faster in any
  368.         way? Either way, we constrain numIngredients to be under the specified
  369.         range given in the documentation. It isn't necessary to do this, but
  370.         in a production setting, such a parameter would need to be considered
  371.         and error handling measures needs to be deployed.
  372.        
  373.         I replaced the standard printf function with fprintf, as in my research
  374.         I discovered that C has a built in error handling method, in which
  375.         outputs are printed to a channel called stderr. This will be used
  376.         to print any errors found.
  377.        
  378.         readIngredients() then loops over the next numIngredients lines and
  379.         scans them into a character array, of which then the pointers are
  380.         stored into an array of strings, and then returned to the main function.
  381.         Had I been in charge of writing the declarations, I'd have had a void
  382.         function that takes a pointer to an array defined in main, as it is
  383.         much harder to run into memory leak issues that way.
  384.        
  385.     */
  386.    
  387. /* ============================================================================= */
  388. /*               Part 2: Reading The Possible Ingredients List                   */
  389. /* ============================================================================= */
  390.    
  391.     int numRecipes; // This value ranges from 1 to 10^5.
  392.        
  393.     fscanf(fpin,"%d", &numRecipes);
  394.     numRecipes++; // Add buffer
  395.  
  396.     if(numRecipes > 10000){
  397.             fprintf(stderr,"Recipes list too large. Aborting.");
  398.     }
  399.    
  400.     double** amtOfEachItem = (double**)malloc(numRecipes*sizeof(double*));
  401.     printf("Reading recipes...");
  402.     recipe** smoothieList = readAllRecipes(&numRecipes, amtOfEachItem, &numIngredients);
  403.     printf("Read recipes.");
  404.     /*
  405.         Like last time, we read the number of lines we want the function to read,
  406.         and pass it as a pointer to the function readAllRecipes(). Again, I'm
  407.         not quite sure why we have to pass it as a pointer, as we are not planning
  408.         on manipulating this value in any way, and it is very static in its value.
  409.         Again, the size is constrained, and we throw an error if the size is too
  410.         big.
  411.        
  412.        
  413.     */
  414. /* ============================================================================= */
  415. /*               Part 3: Reading The Store List                                  */
  416. /* ============================================================================= */
  417.  
  418.     int numStores; // This value ranges from 1 to 100.
  419.    
  420.     fscanf(fpin,"%d", &numStores);
  421.     numStores++; // Add buffer
  422.     if(numStores > 100){
  423.             fprintf(stderr,"Stores list too large. Aborting.");
  424.     }
  425.    
  426.    
  427.     Store** storeList = readAllStores(&numStores, amtOfEachItem, &numIngredients);
  428.    
  429.     /*
  430.         Self explanatory. Exactly the same as the last section, since
  431.         they are effectively doing the same thing on similarly structured
  432.         data.
  433.     */
  434.  
  435. /* ============================================================================= */
  436. /*               Part 4: Calculating The Data                                    */
  437. /* ============================================================================= */
  438.  
  439.     /*
  440.  
  441.         The functions were reorganized to be the most efficient as possible
  442.         in order to avoid repetitive recursive for loops on deep arrays.
  443.         If the functions were kept as it had been, multiple deep for loops
  444.         would've had to be used to access indexes of values within arrays.
  445.  
  446.         The use of strategically passing pointers allowed us to do the
  447.         calculation WITHIN the allocation of the store array itself, so
  448.         that minimum time complexity is needed to process the array.
  449.  
  450.     */
  451.  
  452. /* ============================================================================= */
  453. /*               Part 5: Outputting The Data                                     */
  454. /* ============================================================================= */
  455.  
  456.     printOutput(storeList, ingredientList);
  457.     fclose(fpin);
  458.     fclose(fpout);
  459.    
  460. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement