Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- FILE *fpin;
- FILE *fpout;
- /*
- Defining file pointers at a global scope allows for fewer pointers
- being passed between functions. This allows us to call fpin and
- fpout from any function, whilst keeping the pointer at the same
- location in the file. It is important to remember not to reopen
- the file as to not reset the file pointer.
- */
- typedef struct item {
- int itemID;
- int numParts;
- } item;
- typedef struct recipe {
- int numItems;
- item* itemList;
- int totalParts;
- } recipe;
- typedef struct Quantity {
- int recipeID;
- int weight;
- } Quantity;
- typedef struct Store {
- int numRecipes;
- Quantity* quantities;
- double* totalQuantities;
- } Store;
- char** readIngredients(int *numIngredients){
- // Defining the list of ingredients up to 10^5 ingredients
- char** ingredientList = (char**)malloc((*numIngredients)*(sizeof(char*)));
- for(int i = 0; i < *numIngredients; i++) {
- /*
- We define a packing string that is more than long enough
- to hold a single ingredient. We then malloc a pointer
- exactly long enough to hold the string, plus one character
- for the '\n', to hold the string which will get passed into
- the character array.
- The documentation mentioned that each ingredient would be
- up to 20 characters long, but this does not account for
- ingredients such as "the blood of my sworn slain enemies"
- or "dead babies slain by atheists to appease Satan" and
- the like.
- As of today I have learned that fgets is deprecated and
- that I should be using getline instead. That way the
- block of memory can easily be realloced automatically by
- getline to prevent buffer overflow, which is a common
- error for fgets and fscanf. The only downside is that
- you can't directly typecast and need to manually
- typecast data types. That way we can specify the
- data to be 20 characters long, but should it encounter
- any data that is longer than 20 characters, it would
- dynamically allocate that memory to account for
- more characters.
- Technically it should be fine to use fgets since we
- know we won't return NULL. But to practice best practice
- I use getline here as it will make my code better.
- */
- /*
- size_t bufferSize = 20; // 200 characters to scan.
- char* temp = (char*)malloc(bufferSize*sizeof(char));
- getline(&temp, &bufferSize, fpin);
- */ //Only available on Linux systems...
- /*
- Since the TAs may not be aware of the fact that fgets is
- a deprecated function and may not be familiar with getline,
- I will explain.
- A variable buffer size is defined of type size_t, which is
- a data type used to store sizes of data types. Here we are
- specifically defining the buffer size to be 200, which the
- function getline can dynamically change via realloc in order
- to prevent buffer overflow. The format for getline is as follows:
- getline(&charArray, &bufferSize, modeOfInput)
- and its return type is size_t. I believe this is how to
- implement the code in best practice, although typecasting
- is now required should you wish to extract an int or something
- from the line. The function reads a line up to and including
- the newline terminator
- The strcspn function (I don't know what it stands for) returns
- the index of string1 where string2 is first found. That way we
- can use it to find the first instance of the newline character
- and remove it, so that we are left with just plain text. Man,
- I've been wondering how to do that for years and TIL how to
- do that. Thanks StackOverflow! That is a very useful function
- to learn and I feel like it needs to get the same attention
- as strcpy and strcmp.
- */
- char temp[20];
- fgets(temp, 20, fpin);
- temp[strcspn(temp, "\n")] = 0;
- // Quick fix, this is unstable which is why I wanted
- // to use getline...
- char* ingredient = (char*)malloc((strlen(temp)+1)*sizeof(char));
- strcpy(ingredient, temp);
- ingredient[strcspn(ingredient, "\n")] = 0;
- ingredientList[i] = ingredient;
- //free(temp); //Only necessary for getline...
- }
- return ingredientList;
- }
- recipe* readRecipe(double** weights){
- /*
- Why is this a separate function? It's not too much code to
- put in the main recipe function. We are playing a dangerous
- game with the file pointer here. I feel like it is bad practice
- to define the file pointer globally instead of manually shuffling
- it around because calling this function after you call fclose would
- lead to a segfault I think... I do not think this project was well
- thought out, as it is encouraging some bad practice habits. Does it
- work? Yes. Will it always work if we write the code right? Yes. But
- taking shortcuts only helps in the short term. Effectively writing your
- code so that you can dynamically use snippets in other settings without
- dependencies is far better practice and a good habit to have. That's why
- I wish I could've wrote this code on my own without having to use these
- predefined declarations. I don't like taking shortcuts in my code when I
- know something is bad practice.
- But anyway, we are going to use the same trick that we did before to
- account for any size of the input array. Then we will tokenize it
- using strtok, and use atoi to cast it to an int. While atoi is
- bad practice and it seems strtol should be used instead, strtol
- requires knowledge of the end pointer, which in this use case is
- more trouble than it is worth, especially since we can guarantee
- what we find is an integer. It would technically be better to use
- atof, since it is reasonable for us to find fractional parts in
- recipes (sometimes it's easier to use fractional parts than to
- find the least common integer multiple of all the parts), but the
- defined structs require us to use ints... I suppose that makes sense
- since you rarely see ratios involve nonints.
- */
- recipe* currentRecipe = (recipe*)malloc(sizeof(recipe));
- /*
- size_t bufferSize = 50; // We are assuming 50 characters, but this can dynamically change to however many.
- char* temp = (char*)malloc(bufferSize*sizeof(char));
- getline(&temp, &bufferSize, fpin);
- */ // Apparently getline is only available on GNU systems.
- char temp[200];
- fgets(temp, 200, fpin);
- temp[strcspn(temp, "\n")] = 0;
- //Replacement code...
- int numItems_ = atoi(strtok(temp, " "));
- currentRecipe->numItems = numItems_;
- item* itemList_ = (item*)malloc(numItems_*sizeof(item));
- int totalParts_ = 0;
- /*
- Note that in best practice code, numInputs isn't even necessary as we can just loop until
- we hit a null terminator. But since we are casting to ints and not keeping strings, it's
- probably best to do this the lazy way.
- Back in Intro to C I read from a CSV into a struct, which is where I learned to tokenize.
- This allows for easy breakdown of strings with differing delimiters, but also easy breakdown
- of dynamically lengthed strings of varying data types.
- */
- for(int i = 0; i < numItems_; i++){
- itemList_[i].itemID = atoi(strtok(NULL," ")); // Casting the tokenized string to an int.
- itemList_[i].numParts = atoi(strtok(NULL," ")); // Pointer is NULL as we are using the previously set one.
- totalParts_ += itemList_[i].numParts; // Sum of parts.
- weights[itemList_[i].itemID]+=itemList_[i].numParts;
- }
- currentRecipe->itemList = itemList_; // Passing the pointer of itemList_ to the struct.
- currentRecipe->totalParts = totalParts_; // Passing the sum of itemList_ to the struct.
- return currentRecipe;
- }
- recipe** readAllRecipes(int* numRecipes, double** amtOfEachItem, int* numIngredients) {
- /*
- I suppose I would've just done all this in one function,
- instead of having nested functions because it's a simple
- matter of using getdelim instead of getline... although
- the edge case where the last character is a newline, not
- a space, might need to get handled... I might end up
- implementing the old method I used when I built a csv
- parser and just use strtok, as that does not need to
- account for fringe cases... then again, since strtok
- doesn't have a fringe case issue, it would just encounter
- the \0 and terminate I suppose. But getdelim WOULD encounter
- the newline and have issue with that. If the TA is more
- familiar with getdelim and knows how to avoid the
- fringe case situation without hardcoding the fringe
- handling, let me know, since it would be more efficient
- to directly tokenize the string while reading it, than
- read the whole string, then tokenize it.
- Otherwise this specific portion of the code works exactly
- the same as readIngredients(), as it simply allocates
- an array of recipe structs and then reads a single
- struct into the array over the specified range.
- */
- recipe** recipeList = (recipe**)malloc(*numRecipes*sizeof(recipe*));
- for(int i = 0; i < *numRecipes; i++){
- amtOfEachItem[i]=(double*)malloc(*numIngredients*sizeof(double));
- recipe* singleRecipe = readRecipe(amtOfEachItem);
- int j = 0;
- item* getItem = singleRecipe->itemList;
- while(getItem != NULL){
- amtOfEachItem[i][getItem->itemID]+=getItem->numParts/singleRecipe->totalParts;
- getItem++;
- }
- recipeList[i] = singleRecipe;
- }
- return recipeList;
- }
- double* addWeights(double** amtOfEachItem, Store* currentStore, int* numIngredients){
- int i = 0;
- Quantity* quantities_ = currentStore->quantities;
- double* weightedQuants = (double*)calloc(*numIngredients,sizeof(double));
- while(quantities_ != NULL){
- double* temp = amtOfEachItem[quantities_[i].recipeID];
- int j = 0;
- while(temp != NULL){
- weightedQuants[j] += temp[j]*quantities_[i].weight;
- j++;
- temp++;
- }
- }
- return weightedQuants;
- }
- Store* readStore(double** amtOfEachItem, int* numIngredients){
- /*
- The exact same logic as readRecipe()
- */
- Store* currentStore = (Store*)malloc(sizeof(Store));
- /*size_t bufferSize = 50;
- char* temp = (char*)malloc(bufferSize*sizeof(char));
- getline(&temp, &bufferSize, fpin);
- */ // Same issue as before...
- char temp[200];
- fgets(temp, 200, fpin);
- temp[strcspn(temp, "\n")] = 0;
- //Replacement code...
- int numRecipes_ = atoi(strtok(temp, " " ));
- currentStore->numRecipes = numRecipes_;
- Quantity* quantityList_ = (Quantity*)malloc(numRecipes_*sizeof(Quantity));
- for(int i = 0; i < numRecipes_; i++){
- quantityList_[i].recipeID = atoi(strtok(NULL," "));
- quantityList_[i].weight = atoi(strtok(NULL," "));
- }
- currentStore->quantities = quantityList_;
- currentStore->totalQuantities = addWeights(amtOfEachItem, currentStore, numIngredients);
- return currentStore;
- }
- Store** readAllStores(int* numStores, double** amtOfEachItem, int* numIngredients){
- /*
- The exact same logic as readAllRecipes
- */
- Store** storeList = (Store**)malloc(*numStores*sizeof(Store*));
- for(int i = 0; i < *numStores; i++){
- Store* singleStore = readStore(amtOfEachItem, numIngredients);
- storeList[i] = singleStore;
- }
- return storeList;
- }
- void printOutput(Store** storeList, char** ingredientList){
- int i = 0;
- while(storeList != NULL){
- int j = 0;
- printf("Store #%d:\n", i+1);
- while(storeList[i]->totalQuantities != NULL){
- printf("%s %0.6lf\n",ingredientList[j],storeList[i]->totalQuantities[j]);
- fprintf(fpout, "%s %0.6lf\n",ingredientList[j],storeList[i]->totalQuantities[j]);
- storeList[i]->totalQuantities++;
- j++;
- }
- storeList++;
- i++;
- }
- }
- int main(){
- fpin = fopen("sample_in1.txt","r");
- fpout = fopen("out.txt", "w");
- /*
- This line is necessary for error handling.
- Without this, the code might segfault.
- */
- if(fpin == NULL) {
- fprintf(stderr, "Could not open in file. Exiting now.");
- }
- /* ============================================================================= */
- /* Part 1: Reading The Possible Ingredients List */
- /* ============================================================================= */
- int numIngredients; // This value ranges from 1 to 10^5.
- fscanf(fpin, "%d", &numIngredients);
- numIngredients++; // Add buffer
- if(numIngredients > 10000){
- fprintf(stderr,"Ingredients list too large. Aborting.");
- }
- char** ingredientList = readIngredients(&numIngredients);
- /*
- numIngredients is defined to hold the number of ingredients that need
- to be read in the following numIngredients lines. For whatever reason
- the given required function declaration takes a pointer, even though there
- is no real reason for it to be a pointer, except possibly memory, as it
- temporarily would save 4 bytes of memory, I suppose. Is it faster in any
- way? Either way, we constrain numIngredients to be under the specified
- range given in the documentation. It isn't necessary to do this, but
- in a production setting, such a parameter would need to be considered
- and error handling measures needs to be deployed.
- I replaced the standard printf function with fprintf, as in my research
- I discovered that C has a built in error handling method, in which
- outputs are printed to a channel called stderr. This will be used
- to print any errors found.
- readIngredients() then loops over the next numIngredients lines and
- scans them into a character array, of which then the pointers are
- stored into an array of strings, and then returned to the main function.
- Had I been in charge of writing the declarations, I'd have had a void
- function that takes a pointer to an array defined in main, as it is
- much harder to run into memory leak issues that way.
- */
- /* ============================================================================= */
- /* Part 2: Reading The Possible Ingredients List */
- /* ============================================================================= */
- int numRecipes; // This value ranges from 1 to 10^5.
- fscanf(fpin,"%d", &numRecipes);
- numRecipes++; // Add buffer
- if(numRecipes > 10000){
- fprintf(stderr,"Recipes list too large. Aborting.");
- }
- double** amtOfEachItem = (double**)malloc(numRecipes*sizeof(double*));
- printf("Reading recipes...");
- recipe** smoothieList = readAllRecipes(&numRecipes, amtOfEachItem, &numIngredients);
- printf("Read recipes.");
- /*
- Like last time, we read the number of lines we want the function to read,
- and pass it as a pointer to the function readAllRecipes(). Again, I'm
- not quite sure why we have to pass it as a pointer, as we are not planning
- on manipulating this value in any way, and it is very static in its value.
- Again, the size is constrained, and we throw an error if the size is too
- big.
- */
- /* ============================================================================= */
- /* Part 3: Reading The Store List */
- /* ============================================================================= */
- int numStores; // This value ranges from 1 to 100.
- fscanf(fpin,"%d", &numStores);
- numStores++; // Add buffer
- if(numStores > 100){
- fprintf(stderr,"Stores list too large. Aborting.");
- }
- Store** storeList = readAllStores(&numStores, amtOfEachItem, &numIngredients);
- /*
- Self explanatory. Exactly the same as the last section, since
- they are effectively doing the same thing on similarly structured
- data.
- */
- /* ============================================================================= */
- /* Part 4: Calculating The Data */
- /* ============================================================================= */
- /*
- The functions were reorganized to be the most efficient as possible
- in order to avoid repetitive recursive for loops on deep arrays.
- If the functions were kept as it had been, multiple deep for loops
- would've had to be used to access indexes of values within arrays.
- The use of strategically passing pointers allowed us to do the
- calculation WITHIN the allocation of the store array itself, so
- that minimum time complexity is needed to process the array.
- */
- /* ============================================================================= */
- /* Part 5: Outputting The Data */
- /* ============================================================================= */
- printOutput(storeList, ingredientList);
- fclose(fpin);
- fclose(fpout);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement